Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ normative research-grounding policy, and
[`docs/lineage-bi-research-notes.md`](docs/lineage-bi-research-notes.md) for
supporting literature and aggregate evidence. Event Lineage (reconstructed
post-to-post parents) is distinct from the typed ontology neighborhood
(ADR 0168); source-window continuation is ADR 0124. Do not mix those graphs.
(ADR 0184); source-window continuation is ADR 0124. Do not mix those graphs.

## Hard rule: no real data in repository artifacts

Expand Down
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ flowchart LR
| `post_chat.py` | Pluggable in-popup chat's reason-and-cite step (retrieve step lives in `backend/app/post_chat_ingestion.py`) |
| `commitment_extraction.py` | Pluggable LLM derivation of a customer commitment (promise + deadline) from a post; `Null` default, `ContextualOrchestrator` real impl |
| `ontology.py` | Loads `docs/ontology/lineageweave-kg.ttl`, the formal OWL 2/RDFS/SKOS vocabulary for the Knowledge Graph's node/edge types (ADR 0004) |
| `ontology_neighborhood.py` | Bounded typed ontology/provenance neighborhood (ADR 0168); PostgreSQL stays authoritative, OWL subclass is not an instance edge |
| `ontology_neighborhood.py` | Bounded typed ontology/provenance neighborhood (ADR 0184); PostgreSQL stays authoritative, OWL subclass is not an instance edge |
| `ontology_source_cursor.py` | Opaque HMAC source-window continuation (ADR 0124); keyset pagination, never OFFSET |
| `period_report.py` | Fit GRM/GPCM on persisted IRT rows, FIPC-select, EAP-score a period (ADR 0003 slice 3; Bock & Mislevy, 1982) |
| `fixtures.py` | Synthetic demo dataset -- no real data ships in this repo |
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.d/2.13.0-ontology-explorer.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 2.13.0 — Ontology provenance explorer

- Added `GET /api/ontology/neighborhood` as a bounded typed instance graph
(ADR 0168 / issue #341), distinct from Event Lineage.
(ADR 0184 / issue #341), distinct from Event Lineage.
- PostgreSQL stays authoritative; OWL/RDF/JSON-LD is a projection.
- SKOS broader comes from `corporate_entity.parent_entity_id`; OWL subclass
is rejected as an instance edge.
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.d/2.15.0-gnb-four-korean-chrome.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# 2.15.0 — Four-destination Korean analyst GNB

- Analyst Global Navigation is exactly 게시판, 고객 마스터, 달력, and Ask Agent.
- Buyer/Cubee copy is off the chrome; `BuyerNav` is now `WorkspaceNav`.
- Operator Admin stays a non-GNB destination.
- Unwired CalendarWeave consume fail-closes with
`이 범위의 일정을 아직 받을 수 없습니다` (ADR 0183).
18 changes: 17 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ All notable changes to this project are documented here. Format follows
- Bounded ontology/provenance neighborhood (`GET /api/ontology/neighborhood`)
with typed Post/Person/CorporateEntity/Team nodes, SKOS broader distinct
from OWL subclass, truth-status vocabulary, knowledge-cutoff binding, and
a Keyman-panel explorer that is not Event Lineage (ADR 0168 / #341).
a Keyman-panel explorer that is not Event Lineage (ADR 0184 / #341).
- Corroborated SKOS `altLabel` / `prefLabel` pairs now expand corporate
catalog candidates so a synthetic short form (`AGP`) and full form
(`Aurora Grid Power`) bind one `corporate_entity` row instead of
Expand Down Expand Up @@ -84,6 +84,22 @@ All notable changes to this project are documented here. Format follows
and looks up corporate-parent visibility independently of the child. OWL-Time
is cited as a W3C Candidate Recommendation Draft.

## [2.15.0] - 2026-08-25

### Changed

- Analyst Global Navigation is exactly four Korean destinations: 게시판,
고객 마스터, 달력, and Ask Agent. `BuyerNav` is now `WorkspaceNav`; Buyer
and Cubee are not product names on the chrome (ADR 0183).
- Operator Admin remains available as a non-GNB destination and is not a
fifth analyst tab.

### Fixed

- The 달력 destination fail-closes when CalendarWeave / Naruon CalDAV consume
is unwired, with `이 범위의 일정을 아직 받을 수 없습니다`. Weekly VOC and
newspaper stay on the board.

## [2.12.26] - 2026-08-24

### Added
Expand Down
2 changes: 1 addition & 1 deletion backend/app/ontology_neighborhood_ingestion.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Load an ABAC-visible ontology neighborhood from PostgreSQL (ADR 0168)."""
"""Load an ABAC-visible ontology neighborhood from PostgreSQL (ADR 0184)."""

from __future__ import annotations

Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0124-ontology-source-window-cursor.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
**Status:** Accepted
**Date:** 2026-08-21
**Issue:** [#363](https://github.com/ContextualWisdomLab/LineageWeave/issues/363)
**Predecessor:** [ADR 0168](0168-ontology-provenance-explorer.md)
**Predecessor:** [ADR 0184](0184-ontology-provenance-explorer.md)

**Context:** PR #349 withholds a continuation token when the authorization-safe recursive SQL window is exhausted. The in-memory `after:` cursor can only page facts already loaded, so a buyer cannot inspect every authorized relation in a large neighborhood without fabricating completeness. Issue #363 requires a real database continuation contract that keeps bounds, ABAC, cutoff, and deterministic ordering.

Expand Down
49 changes: 49 additions & 0 deletions docs/adr/0183-gnb-four-korean-chrome.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# ADR 0183: Four-destination Korean analyst GNB chrome

- Status: Accepted
- Date: 2026-08-25
- Supersedes (labels only): [0037](0037-buyer-gnb-and-product-surface.md) English GNB labels
- Folds: closed unmerged #474 WorkspaceNav rename

## Context

Main still shipped `BuyerNav` with English analyst tabs (Board / Customer
master / Calendar / Ask Agent / Admin). LineageWeave is an analyst workspace,
not a storefront, so "Buyer" and "Cubee" are not product names. Calendar is
CalendarWeave / Naruon CalDAV consume only (issue #336); this slice must not
add a calendar kernel.

## Decision

1. Analyst Global Navigation is exactly four destinations, in this order, with
these Korean labels regardless of locale:

1. 게시판 (board)
2. 고객 마스터 (customers)
3. 달력 (calendar)
4. Ask Agent (ask)

2. `BuyerNav` / `BuyerDestination` become `WorkspaceNav` /
`WorkspaceDestination`. GNB CSS uses `.workspace-gnb*`. The accessible name
is `Workspace navigation`.
3. Operator Admin remains a non-GNB destination. It is not a fifth analyst tab.
4. Weekly VOC remains a board filter. Weekly/monthly newspaper remains a
scheduled board post. Neither is a GNB item.
5. The 달력 destination fail-closes when CalendarWeave / Naruon consume is
unwired or missing, with the exact copy
`이 범위의 일정을 아직 받을 수 없습니다`. Existing advanced-review
commitment projection is not a calendar kernel and is not this GNB surface.

## Consequences

- Analyst chrome no longer shows Buyer, Cubee, Board, or Customer master.
- CalendarWeave wiring stays a later consume-only slice. This PR does not
import naruon mailbox, ThreadWeave, or Keyverse dumps.
- Historical ADRs keep the wording of their time.

## References

- ADR 0037 (original four-destination GNB)
- ADR 0038 (CalDAV consume vs internal commitments)
- Issue #336 (Naruon calendar projection)
- Closed unmerged #474 (WorkspaceNav rename)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# ADR 0168: Heterogeneous ontology and provenance explorer
# ADR 0184: Heterogeneous ontology and provenance explorer

**Status:** Accepted
**Date:** 2026-08-21
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ decision from them.
| [`PROV_O_IMPLEMENTATION_MATRIX.md`](../PROV_O_IMPLEMENTATION_MATRIX.md) | [0065](0065-prov-o-provenance-boundary.md) |
| [`ONTOLOGY_NAMESPACE_INVENTORY.md`](../doctoring/ONTOLOGY_NAMESPACE_INVENTORY.md) | [0157](0157-public-ontology-namespace-identity.md) |
| [`image-content-schema.md`](../image-content-schema.md) | [0066](0066-position-preserving-image-content.md) |
| [`storybook-inventory.md`](../storybook-inventory.md) | [0118](0118-uiux-standard-guide-v3-design-overhaul.md), [0168](0168-ontology-provenance-explorer.md) |
| [`storybook-inventory.md`](../storybook-inventory.md) | [0118](0118-uiux-standard-guide-v3-design-overhaul.md), [0184](0184-ontology-provenance-explorer.md) |

Files under `docs/doctoring/` remain non-normative supporting evidence even
when this map links them to an ADR. Runtime-evidence files record observed
Expand Down
2 changes: 1 addition & 1 deletion docs/doctoring/ONTOLOGY_EXPLORER_REFERENCES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Ontology explorer — doctoring

These are the standards that ground ADR 0168 / issue #341. Cite them in
These are the standards that ground ADR 0184 / issue #341. Cite them in
APA 7th when you extend the neighborhood, projection, or Buyer graph.

Cyganiak, R., Wood, D., & Lanthaler, M. (Eds.). (2014). *RDF 1.1 concepts
Expand Down
1 change: 1 addition & 0 deletions docs/storybook-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ buyer-facing control you can click before changing product CSS.
| `Admin/AdminPanel` | Change the tenant brand name, then verify the saved or failed state before leaving settings. | `--surface`, `--border`, `--space-panel-block`, `AdminPanel` |
| `Lineage/LineageDag` | Open the current branch node; compare empty, grouped/forked, mobile-scroll, ungrouped, and long-title states before changing graph CSS. On narrow viewports, swipe the named viewport or focus it and use arrow keys to inspect the full lineage. | `--surface`, `--border`, `--color-focus-border`, `--size-control-min`, `LineageDag` |
| `Chrome/PopupCloseButton` | Close the evidence panel or post popup. | `--space-close-inset`, `--font-size-close`, `PopupCloseButton` |
| `Navigation/WorkspaceNav` | Open 게시판, 고객 마스터, 달력, or Ask Agent. Admin is not a GNB tab. | `--gnb-height`, `--gnb-active-indicator-color`, `WorkspaceNav` |
| `Evidence/OntologyExplorer` | Inspect typed people/orgs/posts, then open authorized evidence. Distinct from Event Lineage. | `--color-primary`, `--color-table-border`, `OntologyExplorer` |
| `Reports/LeftoverPairList` | Read residual R, observed Y, expected E, map rank, and distance after IRT main effects, then open the named post. | `--color-chip-border`, `LeftoverPairList` |

Expand Down
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
"version": "2.14.0",
"version": "2.15.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
16 changes: 8 additions & 8 deletions frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@
}

/* GNB Navigation (§2.3.1) */
.buyer-gnb {
.workspace-gnb {
display: flex;
align-items: center;
height: var(--gnb-height);
Expand All @@ -155,7 +155,7 @@
z-index: var(--z-gnb-pulldown);
}

.buyer-gnb-item {
.workspace-gnb-item {
display: flex;
align-items: center;
height: 100%;
Expand All @@ -170,15 +170,15 @@
transition: color 0.15s ease;
}

.buyer-gnb-item:hover {
.workspace-gnb-item:hover {
color: var(--color-text-heading);
}

.buyer-gnb-item[aria-current="page"] {
.workspace-gnb-item[aria-current="page"] {
color: var(--color-primary);
}

.buyer-gnb-item[aria-current="page"]::after {
.workspace-gnb-item[aria-current="page"]::after {
content: "";
position: absolute;
bottom: 0;
Expand All @@ -188,7 +188,7 @@
background-color: var(--gnb-active-indicator-color);
}

.buyer-gnb-tools {
.workspace-gnb-tools {
margin-left: auto;
display: flex;
align-items: center;
Expand Down Expand Up @@ -906,15 +906,15 @@
.app-header {
padding: 0 1rem;
}
.buyer-gnb {
.workspace-gnb {
padding: 0 1rem;
}
}

@media (max-width: 768px) {
/* Phone Breakpoint (<768px) */

.buyer-gnb {
.workspace-gnb {
display: none; /* Replaced by drawer on mobile */
}

Expand Down
39 changes: 29 additions & 10 deletions frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1944,7 +1944,7 @@ describe("App, authenticated", () => {
stubBackend();
render(<App />);
expect(await screen.findByRole("button", { name: "View post: Public post" })).toBeInTheDocument();
await userEvent.click(screen.getByRole("button", { name: "Customer master" }));
await userEvent.click(screen.getByRole("button", { name: "고객 마스터" }));

expect(await screen.findByText("Demo Corp")).toBeInTheDocument();
expect(screen.getByText("DEMO-CORP-01 · Company")).toBeInTheDocument();
Expand All @@ -1964,7 +1964,7 @@ describe("App, authenticated", () => {
stubBackend({ customerEntityHierarchy: true });
render(<App />);
expect(await screen.findByRole("button", { name: "View post: Public post" })).toBeInTheDocument();
await userEvent.click(screen.getByRole("button", { name: "Customer master" }));
await userEvent.click(screen.getByRole("button", { name: "고객 마스터" }));

expect(await screen.findByText("Demo Group")).toBeInTheDocument();
const subsidiaryRow = screen.getByText("Demo Corp").closest("li");
Expand All @@ -1984,7 +1984,7 @@ describe("App, authenticated", () => {
stubBackend();
render(<App />);
expect(await screen.findByRole("button", { name: "View post: Public post" })).toBeInTheDocument();
await userEvent.click(screen.getByRole("button", { name: "Customer master" }));
await userEvent.click(screen.getByRole("button", { name: "고객 마스터" }));

expect(await screen.findByText("Northridge Grid")).toBeInTheDocument();
expect(screen.getByText("Voice of Customer (1), Voice of Competitor (1)")).toBeInTheDocument();
Expand All @@ -2006,7 +2006,7 @@ describe("App, authenticated", () => {
stubBackend({ admin: true, manyCustomerHints: 1 });
render(<App />);
expect(await screen.findByRole("button", { name: "View post: Public post" })).toBeInTheDocument();
await userEvent.click(screen.getByRole("button", { name: "Customer master" }));
await userEvent.click(screen.getByRole("button", { name: "고객 마스터" }));

expect(await screen.findByText("CUST-0")).toBeInTheDocument();
await userEvent.click(screen.getByRole("button", { name: "Resolve" }));
Expand All @@ -2018,7 +2018,7 @@ describe("App, authenticated", () => {
stubBackend({ manyCustomerHints: 1 });
render(<App />);
expect(await screen.findByRole("button", { name: "View post: Public post" })).toBeInTheDocument();
await userEvent.click(screen.getByRole("button", { name: "Customer master" }));
await userEvent.click(screen.getByRole("button", { name: "고객 마스터" }));

expect(await screen.findByText("CUST-0")).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Resolve" })).not.toBeInTheDocument();
Expand All @@ -2034,7 +2034,7 @@ describe("App, authenticated", () => {
stubBackend({ hintRelatedPosts: true });
render(<App />);
expect(await screen.findByRole("button", { name: "View post: Public post" })).toBeInTheDocument();
await userEvent.click(screen.getByRole("button", { name: "Customer master" }));
await userEvent.click(screen.getByRole("button", { name: "고객 마스터" }));

const customerSection = await screen.findByRole("region", { name: "Observed customer evidence" });
expect(within(customerSection).getByText("Related posts (1)").closest("details")).toHaveClass(
Expand All @@ -2060,7 +2060,7 @@ describe("App, authenticated", () => {
stubBackend({ manyCustomerHints: 45 });
render(<App />);
expect(await screen.findByRole("button", { name: "View post: Public post" })).toBeInTheDocument();
await userEvent.click(screen.getByRole("button", { name: "Customer master" }));
await userEvent.click(screen.getByRole("button", { name: "고객 마스터" }));

expect(await screen.findByText("CUST-0")).toBeInTheDocument();
expect(screen.getByText(/Showing the first 30 of 45 observed customer identifiers/)).toBeInTheDocument();
Expand Down Expand Up @@ -3868,12 +3868,31 @@ describe("App, authenticated", () => {
);
});

it("keeps advanced review tools out of the buyer board", async () => {
it("keeps advanced review tools out of the analyst board", async () => {
stubBackend();
render(<App />);

expect(await screen.findByRole("navigation", { name: "Buyer navigation" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Board" })).toHaveAttribute("aria-current", "page");
const nav = await screen.findByRole("navigation", { name: "Workspace navigation" });
expect(nav).toBeInTheDocument();
expect(screen.getByRole("button", { name: "게시판" })).toHaveAttribute("aria-current", "page");
expect(within(nav).getAllByRole("button").map((button) => button.textContent)).toEqual([
"게시판",
"고객 마스터",
"달력",
"Ask Agent",
]);
expect(nav.textContent).not.toMatch(/Buyer|Cubee|Board|Customer master/i);
expect(within(nav).queryByRole("button", { name: /Admin|관리자/i })).not.toBeInTheDocument();
expect(screen.queryByText("Advanced review tools")).not.toBeInTheDocument();
});

it("fails closed on the calendar destination when CalendarWeave consume is unwired", async () => {
stubBackend();
render(<App />);

await userEvent.click(await screen.findByRole("button", { name: "달력" }));
expect(screen.getByRole("heading", { name: "달력" })).toBeInTheDocument();
expect(screen.getByText("이 범위의 일정을 아직 받을 수 없습니다")).toBeInTheDocument();
expect(screen.queryByText(/Buyer|Cubee/i)).not.toBeInTheDocument();
});
});
Loading
Loading