diff --git a/.cursor/01-expertise-and-principles.mdc b/.cursor/01-expertise-and-principles.mdc
index ed8652fa..f902bfa7 100644
--- a/.cursor/01-expertise-and-principles.mdc
+++ b/.cursor/01-expertise-and-principles.mdc
@@ -5,7 +5,7 @@ scope:
globs: ["**/*"]
---
-You are an expert in **Python, FastAPI, Jinja2, Databricks SDK, Databricks REST APIs, RDF/OWL/SPARQL/SHACL standards, graph databases**, and **knowledge graph engineering**.
+You are an expert in **Python, FastAPI, Jinja2, Databricks SDK, Databricks REST APIs, RDF/OWL/SPARQL/SHACL standards, graph databases**, and **graph viewer engineering**.
### Key Principles
diff --git a/.cursor/02-project-overview.mdc b/.cursor/02-project-overview.mdc
index 60fc1263..62329449 100644
--- a/.cursor/02-project-overview.mdc
+++ b/.cursor/02-project-overview.mdc
@@ -7,13 +7,13 @@ scope:
### Project Overview
-OntoBricks is a **Python FastAPI** web application designed to run as a **Databricks App**. It provides ontology management, knowledge graph engineering, and metadata governance tools on top of Databricks Unity Catalog.
+OntoBricks is a **Python FastAPI** web application designed to run as a **Databricks App**. It provides ontology management, graph viewer engineering, and metadata governance tools on top of Databricks Unity Catalog.
**Core Features:**
- **Ontology Management:** OWL/RDFS/SHACL import/export, class/property CRUD, URI management, industry ontology imports (CDISC, FIBO, IOF). Managed by the `Ontology` domain class.
- **Mapping:** Entity/relationship mapping CRUD, R2RML generation/parsing, SQL validation, LLM-assisted auto-assign. Managed by the `Mapping` domain class.
-- **Digital Twin:** Knowledge graph build, sync, SPARQL query, quality checks, graph visualization, community detection. Managed by the `DigitalTwin` domain class.
+- **Knowledge Graph:** Knowledge graph build, sync, SPARQL query, quality checks, graph visualization, community detection. Managed by the `DigitalTwin` domain class.
- **Domain Management:** Unity Catalog save/load, version status, metadata, design layout persistence. Managed by the `Domain` domain class.
- **Registry:** Dual-mode domain registry with permissions and scheduled builds. Two storage modes, picked from the Databricks App config and surfaced in the **Settings → Registry** tab:
1. **Volume-only** — everything (metadata, indexes, documents, binaries) lives in a UC Volume.
diff --git a/.cursor/03-system-components-and-requirements.mdc b/.cursor/03-system-components-and-requirements.mdc
index 2aec1ead..9857c06f 100644
--- a/.cursor/03-system-components-and-requirements.mdc
+++ b/.cursor/03-system-components-and-requirements.mdc
@@ -12,7 +12,7 @@ scope:
- **Domain Classes:** Business logic lives in `back/objects/` (not in route handlers). Current classes:
- `Ontology` — OWL/RDFS/SHACL operations, class/property CRUD, URI management.
- `Mapping` — entity/relationship mapping CRUD, R2RML, SQL validation, auto-assign.
- - `DigitalTwin` — knowledge graph build, sync, query, quality.
+ - `DigitalTwin` — graph viewer build, sync, query, quality.
- `Domain` — UC save/load, version status, metadata, layout persistence.
- **Core Infrastructure:** `back/core/` hosts shared infrastructure with no dependency on HTTP/FastAPI types: Databricks clients, triple store, graph DB, W3C parsers, reasoning engines, agents, logging, errors.
- **Internal API:** Session-aware JSON endpoints in `api/routers/internal/`, delegating to domain classes.
diff --git a/.cursor/04-project-structure.mdc b/.cursor/04-project-structure.mdc
index 904dfb6f..69e01839 100644
--- a/.cursor/04-project-structure.mdc
+++ b/.cursor/04-project-structure.mdc
@@ -49,7 +49,7 @@ back/objects/
├── domain/ <- Session-scoped UC/metadata/layout operations
├── ontology/ <- Ontology domain class
├── mapping/ <- Mapping domain class
-└── digitaltwin/ <- Digital Twin domain class
+└── digitaltwin/ <- Knowledge Graph domain class
```
### Backend Core Infrastructure (`src/back/core/`)
diff --git a/.cursor/07-project-conventions.mdc b/.cursor/07-project-conventions.mdc
index dc37e536..9e43a38f 100644
--- a/.cursor/07-project-conventions.mdc
+++ b/.cursor/07-project-conventions.mdc
@@ -115,3 +115,39 @@ scope:
# Bad
from back.objects.registry import RegistryService
```
+
+### Merge Safety (feature branch → develop)
+
+When merging `develop` into a long-lived feature branch (or resolving conflicts before a PR):
+
+1. **Feature branch is always the source of truth.** Resolve every conflict with `--ours`
+ unless the change from `develop` is a genuine independent improvement to a shared file.
+
+2. **Auto-merges are not safe.** Git can silently resolve a file with no conflict markers
+ while still discarding feature content (it picks one side's structure). Always verify
+ critical wiring files after the merge, even when git reports success.
+
+3. **After any merge, diff these files between the pre-merge tip and the new HEAD:**
+ ```bash
+ git diff HEAD -- \
+ src/front/templates/base.html \
+ src/front/templates/domain.html \
+ src/front/config/menu_config.json \
+ src/api/routers/internal/__init__.py \
+ src/front/static/ontology/js/ontology-map.js
+ ```
+ These are the most likely victims of silent auto-merge data loss because both branches
+ edit them for unrelated reasons.
+
+4. **Restore silently-lost files** from the pre-merge feature commit:
+ ```bash
+ git checkout --
+ git add
+ ```
+
+5. **Run the full test suite** (`pytest tests/units/ -q`) before committing the merge.
+ A passing suite does not guarantee the UI is intact, but a failing one always indicates
+ a problem.
+
+6. **Commit message** for the merge should list which conflict types were resolved and
+ which files were restored, so reviewers can audit the decision.
diff --git a/.cursor/11-frontend-design.mdc b/.cursor/11-frontend-design.mdc
index f473d0bc..6e793928 100644
--- a/.cursor/11-frontend-design.mdc
+++ b/.cursor/11-frontend-design.mdc
@@ -91,8 +91,10 @@ the entire app chrome — never duplicate or substitute any of it:
| Element | Provided by `base.html` | Don't reimplement |
|---------|-------------------------|-------------------|
-| Navbar (top) | `` driven by `menu_config.json` | a custom top bar |
-| Breadcrumb | `#obBreadcrumb` / `.ob-breadcrumb`, populated by `breadcrumb.js` | a per-page breadcrumb |
+| L1 navbar (top) | `` driven by `menu_config.json` | a custom top bar |
+| L2 subnav | `` — hidden until a domain is loaded (`hasDomain`), contains Domain / Ontology / Mapping / KG dropdowns + breadcrumb + Save Domain button | a second nav element |
+| Breadcrumb | `#obBreadcrumb` inside `#obBreadcrumbWrap` (`` in the L2 subnav row), populated by `breadcrumb.js` | a per-page breadcrumb |
+| Save Domain button | `#menuSaveDomain` (`.ob-subnav-save-btn`) in the right side of the L2 subnav | a per-page save button |
| Domain loading overlay | `#domainLoadingOverlay` / `.domain-loading-overlay` | another full-page spinner |
| Notification Center dropdown | `#notifCenterDropdown` (bell) | a per-page notif list |
| Task Tracker dropdown | `#taskTrackerDropdown` (hourglass) | a per-page task panel |
@@ -273,60 +275,120 @@ keyboard handling.
- ❌ Adjusting `--ob-chrome-height` from page code — the breadcrumb
computes it.
+### Two-Level Navigation (L1 + L2 subnav)
+
+The app chrome has **two horizontal nav levels**, both provided by `base.html`.
+
+#### L1 Navbar
+
+- Standard Bootstrap ``.
+- Shows: brand logo, Registry link, a `·` separator, a `›` path
+ separator, and the Domain link (`#domainL1Link`).
+- `#domainL1Link` is disabled (`.ob-nav-disabled`, `pointer-events: none`)
+ when no domain is loaded. Do not enable it from page code.
+- Other menus (Digital Twin) may be hidden via `"navbar_hidden": true`
+ in `menu_config.json`.
+
+#### L2 Subnav (`#obSubnav`)
+
+- `` — hidden by default.
+- Revealed by `updateDomainMenuVisibility(true)` in `navbar.js` when
+ `hasDomain` becomes true (domain has a name or content in session).
+- Layout (left → right inside `.ob-subnav-nav`):
+ 1. **Domain dropdown** (`#subnavDomainDropdown`) — all domain sub-sections.
+ 2. A `.ob-subnav-divider`.
+ 3. **Ontology dropdown** (`#subnavOntologyDropdown`).
+ 4. **Mapping dropdown** (`#subnavMappingDropdown`).
+ 5. **Knowledge Graph dropdown** (`#subnavKgDropdown`).
+ 6. A `.ob-subnav-flex-spacer` (`flex: 1`) — always pushes the right group to the edge.
+ 7. **Breadcrumb** (`#obBreadcrumbWrap`) — hidden until >1 crumb.
+ 8. **Save Domain button** (`#menuSaveDomain`, `.ob-subnav-save-btn`).
+- The three tab toggles (Ontology/Mapping/KG) do **not** carry
+ `nav-requires-domain`. They are enabled whenever the subnav is
+ visible. Specific dropdown items that truly need a UC-saved domain
+ keep `dropdown-requires-domain`.
+- `--ob-chrome-height` = navbar height + subnav height (subnav already
+ contains the breadcrumb). Updated by `_updateChromeHeight()` in
+ `breadcrumb.js` and called from `updateDomainMenuVisibility`.
+
+**Tokens**
+
+| Level | Token | Value |
+|-------|-------|-------|
+| L1 navbar background | `--db-navbar-bg` | `#ECECEC` |
+| L2 subnav background | `--db-subnav-bg` | `#F4F4F4` (lighter than L1) |
+| Breadcrumb | white (page background) | n/a |
+
+**New Domain flow**
+
+`domainNew()` in `navbar.js`:
+1. Opens `showNewDomainDialog()` (from `utils.js`) — collects name,
+ description, LLM endpoint.
+2. POSTs `/domain/clear` then `/domain/info`.
+3. Immediately opens `showDomainSaveDialog({ afterSave: '/domain/#information' })`
+ so the domain is registered in UC before navigation.
+
+`showNewDomainDialog()` is exposed as `window.showNewDomainDialog`.
+
+**Don'ts**
+
+- ❌ Adding a third nav level above or below these two.
+- ❌ Toggling `#obSubnav` visibility from page code — it is controlled
+ exclusively by `updateDomainMenuVisibility` in `navbar.js`.
+- ❌ Adding `nav-requires-domain` to the L2 tab toggles — the subnav
+ itself being hidden is the gate. Use `dropdown-requires-domain` on
+ items inside a dropdown that need UC.
+- ❌ Moving the Save Domain button out of the L2 subnav.
+- ❌ Manually setting `--ob-chrome-height` from page code.
+
### Breadcrumb Behaviour & Style
-The breadcrumb is owned by `global/js/breadcrumb.js` + the
-`.ob-breadcrumb` styles in `global/css/components.css`. It is **not**
+The breadcrumb is owned by `global/js/breadcrumb.js`. It is **not**
configured per page — it is fully derived from the URL and the
currently-active sidebar section.
+**Location**
+
+The breadcrumb now lives **inside the L2 subnav row** as
+``.
+It is not a standalone `` below the navbar. `breadcrumb.js`
+shows/hides `#obBreadcrumbWrap` (not `#obBreadcrumb`).
+
**Behaviour**
-- The breadcrumb container `#obBreadcrumb` lives in `base.html` and is
- hidden by default (`d-none`). `breadcrumb.js` auto-runs on
- `DOMContentLoaded`, builds the crumbs and removes `d-none` only when
- there is more than one crumb to show. Pages that never qualify
- (e.g. home, about) keep it hidden — that is intentional, do not
- force it.
+- `breadcrumb.js` auto-runs on `DOMContentLoaded`, builds the crumbs
+ and removes `d-none` from `#obBreadcrumbWrap` only when there is
+ more than one crumb to show.
- The route hierarchy is fixed in `breadcrumb.js`:
- Registry → Domain → Ontology → Mapping → Digital Twin (plus
+ Registry → Domain → Ontology → Mapping → Knowledge Graph (plus
Settings as a flat entry). Adding a new top-level page means
- extending `_ROUTE_MAP` and `_HIERARCHY` there — not patching
- templates.
+ extending `_ROUTE_MAP` and `_HIERARCHY` — not patching templates.
- The "Domain" crumb label is read from `#currentDomainName` (set by
- the navbar) — do **not** add a separate fetch for the domain name.
+ the navbar).
- The last crumb is the active sidebar section, updated live on every
- `sidebarSectionChanged` event. Section labels come from the
- sidebar's `.nav-label` text, so renaming a sidebar item
- automatically renames the breadcrumb crumb.
-- After rendering, `breadcrumb.js` writes the total chrome height
- (navbar + breadcrumb) into the CSS variable `--ob-chrome-height`,
- which `.sidebar-layout` reads to compute the available viewport
- height. **Don't set this variable from anywhere else.**
+ `sidebarSectionChanged` event.
+- `breadcrumb.js` exposes itself as `window.OBBreadcrumb` so
+ `navbar.js` can call `OBBreadcrumb._updateChromeHeight()` after the
+ subnav appears/disappears.
+- `--ob-chrome-height` = navbar height + subnav height (breadcrumb is
+ inside the subnav, so no separate measurement). **Don't set this
+ variable from anywhere else.**
**Style**
-- Container: `.ob-breadcrumb` — `0.35rem 1rem` padding, light
- background (`var(--bs-light, #f8f9fa)`), bottom border
- (`var(--bs-border-color, #dee2e6)`), font-size `0.8rem`.
-- Separator: Bootstrap-icons chevron (`\F285`) at `0.65rem`. Do not
- switch to `/`, `>`, or any other glyph.
-- Links: `var(--bs-secondary)`, no underline; underline only on
- `:hover`.
-- Active crumb (last item): `var(--bs-body-color)`, weight 500, no
- link.
-- Each crumb prefixes its label with a Bootstrap icon (`bi-...`).
- Keep icons consistent with the corresponding sidebar entry.
+- Inline in the subnav row: `font-size: 0.65rem`, italic, no icons,
+ muted colour, left border separator.
+- Active crumb: `--db-text`, weight 500.
+- Links: `--db-text-muted`, no underline; underline on `:hover`.
**Don'ts**
- ❌ Manually mutating `#obBreadcrumbList` from page JS. Use the
`_ROUTE_MAP` extension or rely on `sidebarSectionChanged`.
-- ❌ Adding a per-page breadcrumb (``) — the
- global one is the only one allowed.
-- ❌ Changing the separator, font-size, or padding per page.
-- ❌ Forcing the breadcrumb visible on a single-crumb page just to
- display a title — use `.page-header` instead.
+- ❌ Adding a per-page breadcrumb — the global one is the only one allowed.
+- ❌ Toggling `#obBreadcrumb` or `#obBreadcrumbWrap` from page code.
+- ❌ Forcing the breadcrumb visible on a single-crumb page — use
+ `.page-header` instead.
### Section / Partial Conventions
@@ -644,8 +706,13 @@ These already exist; reuse before reinventing.
*Loading Spinners* for the full contract.
- **Page-level loading overlay** — `.domain-loading-overlay` already wired
in `base.html` as `#domainLoadingOverlay`. Toggle via `.d-none`.
-- **Breadcrumb** — `.ob-breadcrumb` driven by `breadcrumb.js`; do not
- build a parallel breadcrumb.
+- **Breadcrumb** — `#obBreadcrumbWrap` (`` inside `#obSubnav`),
+ driven by `breadcrumb.js`; do not build a parallel breadcrumb.
+- **Save Domain button** — `#menuSaveDomain` (`.ob-subnav-save-btn`) in the
+ L2 subnav. Enabled/disabled by `updateVersionStatus()` in
+ `domain-actions.js`. Do not add a second Save button on a page.
+- **New Domain dialog** — `showNewDomainDialog()` (from `utils.js`). Returns
+ `{ name, description, llm_endpoint }` or `null`.
- **Tabs** — `.nav.nav-tabs.ob-tabs` (flat-underline). One shared
visual treatment defined in `components.css`; see
*Tabs (`.ob-tabs`) — flat-underline style* above for the canonical
@@ -927,10 +994,16 @@ src/front/static/
`sidebarSectionChanged` event or `onBeforeSectionChange` hook.
- ❌ Setting `style.display` on `.sidebar-section` directly — toggle
the `.active` class via `SidebarNav.switchTo(...)`.
-- ❌ Writing `--ob-chrome-height` from page code — that's the
- breadcrumb's responsibility.
+- ❌ Writing `--ob-chrome-height` from page code — `breadcrumb.js` /
+ `updateDomainMenuVisibility` own it.
- ❌ A custom per-page breadcrumb. Extend `_ROUTE_MAP` in
`breadcrumb.js` instead.
+- ❌ Toggling `#obSubnav` or `#obBreadcrumbWrap` from page code.
+- ❌ Adding `nav-requires-domain` to L2 subnav tab toggles — use
+ `dropdown-requires-domain` on individual items instead.
+- ❌ A Save Domain button anywhere outside `#obSubnav`.
+- ❌ Calling `alert()` / `prompt()` for new-domain input — use
+ `showNewDomainDialog()` from `utils.js`.
- ❌ Bare `` (no `ob-tabs`), pill tabs,
or per-area redeclaration of `.nav-link` / `.nav-link.active`
styles. All tab nav uses the shared `ob-tabs` flat-underline
diff --git a/.github/workflows/changelog-presence.yml b/.github/workflows/changelog-presence.yml
index c8e02f62..a2d12479 100644
--- a/.github/workflows/changelog-presence.yml
+++ b/.github/workflows/changelog-presence.yml
@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
with:
fetch-depth: 0
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 38a237ef..0778fb6b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,7 +20,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -67,7 +67,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -99,7 +99,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -130,7 +130,7 @@ jobs:
- name: Upload coverage to Codecov
if: matrix.python-version == '3.11'
- uses: codecov/codecov-action@v6
+ uses: codecov/codecov-action@v7
with:
files: ./coverage.xml
flags: unit
@@ -152,7 +152,7 @@ jobs:
needs: test
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -166,7 +166,7 @@ jobs:
run: uv sync --dev
- name: Download coverage artifact
- uses: actions/download-artifact@v5
+ uses: actions/download-artifact@v8
with:
name: coverage-report
@@ -179,7 +179,7 @@ jobs:
timeout-minutes: 15
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
with:
fetch-depth: 2
@@ -222,7 +222,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -250,7 +250,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -275,7 +275,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
diff --git a/.github/workflows/eval-drift.yml b/.github/workflows/eval-drift.yml
index 57c15e3a..97c5555b 100644
--- a/.github/workflows/eval-drift.yml
+++ b/.github/workflows/eval-drift.yml
@@ -39,7 +39,7 @@ jobs:
- agent_auto_icon_assign
- agent_dtwin_chat
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -86,7 +86,7 @@ jobs:
permissions:
issues: write
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Open or update the eval-drift tracking issue
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -108,7 +108,7 @@ jobs:
# GATE: enable when the integration MCP server is reachable and secrets are set.
if: ${{ vars.ONTOBRICKS_INT_MCP_REACHABLE == 'true' }}
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -169,7 +169,7 @@ jobs:
permissions:
issues: write
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Open or update the mcp-smoke tracking issue
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/eval-gate.yml b/.github/workflows/eval-gate.yml
index 9e7a8c8b..d24e836e 100644
--- a/.github/workflows/eval-gate.yml
+++ b/.github/workflows/eval-gate.yml
@@ -31,7 +31,7 @@ jobs:
outputs:
changed_agents: ${{ steps.diff.outputs.changed_agents }}
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
with:
fetch-depth: 0
@@ -55,7 +55,7 @@ jobs:
needs: detect
if: needs.detect.outputs.changed_agents != ''
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Each changed agent has a SPEC.md
env:
@@ -110,7 +110,7 @@ jobs:
needs: detect
if: needs.detect.outputs.changed_agents != ''
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Each changed agent has tests/eval/datasets//baseline.jsonl
env:
diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml
index 2afd103a..bfa6d940 100644
--- a/.github/workflows/lint-pr-title.yml
+++ b/.github/workflows/lint-pr-title.yml
@@ -17,10 +17,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Set up Node
- uses: actions/setup-node@v5
+ uses: actions/setup-node@v6
with:
node-version: "20"
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index b9eec89c..3eb278ec 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -45,7 +45,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -76,7 +76,7 @@ jobs:
# External tier — only on schedule, never on PR. Requires repository secrets.
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -108,7 +108,7 @@ jobs:
needs: [property, e2e, external-smoke]
if: failure()
steps:
- - uses: actions/checkout@v6
+ - uses: actions/checkout@v7
- name: Create or update tracking issue
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index edc6bab7..fafcb3e8 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -26,7 +26,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v7
@@ -64,7 +64,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@v7
- name: Install Databricks CLI
uses: databricks/setup-cli@main
diff --git a/.planning/agents/agent_graph_interpreter/SPEC.md b/.planning/agents/agent_graph_interpreter/SPEC.md
new file mode 100644
index 00000000..fecaa897
--- /dev/null
+++ b/.planning/agents/agent_graph_interpreter/SPEC.md
@@ -0,0 +1,98 @@
+# SPEC: agent_graph_interpreter
+
+## 1. Purpose
+
+Receives pre-computed graph centrality metrics (PageRank, betweenness, degree,
+closeness, clustering) for a knowledge-graph domain and produces structured,
+actionable insights for the end user. The agent may look up additional entity
+details via the `get_entity_details` tool to ground its observations before
+writing its final answer. Called by the Analytics page after the user clicks
+"Interpret".
+
+## 2. Identity
+
+| Field | Value |
+|---|---|
+| `agent_name` | `agent_graph_interpreter` |
+| `module_path` | `src/agents/agent_graph_interpreter/` |
+| `model_endpoint` | domain-configured `llm_endpoint` (auto-discovered) |
+| `temperature` | `0.1` |
+| `mlflow_experiment` | `/Shared/ontobricks/agents/agent_graph_interpreter` |
+
+## 3. Tool surface
+
+| Tool name | Input schema | Output type | Purpose |
+|---|---|---|---|
+| `get_entity_details` | `{"uri": "string"}` | `string (JSON)` | Fetch attributes + direct relationships for a specific entity URI from the live triple-store |
+
+
+get_entity_details schema
+
+```json
+{
+ "type": "object",
+ "properties": {
+ "uri": {
+ "type": "string",
+ "description": "Full URI of the entity to inspect, e.g. https://example.com/Customer/C001"
+ }
+ },
+ "required": ["uri"]
+}
+```
+
+
+## 4. Success criteria
+
+1. **Hub entity identified** — Given a graph where one entity has PageRank 10×
+ higher than the median, the agent names it in "Notable Entities" and explains
+ why it is central.
+2. **Isolated nodes flagged** — When `zero_metrics: ["betweenness","closeness","clustering"]`
+ is set, the agent surfaces this in "Key Findings" and recommends data
+ enrichment.
+3. **Filtered analysis** — When `class_filter_applied: true` and `entity_type:
+ "Customer"`, the agent scopes all observations to Customer entities and does
+ not mention unrelated types.
+
+## 5. Eval dimensions
+
+| Dimension | Metric | Threshold | Weight | Judge |
+|---|---|---|---|---|
+| `section_completeness` | all 3 sections present in output | `1.00` | `0.30` | rule-based |
+| `groundedness` | notable entity labels match top-pagerank input | `0.85` | `0.30` | rule-based |
+| `tool_selection` | `get_entity_details` called for at least 1 top node when nodes > 0 | `0.70` | `0.20` | rule-based |
+| `latency_p95` | seconds | `<= 20.0` | `0.10` | wall-clock |
+| `cost_per_call` | USD | `<= 0.02` | `0.10` | MLflow usage record |
+
+**Aggregate threshold:** weighted sum ≥ 0.82 to pass G2.
+
+## 6. Failure modes
+
+| Symptom | Detection | Mitigation |
+|---|---|---|
+| Hallucinated entity name | `groundedness` < 0.85 | System prompt strictly requires only labels from the input payload |
+| Missing section | `section_completeness` = 0 | Stricter JSON-only system prompt; add failing case to `regression.jsonl` |
+| No tool call | `tool_selection` = 0 | Prompt instructs agent to call `get_entity_details` for the top node |
+| Latency spike | P95 > 20s | Lower `max_tokens`; reduce `MAX_ITERATIONS` |
+
+## 7. Eval dataset
+
+- **Baseline:** `tests/eval/datasets/agent_graph_interpreter/baseline.jsonl` — 20 examples
+- **Regression:** `tests/eval/datasets/agent_graph_interpreter/regression.jsonl` — populated as failures occur
+
+## 8. MLflow tracing
+
+`engine.py` is decorated with `@trace_agent(name="graph_interpreter")`.
+`call_serving_endpoint` carries `trace_name="graph_interpreter"`.
+Tool handler decorated with `@trace_tool`.
+
+## 9. Plan reference
+
+Implementation plan: `.planning/agents/agent_graph_interpreter/PLAN.md`
+
+## 10. Sign-off
+
+- [x] Author has filled every section.
+- [ ] Baseline eval run URI pasted into PR body.
+- [ ] Aggregate threshold ≥ 0.82.
+- [ ] Reviewer waiver (if applicable): _____
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index a781c638..29556c57 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -8,7 +8,7 @@ Thank you to everyone who has contributed to OntoBricks!
|------|--------|------|
| Benoit Cayla | [@benoitcayladbx](https://github.com/benoitcayladbx) | Creator & Lead Maintainer |
| Dermot Smyth | [@dermotsmyth-db](https://github.com/dermotsmyth-db) | Contributor |
-| hourdays | [@hourdays](https://github.com/hourdays) | Contributor |
+| Hugues Journeau | [@hourdays](https://github.com/hourdays) | Contributor |
## How to Contribute
diff --git a/README.md b/README.md
index acfdc5df..c4f2f99d 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,10 @@
-OntoBricks 0.5.1
+OntoBricks 0.6.0
- Digital Twin Builder for Databricks
+ Knowledge Graph Builder for Databricks
@@ -15,7 +15,7 @@
## Project Description
-OntoBricks is a web application that transforms Databricks tables into a materialized knowledge graph. It lets you design ontologies (OWL), map them to Unity Catalog tables via R2RML, materialize triples into a Delta-backed triple store and a Lakebase Postgres graph engine, reason over the graph (OWL 2 RL, SWRL, SHACL), and query it through an auto-generated GraphQL API. The entire pipeline — from metadata import to a queryable knowledge graph — can run in four clicks using LLM-powered automation.
+OntoBricks is a web application that transforms Databricks tables into a materialized graph viewer. It lets you design ontologies (OWL), map them to Unity Catalog tables via R2RML, materialize triples into a Delta-backed triple store and a Lakebase Postgres graph engine, reason over the graph (OWL 2 RL, SWRL, SHACL), and query it through an auto-generated GraphQL API. The entire pipeline — from metadata import to a queryable graph viewer — can run in four clicks using LLM-powered automation.
## Project Support
@@ -158,7 +158,7 @@ git push origin main --tags
| **1** | **Import Metadata** (Domain > Metadata) | Fetches table and column metadata from Unity Catalog |
| **2** | **Generate Ontology** (Ontology > Wizard) | LLM designs entities, relationships, and attributes from your metadata |
| **3** | **Auto-Map** (Mapping > Auto-Map) | LLM generates SQL mappings for every entity and relationship |
-| **4** | **Synchronize** (Digital Twin > Status) | Executes mappings and populates the triple store |
+| **4** | **Synchronize** (Knowledge Graph > Status) | Executes mappings and populates the triple store |
### Domain & registry (0.1.2 UX)
@@ -185,11 +185,11 @@ Engine-specific options are stored as global JSON (`graph_engine_config`). For L
> | Schema | When to run | Who runs it |
> |---|---|---|
> | Registry schema (e.g. `ontobricks_registry`) | After `Settings → Registry → Initialize` | `scripts/deploy.sh` automatically on every `dev-lakebase` deploy (coords: `LAKEBASE_PROJECT` / `LAKEBASE_BRANCH` / `LAKEBASE_REGISTRY_DATABASE` / `LAKEBASE_REGISTRY_SCHEMA`) |
-> | Graph schema (e.g. `ontobricks_graph`) | After first Digital Twin `Build` | The in-app "Create graph DB" flow, or a manual `bootstrap-lakebase-perms.sh` run |
+> | Graph schema (e.g. `ontobricks_graph`) | After first Knowledge Graph `Build` | The in-app "Create graph DB" flow, or a manual `bootstrap-lakebase-perms.sh` run |
>
> The deploy script is **registry-scoped** — it only grants on the registry schema. The graph DB is configured in-app (`Settings → Graph DB`) and may live in a **different** Lakebase project, so its grant is handled separately.
-> **Lakebase build performance.** When the active engine is Lakebase, the Digital Twin build streams warehouse rows in `fetchmany` batches (`SQLWarehouse.iter_rows`) and ingests them via `COPY FROM STDIN` into a per-batch temp table followed by `INSERT … ON CONFLICT DO NOTHING` (and the symmetrical `DELETE … USING` for incremental removes). The FastAPI process never holds the full graph or the full diff: snapshot CTAS and `EXCEPT` execution stay warehouse-side, the app pipes one batch at a time. There is no Volume archive thread — Postgres is the system of record for the graph.
+> **Lakebase build performance.** When the active engine is Lakebase, the Knowledge Graph build streams warehouse rows in `fetchmany` batches (`SQLWarehouse.iter_rows`) and ingests them via `COPY FROM STDIN` into a per-batch temp table followed by `INSERT … ON CONFLICT DO NOTHING` (and the symmetrical `DELETE … USING` for incremental removes). The FastAPI process never holds the full graph or the full diff: snapshot CTAS and `EXCEPT` execution stay warehouse-side, the app pipes one batch at a time. There is no Volume archive thread — Postgres is the system of record for the graph.
> **Lakebase managed-synced mode.** When `graph_engine_config.sync_mode = "managed_synced"`, the bulk R2RML data movement is moved entirely off the app: a Databricks Lakeflow snapshot pipeline keeps a Postgres synced table in lock-step with the R2RML view, and the FastAPI process only orchestrates (`SyncedTableManager.ensure` + `trigger_and_wait`). Reasoning + cohort writes stay on the direct PG path through a writable companion table; readers see both via a UNION view (back-compat name). PG layout per graph version: `g__v_sync` (Lakeflow), `g__v__app` (app), `g__v` (UNION view). See `docs/graphdb-integration.md §9` for the full architecture.
@@ -197,17 +197,17 @@ Engine-specific options are stored as global JSON (`graph_engine_config`). For L
1. **Design** an ontology visually using the OntoViz canvas, or import OWL/RDFS/industry standards (FIBO, CDISC, IOF, HL7 FHIR R4/R4B/R5)
2. **Map** ontology entities to Databricks tables with column-level precision
-3. **Build** the Digital Twin — materializes triples into the triple store (incremental by default)
-4. **Query** through the GraphQL playground or explore the interactive knowledge graph
+3. **Build** the Knowledge Graph — materializes triples into the triple store (incremental by default)
+4. **Query** through the GraphQL playground or explore the interactive graph viewer
5. **Reason** over the graph — run OWL 2 RL inference, SWRL rules, SHACL validation, and constraint checks
-### Knowledge Graph Features
+### Graph Viewer Features
- **Two-phase search** — preview matching entities in a flat list, then select specific ones to expand into the full graph with relationships and neighbors
- **Configurable search depth** — control the maximum traversal depth and entity cap for graph expansion
- **Right-click "Expand neighbours"** — enrich the current graph in place with N-hop neighbours of any selected node (depth follows the right-pane Depth slider, default 2); newly added entities are highlighted and the camera zooms to frame them, with a non-blocking spinner in the canvas top-right while the request runs
-- **Bridge navigation** — follow cross-domain bridges to automatically switch domains and focus on the target entity in the knowledge graph
-- **Data cluster detection** — detect communities in the knowledge graph using Louvain, Label Propagation, or Greedy Modularity algorithms; available client-side (Graphology) for the visible subgraph and server-side (NetworkX) for the full graph; cluster results can be visualized with color-by-cluster mode and collapsed into super-nodes
+- **Bridge navigation** — follow cross-domain bridges to automatically switch domains and focus on the target entity in the graph viewer
+- **Data cluster detection** — detect communities in the graph viewer using Louvain, Label Propagation, or Greedy Modularity algorithms; available client-side (Graphology) for the visible subgraph and server-side (NetworkX) for the full graph; cluster results can be visualized with color-by-cluster mode and collapsed into super-nodes
- **Cohort discovery** — group entities that travel together using rule-based linkage (shared resources via predicates) and compatibility constraints (same-value, value-equals, value-in, value-range); deterministic, explainable cohorts with live counters, why/why-not explainers, and idempotent materialisation as graph triples (`:inCohort`) or Unity Catalog Delta tables. See [`docs/cohort_discovery.md`](docs/cohort_discovery.md).
- **Data quality violation limits** — cap the number of violations displayed per rule (configurable via dropdown, default 10) for faster quality checks
- **Per-rule progress tracking** — SWRL inference and data quality checks report progress for each individual rule
@@ -227,7 +227,7 @@ The **Ontology Designer** view (**Ontology → Designer**) includes a floating A
### MCP Integration
-OntoBricks exposes the knowledge graph to LLM agents via the [Model Context Protocol](https://modelcontextprotocol.io/). Deploy the companion `mcp-ontobricks` app and connect from Cursor, Claude Desktop, or the Databricks Playground.
+OntoBricks exposes the graph viewer to LLM agents via the [Model Context Protocol](https://modelcontextprotocol.io/). Deploy the companion `mcp-ontobricks` app and connect from Cursor, Claude Desktop, or the Databricks Playground.
### Registry OBX Export / Import (UI)
@@ -238,7 +238,7 @@ No command line required — ideal for ad-hoc transfers and cross-tenant sharing
### Registry Import / Export (CLI)
-For automated promotion pipelines, use the
+For automated promotion pipelines use the
`scripts/registry_transfer.sh` command-line tool — export a curated subset
of domains/versions from a source registry into a `.zip`, then preview and
commit it into the target registry. See
diff --git a/changelogs/v0.5.2/benoitcayladbx_2026-06-22.log b/changelogs/v0.5.2/benoitcayladbx_2026-06-22.log
new file mode 100644
index 00000000..95ef4214
--- /dev/null
+++ b/changelogs/v0.5.2/benoitcayladbx_2026-06-22.log
@@ -0,0 +1,24 @@
+# MCP Server: retry on 502/503 (Databricks Apps cold-start)
+
+## Context
+When the MCP server calls the main OntoBricks app during a Databricks Apps
+cold-start or idle-sleep window, the platform proxy returns 502 before the
+app is ready. Previously `_get` and `_post` raised immediately on any 4xx/5xx,
+so a single cold-start would fail the entire MCP tool call.
+
+## Changes
+
+1. `src/mcp-server/server/app.py` — added `import asyncio` (top-level imports)
+2. `src/mcp-server/server/app.py` — added module-level constants `_RETRY_STATUSES = {502, 503}`
+ and `_RETRY_DELAYS = (5, 10, 20)` (seconds between attempts)
+3. `src/mcp-server/server/app.py` — `_get`: retry loop up to 3 times on 502/503,
+ sleeping 5 s → 10 s → 20 s; each retry logged at WARNING level; final attempt
+ still raises via `raise_for_status()`
+4. `src/mcp-server/server/app.py` — `_post`: same retry logic as `_get`
+
+## Modified files
+- `src/mcp-server/server/app.py`
+
+## Test results
+2283 passed, 15 skipped (units only — e2e skipped, Python 3.9 union-type syntax
+incompatibility in conftest.py unrelated to this change)
diff --git a/changelogs/v0.6.0/benoitcayladbx_2026-06-12.log b/changelogs/v0.6.0/benoitcayladbx_2026-06-12.log
new file mode 100644
index 00000000..87809216
--- /dev/null
+++ b/changelogs/v0.6.0/benoitcayladbx_2026-06-12.log
@@ -0,0 +1,695 @@
+# v0.6.0 — 2026-06-12 — benoitcayladbx
+
+## Collaborative Comments & Tasks
+
+### Context
+Implements the `releasereq/collaborative_comments_tasks_plan.md` roadmap
+item: contextual threaded commenting anchored to a DRAFT/IN-REVIEW domain
+(ontology classes/properties, mappings, graph nodes/edges, or the whole
+domain) plus the ability to turn a comment into a personalised task
+assigned to a teammate, surfaced in the cross-domain "My Tasks" worklist.
+Follows the existing `domain_review_events` playbook (idempotent DDL,
+lazy self-heal, SP grants, `(domain_id, version)` grain).
+
+### Changes
+1. `src/back/objects/registry/store/base.py` — new `DomainComment` /
+ `DomainTask` TypedDicts and seven abstract `RegistryStore` methods
+ (`insert_comment`, `list_comments`, `resolve_comment`, `insert_task`,
+ `list_tasks`, `list_tasks_for_assignee`, `update_task_status`).
+2. `src/back/objects/registry/store/lakebase/schema.sql` — canonical DDL
+ for `domain_comments` + `domain_tasks` (anchor/parent/resolved columns,
+ task status CHECK, FK to comments, indexes on
+ `(domain_id, version, anchor_type, anchor_ref)` and
+ `(lower(assignee), status)`).
+3. `src/back/objects/registry/store/lakebase/store.py` — Lakebase impl of
+ the seven methods, a lazy `_ensure_collab_tables()` self-heal (same
+ pattern as `_ensure_review_events_table`), `_collab_tables_ready` guard,
+ row→dict mappers, and the two new tables added to `_KNOWN_TABLES`.
+4. `scripts/bootstrap-lakebase-perms.sh` — Step 2b idempotent migration
+ for the two new tables (run as schema owner) so existing deployments
+ self-heal under `make bootstrap-lakebase`.
+5. `src/back/objects/registry/RegistryService.py` — thin passthroughs to
+ the new store methods.
+6. `src/back/objects/registry/CommentService.py` — NEW stateless service:
+ anchor validation, DRAFT/IN-REVIEW write gate (PUBLISHED is read-only),
+ per-action role rules (member reads/comments; author/editor/admin
+ resolves; assignee/creator/editor/admin updates tasks), and a
+ best-effort `commented` audit row on task create/complete to keep the
+ Validation timeline unified.
+7. `src/api/routers/internal/comments.py` — NEW `/comments` router
+ (list/add/resolve comments, list/create/advance tasks); registered in
+ `src/api/routers/internal/__init__.py`.
+8. `src/back/objects/registry/ReviewService.py` — `my_tasks` now also
+ returns `assigned_tasks` (open/in-progress tasks for the caller) via a
+ best-effort `_assigned_tasks` helper; backward compatible.
+9. `src/front/static/global/js/comments-panel.js` — NEW reusable
+ right-side offcanvas (`window.OntoComments.openThread`) rendering the
+ anchored thread: replies, resolve/reopen, and create-task-from-comment
+ with an assignee picker sourced from the domain access roster.
+10. `src/front/templates/base.html` + `.../css/review-modals.css` — load
+ and style the new panel globally.
+11. `src/front/static/registry/js/registry-my-tasks.js` +
+ `.../home/js/home-tasks.js` — render an "Assigned to me" section with
+ inline status actions (Start / Done) alongside the review worklist.
+12. `src/front/static/domain/js/domain-review.js` — "Discussion" button on
+ the Validation banner opens the domain-level thread.
+13. `docs/architecture.md` — documented the three audit/collab tables in
+ the Lakebase registry schema table.
+
+### Modified / added files
+- src/back/objects/registry/store/base.py
+- src/back/objects/registry/store/lakebase/schema.sql
+- src/back/objects/registry/store/lakebase/store.py
+- scripts/bootstrap-lakebase-perms.sh
+- src/back/objects/registry/RegistryService.py
+- src/back/objects/registry/CommentService.py (new)
+- src/api/routers/internal/comments.py (new)
+- src/api/routers/internal/__init__.py
+- src/back/objects/registry/ReviewService.py
+- src/front/static/global/js/comments-panel.js (new)
+- src/front/templates/base.html
+- src/front/static/global/css/review-modals.css
+- src/front/static/registry/js/registry-my-tasks.js
+- src/front/static/home/js/home-tasks.js
+- src/front/static/domain/js/domain-review.js
+- docs/architecture.md
+- tests/units/registry/test_registry_store.py (extended)
+- tests/units/registry/test_comment_service.py (new)
+- tests/units/api/test_comment_endpoints.py (new)
+
+### Decisions
+- Comments/tasks are writable on DRAFT **and** IN-REVIEW (active
+ collaboration phases); PUBLISHED is read-only (reopen to continue).
+- Fine-grained per-surface anchoring (ontology/mapping/graph selection)
+ is reachable through the documented `OntoComments.openThread(...)` seam;
+ the Validation page wires the domain-level anchor as the first adopter.
+
+### Tests
+- `tests/units` (full unit tree): 2392 passed.
+- New: `test_comment_service.py` (22), `test_comment_endpoints.py` (9).
+- Extended `test_registry_store.py`: in-memory contract for comments/tasks
+ **plus** a `TestLakebaseCollab` class driving the real Lakebase SQL code
+ paths (row mappers, lazy `_ensure_collab_tables` short-circuit,
+ rowcount-based not-found handling) via the scripted cursor.
+- Extended `test_review_service.py`: `my_tasks` now asserts the merged
+ `assigned_tasks` (open/in-progress filter, backend-error resilience,
+ empty-email skip).
+- Full repo run shows 53 failures isolated to `tests/mcp/integration`
+ and the async API tests it pollutes when the entire ~2800-test suite
+ runs in one process — pre-existing event-loop ordering issue; every one
+ of those tests passes in isolation and per-directory. No regressions
+ from this change.
+
+## Per-surface discussion anchoring (ontology / mapping / graph)
+
+### Context
+Follow-up to the Collaborative Comments & Tasks landing: wire the
+fine-grained selection hooks so users can open a thread anchored to an
+individual entity instead of only the whole domain. Completes the
+`OntoComments.openThread(...)` seam noted as future work above.
+
+### Changes
+1. `src/front/static/global/js/comments-panel.js` — added
+ `OntoComments.openForSelection(anchorType, anchorRef, anchorLabel)`:
+ resolves the loaded domain folder + version once (cached) via
+ `/domain/version-status`, gates on a registry-backed domain, then opens
+ the anchored thread. Editor surfaces no longer need to carry
+ folder/version themselves.
+2. `src/front/static/query/js/query-sigmagraph.js` — "Discuss this entity"
+ / "Discuss this relationship" buttons appended to the node and edge
+ detail panels; new `SigmaGraph.discussNode` / `SigmaGraph.discussEdge`
+ public methods anchor to `graph_node` (subject URI) and `graph_edge`
+ (`source|predicate|target`).
+3. `src/front/static/ontology/js/ontology-shared-panels.js` — reusable
+ `injectPanelDiscussButton(...)` adds a header Discuss button on the
+ entity/relationship edit + view panels, anchoring to `ontology_class`
+ / `ontology_property` by URI.
+4. `src/front/static/mapping/js/mapping-shared.js` — reusable
+ `injectMappingDiscussButton(...)` adds a Discuss button to a mapping
+ modal header (anchor_type `mapping`, anchor ref = targeted class /
+ property URI); wired from `mapping-entity-modal.js` and
+ `mapping-relationship-modal.js`.
+
+### Modified files
+- src/front/static/global/js/comments-panel.js
+- src/front/static/query/js/query-sigmagraph.js
+- src/front/static/ontology/js/ontology-shared-panels.js
+- src/front/static/mapping/js/mapping-shared.js
+- src/front/static/mapping/js/mapping-entity-modal.js
+- src/front/static/mapping/js/mapping-relationship-modal.js
+
+### Decisions
+- Anchoring auto-resolves domain context from the existing
+ `/domain/version-status` endpoint rather than threading it through every
+ surface — one cached fetch, consistent gating, matches `domain-review.js`.
+- Anchor refs use the canonical URI (or `source|predicate|target` for
+ edges), matching the `anchor_type` set already enforced by
+ `CommentService` and the `domain_comments` CHECK constraint — no backend
+ change required.
+
+### Tests
+- Frontend-only wiring; no Python surface changed. Backend anchor handling
+ already covered by `test_comment_service.py` / `test_comment_endpoints.py`
+ (all anchor types). Unit suite re-run: no regressions.
+
+### Fixup
+- `ontology-shared-panels.js` — designer panel Discuss button now uses
+ `ms-auto me-1` so it groups top-right next to the close button instead of
+ landing mid-header (the header is `justify-content: space-between`).
+
+## Ontology designer diagram-level discussion + tag picker
+
+### Context
+The Ontology Designer toolbar needed a discussion entry point for the
+*whole* diagram (not a specific entity), with an optional way to re-tag the
+thread to a specific class/relationship — requested next to the icon
+generator button.
+
+### Changes
+1. `src/front/templates/partials/ontology/_ontology_map.html` — added a
+ "Discussion" button (`#mapDiscuss`) to the designer toolbar, next to the
+ icon generator (`#mapAutoAssignIcons`).
+2. `src/front/static/ontology/js/ontology-map.js` — `openOntologyDiscussion()`
+ opens the whole-diagram thread (anchor `domain` / ref `ontology`, kept
+ distinct from the Validation domain thread which uses ref `''`) and
+ passes a `taggable` list built from `OntologyState.config`
+ classes/properties.
+3. `src/front/static/global/js/comments-panel.js` — the thread panel now
+ accepts an optional `taggable` list and renders a header ``
+ picker. Entry 0 is the base scope; choosing a class/relationship re-tags
+ the open discussion (re-anchors + reloads) without closing the panel.
+ `openForSelection(...)` gained a 4th `taggable` arg.
+
+### Modified files
+- src/front/templates/partials/ontology/_ontology_map.html
+- src/front/static/ontology/js/ontology-map.js
+- src/front/static/global/js/comments-panel.js
+
+### Decisions
+- Whole-ontology discussion uses `domain` + ref `ontology` so it is a
+ distinct thread from the Validation page's domain thread, no new
+ `anchor_type` (no backend / CHECK-constraint change).
+- The tag picker reuses the generic anchor model: each option is just an
+ `{anchorType, anchorRef, anchorLabel}` the panel switches to — so the
+ same mechanism is available to any future surface.
+
+### Tests
+- Frontend-only; backend anchor handling unchanged and already covered.
+
+## Comment tags replace per-entity Discuss buttons (designer)
+
+### Context
+Reworked the designer collaboration UX: instead of a Discuss button on every
+right-pane entity/relationship panel (and a header scope switcher), the
+single diagram-level discussion now lets each *comment* carry one or more
+entity/relationship tags, picked from the compose box.
+
+### Changes
+1. `src/front/static/ontology/js/ontology-shared-panels.js` — removed
+ `injectPanelDiscussButton(...)` and its four call sites (entity/property
+ edit + view panels). The right pane no longer carries a Discuss button.
+2. `src/front/static/global/js/comments-panel.js` — replaced the header
+ anchor-switch dropdown with a per-comment **tag picker**:
+ - `taggable` is now a vocabulary of `{type, ref, label}` entries.
+ - The main compose box and each reply box get a tag ``
+ (Entities / Relationships optgroups) that adds removable chips.
+ - Tags are embedded in the comment body via a `[[onto-tags]]` JSON
+ marker (`encodeBody`/`parseBody`) — no schema change — and rendered as
+ badges under each comment.
+3. `src/front/static/global/css/review-modals.css` — styles for the tag
+ chips (`.oc-tag-chip`, `.oc-bubble-tags`, `.oc-tagbar`).
+4. `src/front/static/ontology/js/ontology-map.js` — designer Discussion
+ button still anchors to the whole diagram (domain/'ontology') and now
+ passes the class/relationship vocabulary as `taggable`.
+
+### Decisions
+- Tags live inside the comment body behind a marker so the feature is fully
+ frontend and reversible; if first-class tagging is wanted later, promote
+ the marker payload into a `domain_comments.tags` column.
+- Graph and mapping surfaces keep their own Discuss entry points (separate
+ pages, different context); only the ontology designer right-pane buttons
+ were removed per request.
+
+### Tests
+- Frontend-only; no Python surface touched. Unit suite unaffected.
+
+### Fixup
+- `ontology-map.js` — tag picker never appeared because the vocabulary was
+ read via `window.OntologyState`, but `OntologyState` is a `const` (lexical
+ global, not a `window` property), so the guard short-circuited to an empty
+ list. Now read via `typeof OntologyState !== 'undefined' && ...`.
+
+## Discuss button on all ontology sections
+
+### Context
+The discussion entry point should be reachable from every ontology section,
+not just the Model/Designer.
+
+### Changes
+1. `src/front/static/ontology/js/ontology-init.js` — `injectOntologyDiscussButtons()`
+ adds a chat button to every `.section-header` in the ontology page on load,
+ wired to `openOntologyDiscussion()`. Skips the Import section (per request)
+ and the Model/Designer section (which already has a toolbar button).
+ Placement is layout-aware: appended into an existing right-hand actions
+ group when present, otherwise pushed to the far right with `ms-auto`.
+
+### Modified files
+- src/front/static/ontology/js/ontology-init.js
+
+### Tests
+- Frontend-only; no Python surface touched.
+
+## Task assignee picker lists domain permission holders
+
+### Context
+"Create task" showed an empty "Assign to" list. The panel sourced names
+from `/review/{folder}/{version}/team`, which intersects domain permission
+entries with the Databricks App ACL and can come back empty. The picker
+should simply list everyone who has a role on the domain.
+
+### Changes
+1. `src/back/objects/registry/CommentService.py` — new `list_assignees(...)`:
+ reads the domain permission entries (`permission_service.list_domain_entries`)
+ and returns principals with an assignable role (viewer/editor/builder),
+ most-privileged first. Any member may read it; backend errors degrade to
+ an empty list rather than failing the panel.
+2. `src/api/routers/internal/comments.py` — new
+ `GET /comments/{folder}/{version}/assignees` (per-domain) delegating to it.
+3. `src/front/static/global/js/comments-panel.js` — `loadMembers()` now calls
+ the new assignees endpoint instead of the review team endpoint.
+
+### Modified / added files
+- src/back/objects/registry/CommentService.py
+- src/api/routers/internal/comments.py
+- src/front/static/global/js/comments-panel.js
+- tests/units/registry/test_comment_service.py (assignees cases)
+- tests/units/api/test_comment_endpoints.py (assignees endpoint)
+
+### Decisions
+- Used the domain permission entries (not the App-ACL-intersected Team view)
+ as the assignee source — that is literally "people with permissions on the
+ domain". The Validation Team display is left unchanged.
+
+### Tests
+- `test_comment_service.py` + `test_comment_endpoints.py`: 35 passed
+ (incl. assignees sorting, member gate, backend-error resilience, endpoint
+ delegation).
+
+### Fixup
+- `ontology-init.js` — the injected discuss button used `ms-auto`, which
+ pushed an existing trailing action (e.g. the Wizard's "Generate Ontology"
+ button) to the left. Now the title gets `me-auto` and the discuss button
+ slots just after it, so existing action buttons stay top-right.
+- `ontology-init.js` — re-assert the discuss button on every section change
+ (idempotent, with short delayed passes). Some sections re-render their
+ header on `init()` after the one-time load pass, which dropped the button
+ on Groups, Business Views, Entities, Relationships, Data Quality, Cohorts
+ and Pitfalls. The button is now reliably present on all sections.
+- `ontology-init.js` — unified the discuss-button placement: always insert
+ it right after the title (title gets `me-auto`), ahead of any existing
+ actions group, instead of appending inside that group. The Business Views
+ header's action bar is very crowded and was pushing the appended button
+ into overflow; it's now always visible.
+- `_ontology_design.html` — added the discuss button statically to the
+ Business Views header (class `onto-discuss-btn`, so the JS injector skips
+ it). The OntoViz designer section was the one place runtime injection
+ wasn't surfacing the button reliably; the static button guarantees it.
+- `_ontology_entities.html`, `_ontology_relationships.html` — added the
+ discuss button statically to the Classes and Relationships headers too
+ (same `onto-discuss-btn` opt-out from the JS injector).
+- `_ontology_dataquality.html`, `_ontology_business_rules.html`,
+ `_ontology_cohorts.html`, `_pitfalls.html` — added the discuss button
+ statically to the Data Quality, Business Rules, Cohorts and Pitfalls
+ headers (same `onto-discuss-btn` opt-out).
+
+## Discuss capability on the Mapping pages
+
+### Context
+Extend the contextual discussion (with the entity/relationship tag picker)
+to the Mapping workspace, matching the ontology sections.
+
+### Changes
+1. `src/front/static/mapping/js/mapping-shared.js` — `openMappingDiscussion()`
+ opens the thread anchored to the whole mapping layer (anchor_type
+ `mapping`, ref `mapping`) and passes the loaded ontology's
+ classes/relationships as the comment tag vocabulary.
+2. Added the discuss button statically to every Mapping section header
+ except Import: `_mapping_information.html`, `_mapping_design.html`,
+ `_mapping_manual.html`, `_mapping_autoassign.html`,
+ `_mapping_diagnostics.html`, `_mapping_r2rml.html`,
+ `_mapping_sparksql.html` (each `onclick="openMappingDiscussion()"`).
+
+### Decisions
+- Whole-mapping discussion uses anchor `mapping`/`mapping` (distinct from
+ the ontology designer's `domain`/`ontology` thread); per-mapping modal
+ Discuss buttons remain for entity/relationship-specific threads.
+- `comments-panel.js` is already loaded globally via `base.html`, so the
+ mapping page reuses the same thread panel + tag picker with no extra
+ wiring.
+
+### Tests
+- Frontend-only; no Python surface changed. Backend `mapping` anchor type
+ already validated/covered.
+
+## Discuss capability on the Digital Twin pages
+
+### Context
+Extend the contextual discussion (with the entity/relationship tag picker)
+to the Digital Twin workspace, matching the ontology and mapping sections.
+
+### Changes
+1. `src/front/static/query/js/query.js` — `openTwinDiscussion()` opens the
+ thread anchored to the whole twin (anchor_type `domain`, ref
+ `digital-twin`). It lazily fetches `/ontology/load` once, caches the
+ result, and builds the comment tag vocabulary from the ontology's
+ classes/relationships.
+2. Added the discuss button to every Digital Twin section header:
+ `_query_insights.html`, `_query_dataquality.html`,
+ `_query_reasoning.html`, `_query_cohorts.html`,
+ `_query_sigmagraph.html`, `_query_graphql.html`, `_query_chat.html`
+ (each `onclick="openTwinDiscussion()"`).
+
+### Decisions
+- Whole-twin discussion uses anchor `domain`/`digital-twin` (distinct from
+ the ontology designer's `domain`/`ontology` and the mapping layer's
+ `mapping`/`mapping` threads); the SigmaGraph per-node/edge Discuss
+ buttons remain for graph-element-specific threads.
+- Tag vocabulary is fetched lazily on first open (the query page has no
+ persistent clean ontology config global) and cached in `_twinTaggable`.
+- `comments-panel.js` is already loaded globally via `base.html`, so the
+ Digital Twin page reuses the same thread panel + tag picker.
+
+### Tests
+- Frontend-only; no Python surface changed.
+
+## Digital Twin discuss button — position fix
+
+### Context
+The discuss button rendered to the left of the existing header actions; it
+should sit to the right of them. Also confirmed the "openTwinDiscussion is
+not defined" runtime error was a stale browser cache (`asset_version` is
+`APP_VERSION.`, only bumped on server restart) — the function
+is correctly defined globally in `query.js`.
+
+### Changes
+1. Moved the discuss button to the rightmost position of every Digital Twin
+ section header action group: `_query_insights.html`,
+ `_query_dataquality.html`, `_query_reasoning.html`, `_query_cohorts.html`,
+ `_query_sigmagraph.html`, `_query_graphql.html`, `_query_chat.html`.
+
+### Tests
+- Frontend-only; `node --check query.js` passes.
+
+## Ontology + Mapping discuss button — position fix
+
+### Context
+Apply the same right-alignment to the Ontology and Mapping pages so the
+discuss button consistently sits to the right of the other header actions.
+
+### Changes
+1. Mapping partials — moved the discuss button to the rightmost slot:
+ `_mapping_information.html`, `_mapping_design.html`, `_mapping_manual.html`,
+ `_mapping_autoassign.html`, `_mapping_diagnostics.html`,
+ `_mapping_r2rml.html`, `_mapping_sparksql.html`.
+2. Ontology partials — same reposition: `_ontology_design.html`,
+ `_ontology_entities.html`, `_ontology_relationships.html`,
+ `_ontology_dataquality.html`, `_ontology_business_rules.html`,
+ `_ontology_cohorts.html`, `_pitfalls.html`.
+3. `src/front/static/ontology/js/ontology-init.js` —
+ `injectOntologyDiscussButtons()` now appends the button as the last
+ (rightmost) header element (after any existing actions group) instead of
+ slotting it right after the title. Covers JS-injected sections (Groups,
+ Wizard, etc.).
+4. `_ontology_map.html` `#mapDiscuss` was already rightmost — unchanged.
+
+### Tests
+- Frontend-only; `node --check ontology-init.js` passes; one
+ `onto-discuss-btn` per partial verified (no duplicates).
+
+## Mapping Import — add missing discuss button
+
+### Context
+The Import R2RML mapping section was the only mapping page without a discuss
+button.
+
+### Changes
+1. `_mapping_import.html` — added the rightmost discuss button
+ (`openMappingDiscussion()`) to the section header.
+
+### Tests
+- Frontend-only.
+
+## Ontology Import — add missing discuss button
+
+### Context
+The Ontology Import section was previously excluded; user now wants it
+covered too.
+
+### Changes
+1. `_ontology_import.html` — added the rightmost discuss button
+ (`openOntologyDiscussion()`) statically. The `onto-discuss-btn` class
+ means the JS injector skips it (no duplicate), independent of its
+ existing `#import-section` exclusion.
+
+### Tests
+- Frontend-only.
+
+## Ontology Generate (Wizard) — static rightmost discuss button
+
+### Context
+The Generate Ontology section relied on the JS injector for its discuss
+button; make it static and rightmost (after "Generate Ontology") for
+reliability, matching the other sections.
+
+### Changes
+1. `_ontology_wizard.html` — added a static discuss button after the
+ "Generate Ontology" action (title gets `me-auto`). The `onto-discuss-btn`
+ class makes the JS injector skip it (no duplicate).
+
+### Tests
+- Frontend-only.
+
+## Discussion + tasks code — centralized (DRY)
+
+### Context
+Ensure the discussion pane and its task code are a single shared module
+across the whole app, with no per-surface duplication.
+
+### Audit findings
+- The discussion pane (comments, replies, **task creation**, resolve, tags,
+ assignee picker) lives entirely in the global
+ `src/front/static/global/js/comments-panel.js`, loaded once via
+ `base.html` — already common to every page. Surfaces only call the
+ `window.OntoComments` API (`openThread` / `openForSelection`).
+- `task-tracker.js` is a separate concept (async long-running jobs), not the
+ collaborative tasks of the discussion pane — left as-is.
+- The "My Tasks" worklist (`registry-my-tasks.js`, `home-tasks.js`) is a
+ read-only display feature, already modular — left as-is.
+- Only real duplication: the per-surface "build a comment tag vocabulary
+ from ontology classes/properties" logic was copy-pasted in three files.
+
+### Changes
+1. `comments-panel.js` — added `OntoComments.taggableFromOntology(config)`
+ as the single shared builder for the `{type, ref, label}[]` tag
+ vocabulary; exposed on the global API.
+2. `ontology-map.js` `openOntologyDiscussion()`, `mapping-shared.js`
+ `openMappingDiscussion()`, and `query.js` `openTwinDiscussion()` now call
+ `OntoComments.taggableFromOntology(...)` instead of each rebuilding the
+ list (removed ~45 duplicated lines).
+
+### Tests
+- Frontend-only; `node --check` passes on all four files; no linter errors.
+
+## Domain → Collaboration page (My Tasks + Discussions timeline)
+
+### Context
+A single place in the Domain area that concentrates every collaborative
+signal: the user's tasks first, then all discussions as an activity timeline.
+
+### Changes
+1. `src/front/config/menu_config.json` — new "Collaboration" group in the
+ Domain menu, placed before "Management", with two items: "My Tasks"
+ (`mytasks`) and "Discussions" (`discussions`).
+2. `domain.html` — added `#mytasks-section` and `#discussions-section`
+ (partials below), plus the new CSS/JS includes.
+3. `partials/domain/_domain_mytasks.html` — My Tasks section shell.
+4. `partials/domain/_domain_discussions.html` — Discussions section shell
+ with a "Show resolved" switch + refresh.
+5. `src/front/static/domain/js/domain-collaboration.js` — new module:
+ - My Tasks: reuses `GET /review/my-tasks` to render "Assigned to me"
+ tasks (Start/Done via `/comments/.../tasks/{id}/status`) and the
+ review worklist (Review → loads the domain and deep-links to
+ `?section=review`).
+ - Discussions: pulls every comment for the domain via
+ `GET /comments/{folder}/{version}` (no anchor filter), merges roots +
+ replies, sorts newest-first, groups by day, and renders an activity
+ timeline. Each entry shows author, anchor badge (Class/Property/
+ Mapping/Node/Edge/Domain), body, and tag chips; clicking re-opens the
+ thread through the shared `OntoComments.openThread(...)`.
+ - Hooks the global `sidebarSectionChanged` event (no coupling to
+ `domain.js`).
+6. `src/front/static/domain/css/domain-collaboration.css` — timeline styling.
+7. `comments-panel.js` — exposed `OntoComments.parseBody` and
+ `OntoComments.anchorLabel(type)` so the timeline reuses the panel's body
+ parsing + anchor labels (no duplication).
+
+### Decisions
+- No backend changes: the page composes existing endpoints
+ (`/review/my-tasks`, `/comments/{folder}/{version}`,
+ `/domain/version-status`, task status).
+- Domain context (folder/version) resolved from `/domain/version-status`,
+ matching the panel's own resolution.
+
+### Tests
+- Frontend-only; `node --check` passes; `menu_config.json` validates; no
+ linter errors.
+
+## Create task — "Assign to me" shortcut
+
+### Context
+Make it one click to assign a new task to oneself from the discussion pane.
+
+### Changes
+1. `comments-panel.js` — the "New task from this comment" form now has an
+ "Assign to me" link and marks the current user "(me)" in the assignee
+ dropdown. Current user is resolved once via `GET /domain/current-user`
+ (cached, prefetched on panel open). `assignToMe()` selects the user,
+ adding an option for them if they're not in the roster.
+
+### Decisions
+- Shared in the global panel, so the shortcut appears on every surface
+ (ontology / mapping / twin / domain) automatically.
+- Backend `create_task` only requires a non-empty assignee, so self-assign
+ needs no API change.
+
+### Tests
+- Frontend-only; `node --check` passes; no linter errors.
+
+## Test suite — coverage for collab features + `make test` isolation fix
+
+### Context
+Run the full suite and backfill tests for the new comments/tasks/
+collaboration work. Running the whole tree in one process surfaced a
+pre-existing test-isolation bug: the Playwright **e2e** suite (sync API)
+keeps a session-scoped event loop alive, so every `pytest-asyncio` test
+that runs after it (MCP schema tests, comment/review/version-status
+endpoint tests) failed with `RuntimeError: Cannot run the event loop
+while another loop is running`. This only bites locally — CI runs the PR
+gate with `--ignore=tests/e2e` and runs e2e as a separate nightly job;
+bare `make test` (`pytest`) ran everything together and so went red.
+
+### Changes
+1. `Makefile` — `make test` / `make test-cov` now mirror the CI G1 gate
+ (`--ignore=tests/e2e -m "not e2e and not property and not eval and not
+ external"`) so the Playwright suite no longer poisons the shared
+ asyncio loop. Added a dedicated `make test-e2e` target (runs the
+ Playwright suite by path, in its own process) plus help text.
+2. `tests/units/api/test_domain_current_user_endpoint.py` — new unit tests
+ for `GET /domain/current-user` (the endpoint behind the discussion
+ pane's "Assign to me"): app-mode proxy headers (username preferred,
+ email fallback) and local/PAT mode (workspace client lookup, and the
+ no-client empty fallback). 5 tests.
+3. `tests/units/api/test_ui_rendering.py` — extended `TestDomainPage` for
+ the new Domain → Collaboration area: sidebar links + section divs for
+ `mytasks` / `discussions`, and a `test_collaboration_section_assets_loaded`
+ asserting `domain-collaboration.js` and the timeline/my-tasks containers
+ render.
+
+### Modified files
+- `Makefile`
+- `tests/units/api/test_domain_current_user_endpoint.py` (new)
+- `tests/units/api/test_ui_rendering.py`
+
+### Decisions
+- The comments/tasks service (`tests/units/registry/test_comment_service.py`,
+ 25 tests) and router (`tests/units/api/test_comment_endpoints.py`, 10
+ tests incl. `/assignees`) were already well covered, so new tests target
+ only the genuinely uncovered surfaces (current-user endpoint, the new
+ Domain collaboration UI). The timeline/"Assign to me" JS itself has no
+ Python-side harness (no JS test runner in this repo).
+- `make test-e2e` selects e2e by path rather than `-m e2e` because the e2e
+ tests are not tagged with the `e2e` marker (selecting by marker collects
+ zero); this matches the usage documented in `tests/e2e/conftest.py`.
+
+### Tests
+- PR gate (`make test`): **2627 passed, 17 skipped, 12 deselected** in
+ ~24s (previously 54 failures / 4m17s when e2e ran in-process).
+- New/changed test files lint clean (`flake8 --max-line-length=100`).
+
+## Discussions timeline — multi-select tag filter
+
+### Context
+The Domain → Collaboration → Discussions timeline already renders the
+per-comment tags (entity/relationship chips). Let users narrow the
+timeline to one or more of those tags.
+
+### Changes
+1. `src/front/templates/partials/domain/_domain_discussions.html` — added a
+ "Tags" dropdown (Bootstrap, `data-bs-auto-close="outside"` so multiple
+ boxes can be ticked) with a live count badge, beside the existing
+ "Show resolved" switch. Hidden until the loaded comments actually carry
+ tags.
+2. `src/front/static/domain/js/domain-collaboration.js`
+ - `selectedTags` set tracks the active filter keys.
+ - `buildTagFilter()` computes the distinct tag vocabulary from the loaded
+ comments (via the shared `OntoComments.parseBody`), renders the
+ checkbox menu, wires per-tag toggles and a "Clear" action, and
+ self-prunes selections whose tag no longer appears. Called after
+ comments load / on refresh.
+ - `renderDiscussions()` now filters by tag (OR semantics: keep comments
+ carrying any selected tag) and shows a dedicated "No discussions match
+ the selected tags" empty state when a filter hides everything.
+ - `parsedTags()` / `tagKey()` helpers; `updateTagCount()` drives the
+ badge.
+
+### Decisions
+- OR semantics across selected tags (match any) — the common expectation
+ for a faceted filter; AND would make multi-select almost always empty.
+- Tags keyed by their `ref` (falling back to `label`) so the same entity
+ tagged on different comments collapses to one filter row.
+- Reuses the panel's `OntoComments.parseBody` rather than re-implementing
+ tag extraction — no duplication.
+
+### Tests
+- `node --check` passes; `TestDomainPage` UI-render tests still green
+ (19 passed). Frontend-only; the asset version is fixed at server
+ startup, so restart the dev server to pick up the new JS.
+
+## Discussions — registry upgrade SQL (0.5 -> 0.6)
+
+### Context
+The collaborative comments/tasks tables (`domain_comments`, `domain_tasks`)
+are created lazily by the app (`_ensure_collab_tables`) and by
+`make bootstrap-lakebase` (schema owner), but there was no standalone,
+auditable `psql` migration like `upgrade_lakebase_0.4_To_0.5.sql`. Provide
+one so an existing (pre-Discussions) Lakebase registry can be upgraded
+out-of-band by a DBA.
+
+### Changes
+1. `scripts/upgrade_lakebase_0.5_To_0.6.sql` — new idempotent one-shot
+ migration that adds `domain_comments` + `domain_tasks` (+ the three
+ indexes), mirroring the canonical
+ `src/back/objects/registry/store/lakebase/schema.sql`. Follows the same
+ psql conventions as the 0.4->0.5 script (`-v reg_schema=...`, default
+ `ontobricks_registry`, `\set ON_ERROR_STOP on`, single `BEGIN/COMMIT`).
+ - Also backfills the `anchor_type` and task `status` CHECK constraints on
+ registries whose tables were first created by the app's lazy self-heal
+ path (which omits those constraints), via guarded `pg_constraint`
+ lookups — so the schema ends up fully constrained either way.
+ - Non-destructive: no data touched, no columns dropped.
+
+### Modified files
+- `scripts/upgrade_lakebase_0.5_To_0.6.sql` (new)
+
+### Decisions
+- The explicit script carries the CHECK constraints (anchor_type, status)
+ that the runtime lazy path leaves off, making it the authoritative,
+ fully-constrained migration — and it self-heals already-lazily-created
+ tables by adding the missing constraints.
+- Scoped to the Discussions feature (comments + tasks); the review/build
+ tables belong to the 0.4->0.5 era and already have their own provisioning.
+
+### Tests
+- `psql` available locally; script mirrors the proven 0.4->0.5 structure
+ (2 CREATE TABLE + 3 CREATE INDEX + 2 guarded ADD CONSTRAINT). Idempotent;
+ safe to re-run.
diff --git a/changelogs/v0.6.0/benoitcayladbx_2026-06-16.log b/changelogs/v0.6.0/benoitcayladbx_2026-06-16.log
new file mode 100644
index 00000000..f71cff5b
--- /dev/null
+++ b/changelogs/v0.6.0/benoitcayladbx_2026-06-16.log
@@ -0,0 +1,11 @@
+## Add CONTRIBUTORS.md
+
+**Context:** Added a contributors file to the repository root for GitHub visibility, listing all human contributors with their GitHub handles and roles.
+
+**Changes:**
+1. `CONTRIBUTORS.md` (new) — created contributors file with a table listing Benoit Cayla and Dermot Smyth with links to their GitHub profiles, plus a pointer to CONTRIBUTING.md
+
+**Modified files:**
+- `CONTRIBUTORS.md` (created)
+
+**Test result:** N/A — documentation-only change.
diff --git a/changelogs/v0.6.0/benoitcayladbx_2026-06-17.log b/changelogs/v0.6.0/benoitcayladbx_2026-06-17.log
new file mode 100644
index 00000000..40648c70
--- /dev/null
+++ b/changelogs/v0.6.0/benoitcayladbx_2026-06-17.log
@@ -0,0 +1,29 @@
+## Fix: last_build reset when switching domains (Issue reported by Chang Shi Lim)
+
+**Context:** After a manual UI build, `last_build` was only written to the server
+session — not to the Lakebase registry store (`domain_versions.last_build`). When
+the user switched to another domain and switched back, `load_domain_from_uc` reloaded
+the domain from the registry, found `last_build` empty, and reset the "Last Built"
+indicator. The Validation page still showed "Digital Twin built" because it checks
+live Lakebase table existence, while the "Submit for Review" button checks
+`last_build`, leaving users unable to submit for review after switching domains.
+The only workaround was triggering a scheduled run (which already persisted
+`last_build` to the store via `_persist_domain_metadata`).
+
+**Changes:**
+1. `src/back/objects/registry/store/base.py` — added `stamp_last_build(folder,
+ version, ts)` with a safe default fallback (read+write) so any store
+ implementation works without requiring an override.
+2. `src/back/objects/registry/store/lakebase/store.py` — overridden
+ `stamp_last_build` with a targeted single-column `UPDATE` on
+ `domain_versions.last_build` to avoid re-writing all JSONB blobs.
+3. `src/back/objects/digitaltwin/_build_pipeline.py` — in `_record_build_run`,
+ for `status == "success"` call `svc._store.stamp_last_build(...)` so every
+ successful UI/API build durably persists `last_build` to the registry.
+
+**Modified files:**
+- `src/back/objects/registry/store/base.py`
+- `src/back/objects/registry/store/lakebase/store.py`
+- `src/back/objects/digitaltwin/_build_pipeline.py`
+
+**Test result:** 2291 passed, 15 skipped (pre-existing psycopg skip unrelated to this change).
diff --git a/changelogs/v0.6.0/benoitcayladbx_2026-06-18.log b/changelogs/v0.6.0/benoitcayladbx_2026-06-18.log
new file mode 100644
index 00000000..5528f99a
--- /dev/null
+++ b/changelogs/v0.6.0/benoitcayladbx_2026-06-18.log
@@ -0,0 +1,434 @@
+## Feature: AI Agent as a task assignee (auto-routing)
+
+**Context:** Collaborative tasks (created from the comments panel) could only be
+assigned to a human domain member. This feature adds a virtual "AI Agent"
+assignee. When a task is assigned to it, an asynchronous router agent inspects
+the task title/description, picks the best-suited specialized agent from a
+static registry, runs it against the task's domain, and records the outcome back
+on the task (status transitions + audit comments). Only agents that already have
+background-task + domain-context wiring are dispatchable: OWL generator, business
+rules generator, icon assigner, and the auto SQL mapper. Interactive chat agents
+(dtwin chat, ontology assistant, cohort) are intentionally excluded.
+
+**Changes:**
+1. `src/agents/registry.py` — new static `DISPATCHABLE_AGENTS` registry
+ (`AgentSpec` dataclass + `list_agents()` / `get_agent()`), mirroring the
+ repo's static agent-discovery convention. The descriptions double as routing
+ guidance for the router LLM.
+2. `src/agents/agent_task_router/` — new single-shot router agent
+ (`engine.py`, `tools.py`, `__init__.py`). One `call_serving_endpoint` call,
+ JSON in / JSON out (`{"agent": ..., "reasoning": ...}`); validates the chosen
+ key against the registry and degrades gracefully on "none"/unknown/unparseable.
+3. `src/back/objects/registry/agent_task_runner.py` — new orchestrator.
+ `start_agent_task()` launches a `TaskManager` background job (`task_router`
+ type); the worker routes, marks the `domain_tasks` row `in_progress`,
+ dispatches the chosen agent via the existing domain bridges
+ (`Ontology.generate_with_agent` / `generate_rules_with_agent` /
+ `assign_icons_with_agent`, `Mapping.auto_assign_with_agent`), then marks the
+ task `done` and writes audit comments. Defines the `AI_AGENT_PRINCIPAL`
+ sentinel (`agent://router`) and `is_ai_agent()`.
+4. `src/back/objects/registry/CommentService.py` — `create_task()` triggers the
+ orchestrator for the AI-agent sentinel and returns `agent_task_id`;
+ `list_assignees()` now always lists the "AI Agent" first.
+5. `src/front/static/global/js/comments-panel.js` — renders the AI Agent option
+ (robot emoji, "(auto)" suffix) in the assignee picker and, on creation of an
+ AI-agent task, shows an "AI Agent started" toast and refreshes the background
+ task tracker.
+
+**Modified files:**
+- `src/agents/registry.py` (new)
+- `src/agents/agent_task_router/__init__.py` (new)
+- `src/agents/agent_task_router/engine.py` (new)
+- `src/agents/agent_task_router/tools.py` (new)
+- `src/back/objects/registry/agent_task_runner.py` (new)
+- `src/back/objects/registry/CommentService.py`
+- `src/front/static/global/js/comments-panel.js`
+- `tests/units/agents/test_agent_task_router.py` (new)
+- `tests/units/registry/test_agent_task_runner.py` (new)
+- `tests/units/registry/test_comment_service.py`
+- `docs/architecture.md`
+
+**Test result:** 2644 passed, 17 skipped, 12 deselected (full PR gate).
+
+## Fix: no UI entry point to assign a task to the AI Agent
+
+**Context:** After the feature above, task creation was still only reachable as a
+per-comment action ("Create task" on a comment thread). There was no standalone
+way to create a task, so assigning one to the AI Agent was effectively hidden —
+users reported it was "not possible to assign a task to an AI agent in the UI".
+
+**Changes:**
+1. `src/front/static/global/js/comments-panel.js` — added a "New task" button to
+ the discussion panel header that opens a standalone task form (title,
+ assignee picker incl. the AI Agent, optional due date) and posts to
+ `POST /comments/{folder}/{version}/tasks` with no `comment_id` (already
+ supported by the backend). Refactored the per-comment task form into shared
+ `taskFormHtml()` / `wireTaskForm()` / `hideTaskBox()` helpers, reused by both
+ the per-comment and standalone flows. On success the comment timeline reloads.
+
+**Modified files:**
+- `src/front/static/global/js/comments-panel.js`
+
+**Test result:** Backend unchanged; full PR gate still 2644 passed, 17 skipped.
+
+## Fix: AI Agent outcome not visible + task not marked solved
+
+**Context:** After the AI Agent ran, its outcome was only written to the review
+audit log (not the Discussion) and there was no obvious change, so it looked
+like nothing happened. Requirement: post the agent's report into the domain
+Discussion and mark the task solved when the agent finishes.
+
+**Changes:**
+1. `src/back/objects/registry/agent_task_runner.py` —
+ - `_dispatch_agent` now returns `(summary, report, result)`; each agent
+ builds a human-readable markdown `report` (counts, sizes, where to review).
+ - Replaced the audit-only `_audit` with `_report`, which posts the report as
+ a real comment in the domain Discussion (threaded under the originating
+ comment via `_resolve_anchor`, else a domain-level note) AND records the
+ review-audit row. Used for the success report and for failure messages.
+ - On success the task is set to `done` (solved); the routing decision is now
+ folded into the final report instead of a separate audit row.
+2. `tests/units/registry/test_agent_task_runner.py` — updated for the 3-tuple
+ dispatch return and to assert the report is posted as a Discussion comment.
+
+**Modified files:**
+- `src/back/objects/registry/agent_task_runner.py`
+- `tests/units/registry/test_agent_task_runner.py`
+
+**Test result:** 2644 passed, 17 skipped, 12 deselected (full PR gate).
+
+**Note / follow-up:** the remaining specialized agents produce *proposals*
+(ontology draft, business rules, mappings, icons) that are not auto-applied —
+the report points the user to the relevant page to review/apply. Also,
+AI-Agent-assigned tasks are not yet surfaced in the "My Tasks" worklist
+(assignee is the `agent://router` sentinel, not a user email); the status is
+persisted correctly and the Discussion shows the outcome. My-Tasks visibility
+remains an optional follow-up.
+
+## Fix: editing tasks routed to the generator (no applied outcome)
+
+**Context:** An AI Agent task like "evaluate whether the Person class is
+necessary / restructure the hierarchy" was routed to the **Ontology Generator**,
+which only produced a 13k-char Turtle *draft* and never applied it — so the user
+saw a report but the ontology was unchanged. The task was really an *edit* of the
+existing ontology, which the **Ontology Assistant** performs in place (and
+persists). The generator's scope (whole-ontology from-scratch generation) didn't
+match the request, and editing had no dispatch target at all.
+
+**Changes:**
+1. `src/agents/registry.py` —
+ - Added a new `ontology_assistant` dispatchable agent ("Ontology Assistant")
+ whose description steers the router to edit/remove/rename/restructure
+ classes & properties on an existing ontology (changes applied directly).
+ - Tightened the `owl_generator` description to *whole-ontology, from-scratch*
+ generation only, explicitly deferring targeted edits to the assistant.
+2. `src/back/objects/registry/agent_task_runner.py` —
+ - `_dispatch_agent` gained a `task_text` argument (task title + description)
+ and an `ontology_assistant` branch: it runs
+ `agent_ontology_assistant.run_agent` single-turn with the task as the user
+ message and, when `ontology_changed`, calls
+ `Ontology.apply_agent_ontology_changes(...)` which normalizes, prunes orphan
+ mappings, updates the session ontology and **saves** it — a real, applied
+ outcome. The Discussion report carries the assistant's reply + new
+ class/property counts.
+ - `_run` now passes `task_text` into `_dispatch_agent`.
+3. `tests/units/registry/test_agent_task_runner.py` — added coverage for the
+ assistant branch applying + persisting changes, and for the no-change path.
+4. `tests/units/agents/test_agent_task_router.py` — registry assertion now
+ expects `ontology_assistant`.
+
+**Modified files:**
+- `src/agents/registry.py`
+- `src/back/objects/registry/agent_task_runner.py`
+- `tests/units/registry/test_agent_task_runner.py`
+- `tests/units/agents/test_agent_task_router.py`
+
+**Test result:** targeted suites green —
+`test_agent_task_runner.py` + `test_agent_task_router.py` +
+`test_comment_service.py`: 44 passed. Full suite shows 59 pre-existing,
+unrelated failures (API endpoint tests) caused by global test-ordering pollution
+— each affected file passes in isolation, and the failures reproduce without
+these changes.
+
+## Feature: AI Agent asks clarifying questions before running
+
+**Context:** AI-Agent tasks ran fire-and-forget — the router picked an agent and
+it acted immediately, so a vague or mis-routed task (e.g. "evaluate whether the
+Person class is necessary") produced an unwanted result. The AI Agent now
+**always confirms scope first**: it posts a short plan + clarifying questions in
+the task's Discussion thread, parks itself (`in_progress`), and resumes when the
+assignee replies, iterating across multiple rounds until confident before
+running the chosen agent. Designed with the brainstorming → writing-plans →
+subagent-driven-development flow; spec and plan live under
+`docs/superpowers/{specs,plans}/2026-06-18-ai-agent-clarifying-questions*`.
+Zero schema change: parked state reuses `in_progress`, the route is re-derived
+from the deterministic (temp 0) router each pass, and linkage uses the existing
+`comment_id` (a kickoff comment is created for standalone tasks).
+
+**Changes:**
+1. `src/agents/agent_task_planner/` (new: `engine.py`, `__init__.py`, `tools.py`)
+ — single-shot "ready vs. ask" gate mirroring the router. Returns
+ `PlanResult{ready, message}`; degrades safe (never `ready` on LLM/parse
+ failure), so the agent never runs without a human go-ahead.
+2. `src/back/objects/registry/agent_task_runner.py` — reworked the background
+ worker into route → reconstruct thread Q&A (`_thread_history`) → plan →
+ branch (`_run_for_task`): not ready → post the question and park; ready →
+ fold the assignee's answers into the agent input (`_fold_answers`) and run.
+ Added `resume_agent_task` (relaunched on a reply), a `_launch_worker` shared
+ launcher, and an atomic, leak-free in-process concurrency guard
+ (`_ACTIVE_TASKS` + `_ACTIVE_LOCK` + `_claim_task`).
+3. `src/back/objects/registry/CommentService.py` — `create_task` creates a
+ domain-level kickoff comment as the thread root for standalone AI-Agent tasks
+ (warns if it cannot); `add_comment` calls `_maybe_resume_agent`, which resumes
+ the matching active AI-Agent task when a teammate replies on its thread.
+4. `docs/architecture.md` — documented the clarify-then-run loop and added
+ `agent_task_planner/` to the agents listing.
+
+**Modified files:**
+- `src/agents/agent_task_planner/engine.py` (new)
+- `src/agents/agent_task_planner/__init__.py` (new)
+- `src/agents/agent_task_planner/tools.py` (new)
+- `src/back/objects/registry/agent_task_runner.py`
+- `src/back/objects/registry/CommentService.py`
+- `tests/units/agents/test_agent_task_planner.py` (new)
+- `tests/units/registry/test_agent_task_runner.py`
+- `tests/units/registry/test_comment_service.py`
+- `docs/architecture.md`
+
+**Test result:** new + touched suites green —
+`test_agent_task_planner.py` (3), `test_agent_task_runner.py` (14),
+`test_comment_service.py` (32), plus `tests/units/agents` and
+`tests/units/registry` sweeps. The only failures in the registry sweep are 4
+pre-existing `TestLakebaseCollab` cases in `test_registry_store.py` that require
+the optional `psycopg` backend (not installed); they reproduce without these
+changes.
+
+## Fix: ontology design tasks no longer dead-end at routing
+
+**Context:** An AI-Agent task phrased as a design/modeling request (e.g. "create
+an Agent Manager") was routed to "none" and hard-failed with "I could not route
+this task…", because every dispatchable agent's guidance read as narrow
+edit/generate/rules/icons/mapping work and the router treated an unfamiliar
+entity name as off-topic. Ontology design is exactly what the Ontology Assistant
+does, so these tasks should route there.
+
+**Changes:**
+1. `src/agents/registry.py` — broadened the `ontology_assistant` routing
+ description to declare it the DEFAULT agent for ontology modeling/design:
+ create/add/model/design NEW classes/entities/relationships (with an explicit
+ "create an Agent Manager entity" example) in addition to edits/cleanup, and to
+ prefer it over the Generator unless the whole ontology is being (re)built.
+2. `src/agents/agent_task_router/engine.py` — strengthened the router system
+ prompt: the ontology IS the domain model, so design/model/create/structure
+ tasks (and unfamiliar names, likely domain entities) route to the Ontology
+ Assistant; reserve "none" for work clearly unrelated to the ontology, its
+ mappings, rules, or icons.
+3. `tests/units/agents/test_agent_task_router.py` — added guards that the
+ Assistant's description advertises design intent and the router prompt treats
+ the ontology as the domain model and reserves "none" for unrelated tasks.
+
+**Modified files:**
+- `src/agents/registry.py`
+- `src/agents/agent_task_router/engine.py`
+- `tests/units/agents/test_agent_task_router.py`
+
+**Test result:** `test_agent_task_router.py` + `test_agent_task_runner.py` →
+26 passed.
+
+## Feature: AI-Agent async status + answer UX in the Discussion pane
+
+**Context:** When an AI-Agent task ran, its progress was only visible in the
+header task-tracker dropdown, and the Discussion offcanvas (the surface where
+the task lives) never auto-refreshed — so the agent's clarifying question never
+appeared live and there was no obvious way to answer it. The only path was the
+generic "Reply" button on the thread root, which works (a reply resumes the
+agent) but is undiscoverable. This adds inline progress + a clear answering
+affordance, frontend-only, reusing existing endpoints.
+
+**Changes:**
+1. `src/front/static/global/js/comments-panel.js` — added an AI-Agent live-status
+ layer:
+ - `loadAiTasks()` reads `/comments/{f}/{v}/tasks`, keying the version's
+ AI-Agent tasks (`assignee == agent://router`) by their thread-root
+ `comment_id` to drive per-thread state.
+ - `loadAgentRuns()` reads `/tasks/` for active `task_router` background runs;
+ `renderAgentStrip()` shows a top-of-panel progress strip (animated bar +
+ current step) mirroring the header tracker.
+ - Per-thread status chip (`agentChipHtml`): working… / waiting for your reply
+ / queued / done, plus a tinted left edge on AI threads.
+ - Prominent inline "Answer the AI Agent" box (`agentAnswerHtml`) on parked
+ (in_progress, not running) threads; sending posts a reply that resumes the
+ agent. Wired in `bindThreadActions`.
+ - Live polling (`startPanelPolling`/`panelPollTick`, 4s) while the panel is
+ open and any AI work is in flight; change-detected re-render via
+ `listSignature` and deferred while the user is mid-reply (`userIsComposing`)
+ so an open answer box is never clobbered. Stops when idle and on panel
+ close.
+ - `ensureAgentTracking()` kicks the strip + polling after opening a thread,
+ posting any comment/reply, or creating an AI-Agent task.
+2. `src/front/static/global/css/review-modals.css` — styles for the progress
+ strip (spinning robot + striped bar), the AI thread accent edge, the status
+ chips (working/waiting/queued/done), and the answer box.
+
+**Modified files:**
+- `src/front/static/global/js/comments-panel.js`
+- `src/front/static/global/css/review-modals.css`
+
+**Test result:** Frontend-only (vanilla JS/CSS, no JS unit-test harness in repo);
+no Python/backend code touched. Linter clean on both files. Verified the data
+contracts against `CommentService.list_tasks` (DomainTask `assignee`/`status`/
+`comment_id`) and `Task.to_dict` (`task_type="task_router"`, `progress`, `steps`,
+`current_step`).
+
+## Feature: AI-Agent design edits refresh the open pages live
+
+**Context:** When the Ontology Assistant agent edits and saves the ontology in
+place, the open editor pages (ontology designer, mapping) kept showing stale
+data until a manual reload. The agent's outcome was visible in the Discussion,
+but the model itself wasn't refreshed. This wires a live refresh off the
+existing AI-task completion signal — no backend change.
+
+**Changes:**
+1. `src/front/static/global/js/comments-panel.js` — `announceAgentCompletions()`
+ detects when an AI-Agent task transitions to `done` (transition-guarded:
+ baselines on first load, fires only on a live change) and dispatches a global
+ `ontobricks:design-updated` CustomEvent. Snapshot state reset on `openThread`.
+2. `src/front/static/ontology/js/ontology-init.js` — listens for
+ `ontobricks:design-updated`: re-pulls `loadOntologyFromSession()`, refreshes
+ the navbar status, and re-initialises the active section via
+ `_initSectionByName(SidebarNav.getActiveSection())` so the designer / map /
+ entity / relationship views reflect the agent's changes. Version-guarded, so
+ an unchanged ontology is a no-op for the canvas.
+3. `src/front/static/mapping/js/mapping-init.js` — listens for the same event:
+ re-fetches `/ontology/get-loaded-ontology` into `MappingState.loadedOntology`
+ and calls `refreshMappingDesign()` + `updateMappingCompletionStatus()`.
+
+**Modified files:**
+- `src/front/static/global/js/comments-panel.js`
+- `src/front/static/ontology/js/ontology-init.js`
+- `src/front/static/mapping/js/mapping-init.js`
+
+**Test result:** Frontend-only; `node --check` clean on all three files, linter
+clean, no backend code touched. Reuses existing global refresh entry points
+(`loadOntologyFromSession`, `_initSectionByName`, `refreshMappingDesign`).
+
+## Feature: render markdown in the Discussion panel
+
+**Context:** Comment bodies — especially the AI Agent's rich outcome reports —
+are written in markdown but the Discussion offcanvas showed the raw source
+(`**bold**`, `_italics_`, `- bullets`, headings). Rendered them as HTML.
+
+**Changes:**
+1. `src/front/static/global/js/comments-panel.js` — added a `renderMarkdown()`
+ helper that uses the globally-loaded `marked` (same library/options as the
+ ontology chat assistant: `breaks:true, gfm:true`), with a plain-text +
+ ` ` fallback. `bubble()` now renders the parsed body (tags already
+ stripped via `parseBody`) through it into a `.oc-text.oc-md` container
+ instead of escaping it verbatim.
+2. `src/front/static/global/css/review-modals.css` — tight, bubble-friendly
+ styles for rendered markdown (`.oc-md`): paragraph/list/heading spacing,
+ inline code + code blocks, links, blockquotes, and tables, with
+ `white-space:normal` so block elements lay out correctly.
+
+**Modified files:**
+- `src/front/static/global/js/comments-panel.js`
+- `src/front/static/global/css/review-modals.css`
+
+**Test result:** Frontend-only; `node --check` + linter clean. `marked` is
+already loaded for all pages via `base.html`.
+
+## Tests: Discussion-panel front-end contract tests
+
+**Context:** The Discussion-panel work above (AI-Agent async status + answering,
+the `ontobricks:design-updated` refresh wiring, and markdown rendering) lives
+entirely in static JS/CSS, and the repo has no JS unit-test harness. Added
+Python contract tests that fetch the served assets through the app's `/static`
+mount and assert the wiring is present — same philosophy as
+`tests/units/api/test_ui_rendering.py` — so an accidental removal/rename of a
+key hook is caught by CI.
+
+**Changes:**
+1. `tests/units/api/test_discussion_panel_assets.py` (new) — 16 token-level
+ assertions across four classes:
+ - `TestDiscussionMarkdownRendering` — `renderMarkdown` via global `marked`
+ with a plain-text fallback, bubble renders into `.oc-md`, CSS present.
+ - `TestDiscussionAgentStatus` — `loadAiTasks`/`loadAgentRuns` (`task_router`),
+ `renderAgentStrip` + `oc-agent-strip`, the polling loop
+ (`panelPollTick`/`startPanelPolling`/`userIsComposing`), the per-thread
+ chip (`agentChipHtml`, "waiting for your reply"), and the strip/chip CSS.
+ - `TestAnswerTheAgent` — the "Answer the AI Agent" box, `data-agent-send` →
+ `ensureAgentTracking` wiring, and `.oc-agent-answer` CSS.
+ - `TestDesignUpdatedRefresh` — the panel dispatches
+ `ontobricks:design-updated` (`announceAgentCompletions`), and the ontology
+ page (`loadOntologyFromSession` + `_initSectionByName(...)`) and mapping
+ page (`/ontology/get-loaded-ontology` + `refreshMappingDesign`) listen for
+ it.
+
+**Modified files:**
+- `tests/units/api/test_discussion_panel_assets.py` (new)
+
+**Test result:** `pytest tests/units/api/test_discussion_panel_assets.py` →
+16 passed.
+
+## Feature: render markdown in the Domain → Discussions timeline
+
+**Context:** Markdown rendering was added to the Discussion offcanvas earlier,
+but the Domain → Discussions timeline (a separate surface) still showed raw
+markdown source for each comment. Rendered it there too for parity.
+
+**Changes:**
+1. `src/front/static/domain/js/domain-collaboration.js` — added a
+ `renderMarkdown()` helper (same approach as the panel: global `marked` with a
+ plain-text/` ` fallback) and `timelineEntry()` now renders the parsed body
+ through it into a `.oc-tl-text.oc-md` container instead of escaping verbatim.
+2. `src/front/static/global/css/review-modals.css` — broadened the markdown
+ white-space reset from `.oc-text.oc-md` to `.oc-md` so it also applies to the
+ timeline's `.oc-tl-text.oc-md` (which carried `white-space: pre-wrap`). The
+ Domain page already loads `review-modals.css`, so the shared `.oc-md` block
+ styles the timeline with no extra assets.
+3. `tests/units/api/test_discussion_panel_assets.py` — added
+ `TestDiscussionTimelineMarkdown` (3 tests): the timeline defines the marked
+ renderer, `timelineEntry` renders `renderMarkdown(parsed.text)` into `.oc-md`,
+ and the unscoped `.oc-md` reset is present.
+
+**Modified files:**
+- `src/front/static/domain/js/domain-collaboration.js`
+- `src/front/static/global/css/review-modals.css`
+- `tests/units/api/test_discussion_panel_assets.py`
+
+**Test result:** `pytest tests/units/api/test_discussion_panel_assets.py` →
+19 passed. `node --check` + linter clean.
+
+## Change: Discussion panel is a single domain-wide thread (no anchors/tags)
+
+**Context:** Opening the discussion from the Ontology page showed a header badge
+like "Domain · Whole ontology diagram" and separated threads by the selected
+anchor (class/property/mapping/graph). Discussions are meant to be about the
+Domain as a whole, so the per-anchor separation and the entity/relationship tag
+picker were confusing. Unified everything to the Domain.
+
+**Changes:**
+1. `src/front/static/global/js/comments-panel.js`:
+ - `openThread()` now always opens the Domain thread (`anchorType: 'domain'`,
+ `anchorRef: ''`) regardless of the caller's anchor hints, so every entry
+ point (ontology, mapping, graph, domain) shows the same discussion.
+ - `renderAnchorBadge()` dropped the "kind" badge that separated threads; the
+ header subtitle now just names the domain (`folder · vX`).
+ - Removed the entity/relationship tag picker from the compose and reply
+ boxes; new comments post the raw body (no embedded tag marker).
+ - Deleted the now-unused tag-input helpers (`tagWidgetHtml`,
+ `tagSelectOptions`, `setupTagbar`, `addTagChip`, `collectTags`,
+ `encodeBody`). Kept `TAG_MARK` + `parseBody` + `tagsHtml` so older comments
+ that embedded tags still display their chips (read-only). `openForSelection`
+ / `taggableFromOntology` remain as public API but their anchor/taggable
+ args are now ignored by the panel.
+2. `tests/units/api/test_discussion_panel_assets.py` — added
+ `TestDiscussionDomainScope` (4 tests): opens at the domain anchor, no kind
+ badge, no tag picker in compose, and no tag encoding on post.
+
+**Modified files:**
+- `src/front/static/global/js/comments-panel.js`
+- `tests/units/api/test_discussion_panel_assets.py`
+
+**Test result:** `pytest tests/units/api/test_discussion_panel_assets.py` →
+23 passed. `node --check` + linter clean.
diff --git a/changelogs/v0.6.0/benoitcayladbx_2026-06-19.log b/changelogs/v0.6.0/benoitcayladbx_2026-06-19.log
new file mode 100644
index 00000000..2a602c79
--- /dev/null
+++ b/changelogs/v0.6.0/benoitcayladbx_2026-06-19.log
@@ -0,0 +1,400 @@
+# Per-Attribute Include/Exclude in Mapping Designer
+
+## Context
+Users needed the ability to intentionally exclude specific ontology attributes from the mapping,
+rather than having all unmapped attributes flagged as gaps. This adds a checkbox per attribute in
+the Designer bottom pane (Status tab, Attributes table) — checked = included (default),
+unchecked = excluded from mapping, validation and gap reporting.
+
+## Changes
+
+1. **`src/front/static/mapping/js/mapping-design.js`**
+ - Added `excludedAttributes: []` field to `EntityPanelState` and `RelPanelState`
+ - `initEntityPanel()` / `initRelationshipPanel()`: initialise `excludedAttributes` from
+ `existingMapping.excluded_attributes` on panel open
+ - `epOntologyRows` / `rpOntologyRows`: replaced static index+icon rows with checkbox rows
+ (`ep-attr-include-cb` / `rp-attr-include-cb`); excluded attributes render with
+ strikethrough and a dash icon
+ - Updated table headers: `#` column replaced with a `bi-check2-square` icon column
+ - `initEntityPanel()` / `initRelationshipPanel()`: added `change` event listeners on
+ per-attribute checkboxes; unchecking also removes the attribute from `attributeMappings`
+ and updates the row DOM in place (no full re-render)
+ - `saveEntityPanelMapping()` / `saveRelPanelMapping()`: filters excluded attributes out of
+ `attribute_mappings` before saving, and writes `excluded_attributes` array into the
+ mapping object when non-empty
+
+2. **`src/back/objects/mapping/Mapping.py`**
+ - `build_entity_mapping()`: propagates `excluded_attributes` list when present in input data
+ - `build_relationship_mapping()`: same
+ - `compute_mapping_gaps()`: attributes listed in `excluded_attributes` are no longer reported
+ as unmapped gaps
+
+## Modified Files
+- `src/front/static/mapping/js/mapping-design.js`
+- `src/back/objects/mapping/Mapping.py`
+
+## Test Results
+- 132 mapping-specific tests: all passed
+- Full unit suite (excluding e2e / property tests requiring optional deps): 2573 passed, 5 pre-existing failures (Lakebase/psycopg not installed), 57 skipped
+
+---
+
+# Bug Fix: Attribute Exclusion State Not Persisted
+
+## Context
+When a user toggled an attribute's include/exclude checkbox in the Designer Status tab, the change
+was stored only in the transient `EntityPanelState` / `RelPanelState`. If the panel was closed
+(or the user switched to another entity) without clicking "Save", the checkbox state was lost and
+reverted to the last persisted value on next open.
+
+## Changes
+
+1. **`src/front/static/mapping/js/mapping-design.js`**
+ - Added `_syncEntityAttrExclusions(classUri)` helper: writes
+ `EntityPanelState.excludedAttributes` directly into the matching entry of
+ `MappingState.config.entities` (creating a lightweight stub if no full mapping exists yet)
+ and calls `autoSaveMappings()`.
+ - Added `_syncRelAttrExclusions(propertyUri)` helper: same for relationships, using
+ `MappingState.config.relationships`.
+ - `initEntityPanel()` checkbox handler: calls `_syncEntityAttrExclusions(classUri)` after
+ every toggle so the state is immediately reflected in the global config and persisted to the
+ backend session.
+ - `initRelationshipPanel()` checkbox handler: calls `_syncRelAttrExclusions(ontologyProperty.uri)`
+ on every toggle.
+ - `initRelationshipPanel()`: stores `ontologyProperty.uri` into `RelPanelState.propertyUri` so
+ the auto-exclude button can reference it outside the closure.
+ - `autoExcludeUnmappedEntityAttrs()`: calls `_syncEntityAttrExclusions(classUri)` after
+ bulk-excluding unmapped attributes.
+ - `autoExcludeUnmappedRelAttrs()`: calls `_syncRelAttrExclusions(RelPanelState.propertyUri)`
+ after bulk-excluding unmapped attributes.
+
+## Modified Files
+- `src/front/static/mapping/js/mapping-design.js`
+
+## Test Results
+- Full unit suite (excluding e2e / property tests requiring optional deps): 2141 passed,
+ 1 pre-existing failure (Lakebase/psycopg not installed), 15 skipped
+
+---
+
+# Bug Fix: Column Assignments in Mapping Tab Not Persisted + Mapping Tab Disable State
+
+## Context
+Two issues in the Designer bottom pane Mapping tab:
+1. Column assignments (ID / Label / Attribute → column) made via the dropdown in the Mapping
+ tab were lost if the user closed the panel without clicking Save, because they only lived in
+ `EntityPanelState` / `RelPanelState` and were never written back to `MappingState.config`.
+2. The Mapping tab button was only disabled when there was no `existingMapping` object at all —
+ it remained enabled for mappings that had an empty `sql_query` (e.g. stub entries).
+
+## Changes
+
+1. **`src/front/static/mapping/js/mapping-design.js`**
+ - Added `_syncEntityColAssignments()` helper: reads `idColumn`, `labelColumn`,
+ `attributeMappings` (filtered for excluded attrs) from `EntityPanelState` and writes
+ them into the existing `MappingState.config.entities` entry, then calls
+ `autoSaveMappings()`. No-op when no existing entry is found.
+ - Added `_syncRelColAssignments()` helper: same for relationships, using
+ `RelPanelState.propertyUri`.
+ - `showEntityColumnMenu()` dropdown click handler: calls `_syncEntityColAssignments()`
+ after every column role assignment (ID / Label / Attribute / Clear).
+ - `showRelColumnMenu()` dropdown click handler: calls `_syncRelColAssignments()` after
+ every column role assignment.
+ - `runEntityPanelQuery()`: calls `_syncEntityColAssignments()` after
+ `autoMapEntityColumns()` + `renderEntityPanelGrid()` so auto-mapped columns are
+ immediately persisted.
+ - `runRelPanelQuery()`: same with `_syncRelColAssignments()`.
+ - Entity Mapping tab button: changed disabled condition from `!existingMapping` to
+ `!existingMapping?.sql_query` so the tab is disabled whenever there is no SQL query,
+ regardless of whether a partial mapping object exists.
+ - Relationship Mapping tab button: same fix.
+
+## Modified Files
+- `src/front/static/mapping/js/mapping-design.js`
+
+## Test Results
+- No new tests added (UI-only change).
+- Full unit suite (excluding e2e / property tests requiring optional deps): 2141 passed,
+ 1 pre-existing failure (Lakebase/psycopg not installed), 15 skipped
+
+---
+
+# Fix: Save Button Always Available for Mapped Entities (Replaces Auto-Save)
+
+## Context
+Auto-save on checkbox/column-assignment changes was not working: changes were lost on page
+refresh. The root cause was that `savePanelBtn` is disabled by default and only re-enabled by
+`renderEntityPanelGrid()` (called only when the Mapping tab is opened and query run). A user
+staying on the Status tab to toggle attribute exclusions could never save. Separately, if the
+auto-load query returned different columns than saved, idColumn was reset to null and the Save
+button was permanently disabled.
+
+The approach is changed to: remove all auto-save calls, make the Save button always available
+for entities/relationships that already have a saved mapping.
+
+## Changes
+
+1. **`src/front/static/mapping/js/mapping-design.js`**
+ - Removed all `_syncEntityAttrExclusions`, `_syncRelAttrExclusions`,
+ `_syncEntityColAssignments`, `_syncRelColAssignments` calls from event handlers
+ (checkbox change, column dropdown click, autoMap, auto-exclude buttons).
+ - `updateEntityPanelSaveBtn()`: now looks up the existing config entry by `panelEntityClass`
+ and uses `existingInConfig?.id_column` as a fallback when `EntityPanelState.idColumn`
+ is null (e.g. after auto-load with mismatched columns). Shows a warning status message
+ instead of disabling Save.
+ - `updateRelPanelSaveBtn()`: same fallback using `RelPanelState.propertyUri` and
+ `existingInConfig?.source_id_column` / `target_id_column`.
+ - `saveEntityPanelMapping()`: uses `effectiveSql` and `effectiveIdCol` (with fallback to
+ existing config values) so the save works when the user only changed attribute exclusions
+ on the Status tab without touching the Mapping tab. Also preserves existing `excluded`
+ flag from config.
+ - `saveRelPanelMapping()`: same fallbacks for `effectiveSql`, `effectiveSrc`, `effectiveTgt`.
+ - Auto-exclude button notifications updated to say "click Save to persist".
+
+## Modified Files
+- `src/front/static/mapping/js/mapping-design.js`
+
+## Test Results
+- Full unit suite (excluding e2e / property tests requiring optional deps): 2141 passed,
+ 1 pre-existing failure (Lakebase/psycopg not installed), 15 skipped
+
+---
+
+# Fix: Allow Saving Attribute Exclusions Without a Full Mapping
+
+## Context
+When an entity has no SQL mapping yet, the Save button showed "SQL + ID column required"
+and blocked saving. The user needs to be able to set attribute exclusion preferences
+(include/exclude checkboxes in Status tab) before the SQL is configured.
+
+The backend already skips stubs with empty `sql_query` in R2RML generation, gap computation,
+and validation (`if m.get("sql_query")`), so lightweight stub entries are safe.
+
+## Changes
+
+1. **`src/front/static/mapping/js/mapping-design.js`**
+ - `saveEntityPanelMapping()`: when `effectiveSql` or `effectiveIdCol` is empty, saves
+ only `excluded_attributes` without showing a warning. Updates the existing config entry
+ if one exists; otherwise creates a lightweight stub
+ `{ ontology_class, ontology_class_label, sql_query: '', excluded_attributes: [...] }`.
+ Shows "Attribute selections saved" notification, closes panel, refreshes canvas.
+ - `saveRelPanelMapping()`: same pattern for relationship panels.
+ - `updateEntityPanelSaveBtn()` / `updateRelPanelSaveBtn()`: Save button is now always
+ enabled (never disabled by these functions, except for inactive-version mode).
+ Status line shows informational text when no ID column is configured.
+ - Removed `saveBtn.disabled = true` on panel open (was overriding the always-enabled logic).
+ - Removed `disabled` attribute from `savePanelBtn` in HTML template.
+
+2. **`src/front/templates/partials/mapping/_mapping_design.html`**
+ - Removed `disabled` from `savePanelBtn` button element.
+
+## Modified Files
+- `src/front/static/mapping/js/mapping-design.js`
+- `src/front/templates/partials/mapping/_mapping_design.html`
+
+## Test Results
+- Full unit suite (excluding e2e / property tests requiring optional deps): 2141 passed,
+ 1 pre-existing failure (Lakebase/psycopg not installed), 15 skipped
+
+---
+
+# Bug Fix: Auto-Map Overwrites User Attribute Exclusions
+
+## Context
+When the auto-mapping agent ran for an entity that already had user-excluded attributes
+(saved in `excluded_attributes`), it would:
+1. Re-include excluded attributes in the generated `attribute_mappings` (since it reads from
+ the ontology's declared attributes without checking exclusions).
+2. Drop the `excluded_attributes` field entirely when replacing the existing mapping entry
+ in `ctx.entity_mappings`.
+
+The result: excluded attributes were silently re-mapped and their exclusion state was lost.
+
+## Changes
+
+1. **`src/agents/tools/mapping.py`**
+ - `tool_submit_entity_mapping()`: now reads the existing mapping's `excluded_attributes`
+ before building the new mapping. Applies a second filter on `filtered_mappings` to
+ remove any agent-assigned mappings for user-excluded attributes. Copies
+ `excluded_attributes` into the new mapping dict so the field survives the update.
+ - `tool_submit_relationship_mapping()`: same — preserves `excluded_attributes` from the
+ existing relationship mapping when updating.
+
+2. **`src/agents/tools/ontology.py`**
+ - `tool_get_ontology()`: before returning an entity's `attributes` list to the agent,
+ strips out any attributes that appear in the existing mapping's `excluded_attributes`.
+ This prevents the agent from even seeing excluded attributes, providing defence-in-depth.
+
+## Modified Files
+- `src/agents/tools/mapping.py`
+- `src/agents/tools/ontology.py`
+
+## Test Results
+- Full unit suite (excluding e2e / property tests requiring optional deps): 2141 passed,
+ 1 pre-existing failure (Lakebase/psycopg not installed), 15 skipped
+
+---
+
+# Bug Fix: Unmap Clears Attribute Exclusions + Auto-Map Result Drops excluded_attributes
+
+## Context
+Two related issues caused attribute exclusion state to be lost silently:
+
+1. **Unmap action** — right-clicking an entity on the canvas and choosing "Unmap" called
+ `MappingState.config.entities.splice(existingIndex, 1)`, which deleted the entire entry
+ including any `excluded_attributes`. After an Unmap + re-map cycle the exclusion state
+ appeared reset.
+
+2. **Auto-map result** — `_saveEntityAgentResult()` / `_saveRelAgentResult()` constructed a
+ new mapping object from the agent result without carrying forward `excluded_attributes`.
+ Even though `tool_submit_entity_mapping` now preserves `excluded_attributes` in the backend
+ `ctx.entity_mappings`, the frontend silently dropped it when writing back to
+ `MappingState.config`.
+
+ Additionally `_buildAgentEntityItem()` sent the full attribute list to the single-entity
+ auto-map endpoint — user-excluded attributes were visible to the agent.
+
+## Changes
+
+1. **`src/front/static/mapping/js/mapping-design.js`**
+ - `contextMenuUnassignEntity()`: instead of always splicing the entry, checks if
+ `excluded_attributes` is non-empty. If so, replaces the entry with a lightweight stub
+ `{ ontology_class, ontology_class_label, sql_query: '', excluded_attributes: [...] }`,
+ preserving exclusion state across an Unmap + re-map cycle.
+ - `contextMenuUnassignRelationship()`: same pattern for relationships.
+ - `_saveEntityAgentResult()`: reads `excluded_attributes` from the agent result (`m`),
+ falls back to `existingEntry.excluded_attributes` in `MappingState.config`, and writes
+ the field into the new mapping object when non-empty.
+ - `_saveRelAgentResult()`: same fallback for relationships.
+ - `_buildAgentEntityItem()`: filters out user-excluded attributes before building the item
+ payload sent to the single-entity auto-map endpoint, so the agent never sees them.
+
+## Modified Files
+- `src/front/static/mapping/js/mapping-design.js`
+
+## Test Results
+- Full unit suite (excluding e2e / property tests requiring optional deps): 2141 passed,
+ 1 pre-existing failure (Lakebase/psycopg not installed), 15 skipped
+
+---
+
+# Bug Fix: Attribute KPI and Canvas Node Color Ignore Excluded Attributes
+
+## Context
+Two UI components used all `dataProperties` (including user-excluded ones) when computing
+attribute coverage, causing the gauge and node colour to show less than 100% even when every
+*included* attribute was mapped.
+
+- **Auto-Map page KPI** (`updateStatus`): `totalAttributes` was the raw count of all attributes.
+ With 1 excluded attribute out of 14, the gauge showed 13/14 ≈ 93 % instead of 13/13 = 100 %.
+- **Designer canvas node colour**: the `partial` (orange) status was set when any `dataProperty`
+ had no column assigned, even excluded ones.
+
+Secondary issues in the same files:
+- `entitiesWithMissingAttrs` (drives the "Re-assign" button visibility) used excluded attrs.
+- `startPartial()` would queue entities for re-mapping because excluded attrs appeared missing.
+- `start()` and `startPartial()` sent the full attribute list to the backend, so the agent saw
+ excluded attributes again.
+
+## Changes
+
+1. **`src/front/static/mapping/js/mapping-autoassign.js`**
+ - `updateStatus()` / attribute loop: skip attributes present in `em.excluded_attributes`
+ before incrementing `totalAttributes` and `mappedAttributes`. The gauge denominator now
+ equals only included attributes.
+ - `entitiesWithMissingAttrs` filter: also skips excluded attributes when deciding whether
+ an entity has "missing" attributes (affects "Re-assign" button visibility).
+ - `startPartial()` entity filter: same fix — excluded attrs no longer trigger a re-map.
+ - `start()` entity builder: filters `excluded_attributes` out of the `attributes` array
+ before sending to `/mapping/auto-assign/start`.
+ - `startPartial()` entity builder: same filter.
+
+2. **`src/front/static/mapping/js/mapping-design.js`**
+ - Canvas node status logic: builds `includedProps` by filtering `dataProperties` against
+ `em.excluded_attributes`, then checks only `includedProps` for column completeness.
+ A fully-mapped entity with some excluded attributes now shows green instead of orange.
+
+## Modified Files
+- `src/front/static/mapping/js/mapping-autoassign.js`
+- `src/front/static/mapping/js/mapping-design.js`
+
+## Test Results
+- Full unit suite (excluding e2e / property tests requiring optional deps): 2141 passed,
+ 1 pre-existing failure (Lakebase/psycopg not installed), 15 skipped
+
+---
+
+# Feature: Column Names with Spaces / Special Characters in SQL and R2RML
+
+## Context
+Column names in source tables (Databricks Delta / UC) may contain spaces, hyphens,
+dots or other characters that are invalid in plain SQL identifiers or R2RML column
+references. Without quoting, the generated SQL would be syntactically invalid and
+the R2RML `rr:column` / `rr:template` references would not resolve correctly.
+
+## Changes
+
+1. **`src/agents/agent_auto_assignment/engine.py`** — system prompt
+ - Added a new "COLUMN NAME QUOTING (CRITICAL)" section instructing the agent to:
+ - Wrap unsafe column names with backticks: `` `customer name` ``.
+ - Alias backtick-quoted columns to safe snake_case names:
+ `` `customer name` AS customer_name ``.
+ - Pass only the final output column name (the alias) as `id_column`,
+ `label_column`, and `attribute_mappings` keys to `submit_entity_mapping`.
+ - Never pass a column name with spaces as a mapping key.
+ - Added "Apply the same backtick-quoting rules" note to the relationship SQL rules.
+
+2. **`src/back/core/w3c/r2rml/R2RMLGenerator.py`**
+ - Added `_quote_column(col)` helper: wraps a column name in double-quotes (per
+ R2RML spec §7.4) when it is not a plain SQL identifier (`[A-Za-z_][A-Za-z0-9_]*`).
+ Already-quoted names and empty strings are returned unchanged.
+ - Applied `_quote_column` to every `rr:column` Literal (label and attribute
+ object maps) and every column reference inside `rr:template` curly-braces
+ (entity subject, relationship source/target subject).
+
+3. **`tests/units/mapping/test_r2rml_generator.py`**
+ - Added `TestQuoteColumn` class with 8 tests covering: plain identifiers, already-
+ quoted names, spaces, hyphens, dots, empty strings, end-to-end entity template
+ quoting, and end-to-end attribute-mapping column quoting.
+
+## Modified Files
+- `src/agents/agent_auto_assignment/engine.py`
+- `src/back/core/w3c/r2rml/R2RMLGenerator.py`
+- `tests/units/mapping/test_r2rml_generator.py`
+
+## Test Results
+- R2RML generator tests: 20 passed (8 new)
+- Full mapping suite: 139 passed, 1 skipped
+- Full unit suite (excluding e2e / property tests): 2141 passed,
+ 1 pre-existing failure (Lakebase/psycopg not installed), 15 skipped
+
+---
+
+# Tests: Attribute Include/Exclude Coverage
+
+## Context
+No unit tests existed for the per-attribute exclusion feature that spans
+the model layer, gap reporting, R2RML export, and all three agent tools.
+
+## Changes
+
+1. **`tests/units/mapping/test_attribute_exclusion.py`** (new file — 33 tests)
+
+ | Class | Tests | What is verified |
+ |---|---|---|
+ | `TestBuildEntityMappingExcludedAttributes` | 5 | `excluded_attributes` propagated, empty list omitted, absent key omitted, copy isolation, coexists with `excluded` flag |
+ | `TestBuildRelationshipMappingExcludedAttributes` | 4 | Same for relationship mappings |
+ | `TestComputeMappingGapsExcludedAttributes` | 6 | Excluded attrs not in gap list; all excluded → no gaps; mapped+excluded → no gap; unmapped entity not checked |
+ | `TestR2RMLExcludedAttributes` | 3 | Included attr present in R2RML; absent attr_mapping → no predicate/column; end-to-end with excluded_attributes field |
+ | `TestToolSubmitEntityMappingExcludedAttributes` | 6 | No existing excl → field absent; existing excl preserved on re-map; agent mapping for excluded attr stripped; multiple exclusions; no attrs submitted; updates existing entry |
+ | `TestToolSubmitRelationshipMappingExcludedAttributes` | 3 | No excl → absent; excl preserved; updates existing entry |
+ | `TestToolGetOntologyExcludedAttributes` | 6 | All attrs visible; one excl hidden; multiple excl hidden; all excl → empty list; entity count unchanged; exclusion scoped to correct entity |
+
+## Modified Files
+- `tests/units/mapping/test_attribute_exclusion.py` (created)
+
+## Test Results
+- New suite: 33 passed
+- Full mapping suite: 172 passed, 1 skipped
+- Full unit suite (excluding e2e / property tests): 2174 passed,
+ 1 pre-existing failure (Lakebase/psycopg not installed), 15 skipped
diff --git a/changelogs/v0.6.0/benoitcayladbx_2026-06-20.log b/changelogs/v0.6.0/benoitcayladbx_2026-06-20.log
new file mode 100644
index 00000000..61b35c65
--- /dev/null
+++ b/changelogs/v0.6.0/benoitcayladbx_2026-06-20.log
@@ -0,0 +1,114 @@
+# Documentation Update — Attribute Include/Exclude, Column Quoting, Auto-Map Improvements
+
+## Context
+
+Documentation refresh following the complete implementation of per-attribute include/exclude
+in the Mapping Designer, Auto-Map KPI excluded counts, metadata quality warning, column-name
+quoting in SQL and R2RML, Auto-Exclude canvas logic refinement, and all related bug fixes.
+No code changes were made in this session — documentation only.
+
+## Changes
+
+1. `docs/features.md`
+ - Updated **Data Mapping** section to document per-attribute include/exclude, Auto-Exclude
+ unmapped attributes button, includes-aware Partial Mapping Detection, Auto-Map KPI excluded
+ counts, Metadata Quality Warning, column-name quoting (backtick SQL + R2RML double-quote),
+ and R2RML excluded-attribute suppression.
+
+2. `docs/user-guide.md`
+ - Added **Excluding and Including Attributes** subsection under Step 2 (Map Data Sources)
+ covering the Status tab checkbox workflow, Auto-Exclude unmapped, canvas colour semantics,
+ and KPI impact.
+ - Updated **Auto-Map** subsection to mention excluded-counts badge and Metadata Quality Warning.
+ - Updated **Validate Your Mappings** to clarify that only included attributes are checked.
+ - Extended **Mapping Strategy** tips with three new items: exclude irrelevant attributes early,
+ quote special column names, and use the metadata quality warning.
+
+3. `docs/sphinx/changelog.rst`
+ - Added v0.6.0 section covering all features, bug fixes, and new tests from this development
+ cycle.
+
+## Modified Files
+
+- `docs/features.md`
+- `docs/user-guide.md`
+- `docs/sphinx/changelog.rst`
+
+## Test Results
+
+108 / 108 passed (`tests/units/mapping/`) — no regressions.
+
+---
+
+# Always-Quote Column Names in R2RML and Agent SQL
+
+## Context
+
+User noted that the conditional quoting in `_quote_column` (only quote when not a plain
+identifier) could still miss edge cases: SQL reserved words, digit-leading names, or any
+future naming convention. Simpler and safer to always quote.
+
+## Changes
+
+1. `src/back/core/w3c/r2rml/R2RMLGenerator.py`
+ - `_quote_column`: removed the conditional check — now unconditionally wraps every
+ column name in double-quotes per R2RML spec §7.4.
+
+2. `src/back/core/w3c/r2rml/R2RMLParser.py`
+ - Added `_unquote_column()` helper that strips double-quotes when reading column names
+ back from R2RML.
+ - Applied `_unquote_column` to `id_column` (from `rr:template`), `object_column`
+ (from `rr:column`), and `target_id_column` (from relationship templates).
+
+3. `src/agents/agent_auto_assignment/engine.py`
+ - Updated `COLUMN NAME QUOTING` section of the system prompt: agent is now instructed
+ to ALWAYS backtick-quote every column name in SQL, even plain identifiers.
+
+4. `tests/units/mapping/test_r2rml_generator.py`
+ - `test_plain_identifier_unchanged` renamed to `test_plain_identifier_always_quoted`;
+ expectation updated to `"customer_id"`.
+ - Relationship URI template assertion updated to check presence of the class path
+ and column name rather than the exact unquoted template string.
+
+## Modified Files
+
+- `src/back/core/w3c/r2rml/R2RMLGenerator.py`
+- `src/back/core/w3c/r2rml/R2RMLParser.py`
+- `src/agents/agent_auto_assignment/engine.py`
+- `tests/units/mapping/test_r2rml_generator.py`
+
+## Test Results
+
+108 / 108 passed (`tests/units/mapping/`) — no regressions.
+
+---
+
+# Fix: strip backticks from submitted column names, clarify alias quoting in prompt
+
+## Context
+
+After the "always backtick every column" change, the LLM sometimes passed backtick-
+quoted values for id_column/label_column/source_id_column/target_id_column/attribute_mappings
+(e.g. `ID` instead of ID), causing 5 mappings to not be generated or stored incorrectly.
+
+## Changes
+
+1. `src/agents/tools/mapping.py`
+ - Added `_strip_backticks()` helper.
+ - Applied it to `id_column`, `label_column`, and all `attribute_mappings` values in
+ `tool_submit_entity_mapping`.
+ - Applied it to `source_id_column` and `target_id_column` in
+ `tool_submit_relationship_mapping`.
+
+2. `src/agents/agent_auto_assignment/engine.py`
+ - Rewrote the COLUMN NAME QUOTING section of the system prompt to explicitly state
+ that alias names (after AS) must NEVER be backtick-quoted, with concrete examples.
+
+## Modified Files
+
+- `src/agents/tools/mapping.py`
+- `src/agents/agent_auto_assignment/engine.py`
+
+## Test Results
+
+109 / 109 passed (`tests/units/mapping/`) — no regressions.
diff --git a/changelogs/v0.6.0/benoitcayladbx_2026-06-21.log b/changelogs/v0.6.0/benoitcayladbx_2026-06-21.log
new file mode 100644
index 00000000..4cadc8f8
--- /dev/null
+++ b/changelogs/v0.6.0/benoitcayladbx_2026-06-21.log
@@ -0,0 +1,58 @@
+## Data Sources — missing description warning
+
+### Context
+Users editing Data Sources had no visibility into how many tables and columns were missing descriptions.
+Description completeness directly affects AI mapping accuracy. Added an inline warning banner (consistent
+with the one already present on the Mapping → Auto-Map page) that counts and surfaces missing descriptions
+as soon as data sources are loaded or edited.
+
+### Changes
+
+1. `src/front/templates/partials/domain/_domain_metadata.html`
+ - Added `
-
+
+
+ {% set ontology_m = namespace(v=none) %}
+ {% set mapping_m = namespace(v=none) %}
+ {% set kg_m = namespace(v=none) %}
+ {% set domain_m = namespace(v=none) %}
+ {% for m in menu_config.menus %}
+ {% if m.id == 'ontology' %}{% set ontology_m.v = m %}{% endif %}
+ {% if m.id == 'assignment' %}{% set mapping_m.v = m %}{% endif %}
+ {% if m.id == 'digitaltwin'%}{% set kg_m.v = m %}{% endif %}
+ {% if m.id == 'domain' %}{% set domain_m.v = m %}{% endif %}
+ {% endfor %}
+
+
+
+ {% if domain_m.v %}
+
+
+ Domain
+
+
+ {% set ns = namespace(first_group=true) %}
+ {% for group in domain_m.v.groups %}
+ {% if group.id != 'domain-design' and not group.sidebar_only|default(false) %}
+
+
+ {% set ns.first_group = false %}
+ {% for item in group['items'] %}
+ {{ item.label }}
+ {% endfor %}
+ {% endif %}
+ {% endfor %}
+
+
+ {% endif %}
+
+ {% if ontology_m.v %}
+
+
+ {{ ontology_m.v.label }}
+
+
+
+ {% endif %}
+ {% if mapping_m.v %}
+
+
+ {{ mapping_m.v.label }}
+
+
+
+ {% endif %}
+ {% if kg_m.v %}
+
+
+ {{ kg_m.v.label }}
+
+
+
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+ {% set save_action = namespace(v=none) %}
+ {% if domain_m.v %}{% for a in domain_m.v.navbar_actions %}{% if a.action == 'domainSave' %}{% set save_action.v = a %}{% endif %}{% endfor %}{% endif %}
+ {% if save_action.v %}
+
+ {{ save_action.v.label }}
+
+ {% endif %}
+
+
+
+
+
diff --git a/src/front/templates/domain.html b/src/front/templates/domain.html
index 2006c0d4..d3c59dc4 100644
--- a/src/front/templates/domain.html
+++ b/src/front/templates/domain.html
@@ -9,6 +9,7 @@
+
{% endblock %}
@@ -42,6 +43,16 @@
+
+
+
+
+
+
-
+
+
+
+
diff --git a/src/front/templates/home.html b/src/front/templates/home.html
index d7e8c25a..20786232 100644
--- a/src/front/templates/home.html
+++ b/src/front/templates/home.html
@@ -66,7 +66,12 @@
Current Domain:
-
All Domains
+
+
All Domains
+
+ New Domain
+
+
diff --git a/src/front/templates/ontology.html b/src/front/templates/ontology.html
index b62ce58c..f7cf2d6e 100644
--- a/src/front/templates/ontology.html
+++ b/src/front/templates/ontology.html
@@ -86,7 +86,7 @@
{% include "partials/ontology/_ontology_business_rules.html" %}
-
+
@@ -127,7 +127,7 @@
-
+
diff --git a/src/front/templates/partials/_cohort_modals.html b/src/front/templates/partials/_cohort_modals.html
index 0a918009..302787f5 100644
--- a/src/front/templates/partials/_cohort_modals.html
+++ b/src/front/templates/partials/_cohort_modals.html
@@ -24,7 +24,7 @@
Configure outputs
Graph triples
Writes :inCohort<RuleName> triples
- into the knowledge graph (one membership predicate
+ into the graph viewer (one membership predicate
per rule, so multiple rules co-exist cleanly).
diff --git a/src/front/templates/partials/domain/_domain_discussions.html b/src/front/templates/partials/domain/_domain_discussions.html
new file mode 100644
index 00000000..6ae2dc30
--- /dev/null
+++ b/src/front/templates/partials/domain/_domain_discussions.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+ Loading discussions...
+
+
+
diff --git a/src/front/templates/partials/domain/_domain_documents.html b/src/front/templates/partials/domain/_domain_documents.html
index d61c7bc3..2f6698e0 100644
--- a/src/front/templates/partials/domain/_domain_documents.html
+++ b/src/front/templates/partials/domain/_domain_documents.html
@@ -6,6 +6,10 @@
Document Management
Upload and manage domain documents stored in the Unity Catalog volume
+
+
+
diff --git a/src/front/templates/partials/domain/_domain_information.html b/src/front/templates/partials/domain/_domain_information.html
index a9123693..cdc3e835 100644
--- a/src/front/templates/partials/domain/_domain_information.html
+++ b/src/front/templates/partials/domain/_domain_information.html
@@ -1,16 +1,9 @@
-
@@ -27,6 +31,9 @@
Data Sources
No data sources loaded
+
+
+
@@ -44,6 +51,27 @@
Data Sources