From 4ce0a4afb98de1fa1bdc8ce4527133f86c0658e7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 22:40:16 -0700 Subject: [PATCH 01/26] test(projects): define evidence-bound project history contract --- tests/test_project_history.py | 60 +++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 tests/test_project_history.py diff --git a/tests/test_project_history.py b/tests/test_project_history.py new file mode 100644 index 000000000..52df02e54 --- /dev/null +++ b/tests/test_project_history.py @@ -0,0 +1,60 @@ +"""RED contracts for the Buyer project-history timeline.""" + +from __future__ import annotations + +import pytest + +from lineageweave.project_history import ( + classify_project_event, + normalize_project_key, + responsibility_transition_code, +) + + +def test_project_identity_is_exact_but_unicode_compatible() -> None: + """Compatibility forms may normalize; fuzzy project binding may not.""" + assert normalize_project_key(" P-100 ") == "p-100" + assert normalize_project_key("P-100-A") != normalize_project_key("P-100") + with pytest.raises(ValueError): + normalize_project_key(" ") + + +def test_event_display_classification_does_not_create_authority() -> None: + """The lifecycle label is presentation metadata over an existing post.""" + assert ( + classify_project_event( + title="Contract awarded", + source_stage_code=None, + source_detail_state_code=None, + voc_type_code=None, + is_focus=False, + ) + == "contract_awarded" + ) + assert ( + classify_project_event( + title="Field complaint received", + source_stage_code=None, + source_detail_state_code=None, + voc_type_code="voc", + is_focus=True, + ) + == "voc_received" + ) + assert ( + classify_project_event( + title="Field complaint received", + source_stage_code=None, + source_detail_state_code=None, + voc_type_code="voc", + is_focus=False, + ) + == "source_recorded" + ) + + +def test_responsibility_transition_describes_document_evidence_only() -> None: + """Missing adjacent evidence is a visible evidence gap, not an HR fact.""" + assert responsibility_transition_code(["person:a"], ["person:a"]) == "continuous" + assert responsibility_transition_code(["person:a"], ["person:b"]) == "handoff" + assert responsibility_transition_code(["person:a"], []) == "assignment_gap" From dc74d3671e864aac3b03d22fdcb39592f6869d71 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 22:45:12 -0700 Subject: [PATCH 02/26] test(ui): define accessible project-history timeline contract --- .../ProjectHistoryTimeline.test.tsx | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 frontend/src/components/ProjectHistoryTimeline.test.tsx diff --git a/frontend/src/components/ProjectHistoryTimeline.test.tsx b/frontend/src/components/ProjectHistoryTimeline.test.tsx new file mode 100644 index 000000000..5428969eb --- /dev/null +++ b/frontend/src/components/ProjectHistoryTimeline.test.tsx @@ -0,0 +1,130 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +import type { ProjectHistoryProjection } from "../projectHistory"; +import { ProjectHistoryTimeline } from "./ProjectHistoryTimeline"; + +const projection: ProjectHistoryProjection = { + contract_version: 1, + project_key: "P-100", + normalized_project_key: "p-100", + project_name: "Transformer renewal", + focus_event_id: "voc", + time_basis_code: "source_post_created_at_fallback", + event_count: 3, + distinct_observed_actor_count: 2, + truncated: false, + events: [ + { + event_id: "award", + source_post_id: "post-award", + event_title: "Contract awarded", + event_type_code: "contract_awarded", + event_type_basis_code: "display_classification", + occurred_at: "2022-03-11T09:00:00Z", + time_basis_code: "source_post_created_at_fallback", + voc_type_code: null, + source_stage_code: null, + source_detail_state_code: null, + project_matches: [], + observed_responsibilities: [ + { + actor_key: "person:sales", + actor_name: "Kim OO", + actor_type_code: "prov_person", + affiliated_organization_name: "Demo Corp", + responsibility: "Observed award owner", + truth_status_code: "observed", + provenance: "post_summary_role", + }, + ], + responsibility_transition_code: null, + related_prior_paths: [], + }, + { + event_id: "spec", + source_post_id: "post-spec", + event_title: "Specification changed", + event_type_code: "specification_changed", + event_type_basis_code: "display_classification", + occurred_at: "2023-06-15T09:00:00Z", + time_basis_code: "source_post_created_at_fallback", + voc_type_code: null, + source_stage_code: null, + source_detail_state_code: null, + project_matches: [], + observed_responsibilities: [ + { + actor_key: "person:pm", + actor_name: "Park OO", + actor_type_code: "prov_person", + affiliated_organization_name: "Demo Corp", + responsibility: "Observed specification owner", + truth_status_code: "observed", + provenance: "post_summary_role", + }, + ], + responsibility_transition_code: "handoff", + related_prior_paths: [], + }, + { + event_id: "voc", + source_post_id: "post-voc", + event_title: "VOC received", + event_type_code: "voc_received", + event_type_basis_code: "display_classification", + occurred_at: "2026-02-02T09:00:00Z", + time_basis_code: "source_post_created_at_fallback", + voc_type_code: "voc", + source_stage_code: null, + source_detail_state_code: null, + project_matches: [], + observed_responsibilities: [], + responsibility_transition_code: "assignment_gap", + related_prior_paths: [ + { + source_event_id: "award", + target_event_id: "voc", + event_ids: ["award", "spec", "voc"], + edges: [ + { parent_event_id: "award", child_event_id: "spec", fused_score: 0.91 }, + { parent_event_id: "spec", child_event_id: "voc", fused_score: 0.73 }, + ], + minimum_fused_score: 0.73, + truth_status_code: "inferred", + source_relation_code: "post_lineage_edge", + provenance: "post_lineage_edge.fused_score", + }, + ], + }, + ], +}; + +describe("ProjectHistoryTimeline", () => { + it("shows the focus event, evidence gap, and non-causal prior history", () => { + const onOpenPost = vi.fn(); + render(); + + const vocTab = screen.getByRole("tab", { name: /VOC received/ }); + expect(vocTab).toHaveAttribute("aria-selected", "true"); + expect(vocTab).toHaveAttribute("aria-current", "step"); + expect(screen.getByText(/evidence gap/i)).toBeInTheDocument(); + expect(screen.getByText(/related history, not causation/i)).toBeInTheDocument(); + + fireEvent.click(screen.getByRole("button", { name: /open source record: VOC received/i })); + expect(onOpenPost).toHaveBeenCalledWith("post-voc"); + }); + + it("uses roving keyboard selection and a labelled tabpanel", () => { + render(); + const vocTab = screen.getByRole("tab", { name: /VOC received/ }); + + fireEvent.keyDown(vocTab, { key: "ArrowLeft" }); + const specTab = screen.getByRole("tab", { name: /Specification changed/ }); + expect(specTab).toHaveFocus(); + expect(specTab).toHaveAttribute("aria-selected", "true"); + + const panel = screen.getByRole("tabpanel"); + expect(panel).toHaveAttribute("aria-labelledby", specTab.id); + }); +}); From 09ab37bc9d38ba539f6dccdbbe0153fd329081f8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 22:46:45 -0700 Subject: [PATCH 03/26] test(projects): classify every visible VOC record --- tests/test_project_history.py | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/tests/test_project_history.py b/tests/test_project_history.py index 52df02e54..5e731585f 100644 --- a/tests/test_project_history.py +++ b/tests/test_project_history.py @@ -31,26 +31,17 @@ def test_event_display_classification_does_not_create_authority() -> None: ) == "contract_awarded" ) - assert ( - classify_project_event( - title="Field complaint received", - source_stage_code=None, - source_detail_state_code=None, - voc_type_code="voc", - is_focus=True, - ) - == "voc_received" - ) - assert ( - classify_project_event( - title="Field complaint received", - source_stage_code=None, - source_detail_state_code=None, - voc_type_code="voc", - is_focus=False, + for is_focus in (False, True): + assert ( + classify_project_event( + title="Field complaint received", + source_stage_code=None, + source_detail_state_code=None, + voc_type_code="voc", + is_focus=is_focus, + ) + == "voc_received" ) - == "source_recorded" - ) def test_responsibility_transition_describes_document_evidence_only() -> None: From 3ee2dc723ecb380bd0a74e3df7f0befd6f9004ac Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 22:48:30 -0700 Subject: [PATCH 04/26] feat(projects): port evidence-bound history core for RED repair --- backend/app/project_history.py | 220 ++++++++++ .../src/components/ProjectHistoryTimeline.css | 241 +++++++++++ .../ProjectHistoryTimeline.stories.tsx | 97 +++++ .../src/components/ProjectHistoryTimeline.tsx | 292 +++++++++++++ frontend/src/projectHistory.ts | 391 +++++++++++++++++ lineageweave/project_history.py | 396 ++++++++++++++++++ 6 files changed, 1637 insertions(+) create mode 100644 backend/app/project_history.py create mode 100644 frontend/src/components/ProjectHistoryTimeline.css create mode 100644 frontend/src/components/ProjectHistoryTimeline.stories.tsx create mode 100644 frontend/src/components/ProjectHistoryTimeline.tsx create mode 100644 frontend/src/projectHistory.ts create mode 100644 lineageweave/project_history.py diff --git a/backend/app/project_history.py b/backend/app/project_history.py new file mode 100644 index 000000000..68d163ae5 --- /dev/null +++ b/backend/app/project_history.py @@ -0,0 +1,220 @@ +"""ABAC-safe PostgreSQL projection for Buyer project histories.""" + +from __future__ import annotations + +from collections.abc import Mapping, Sequence +from datetime import datetime +from typing import Any, Protocol + +from backend.app.post_eligibility import SOURCE_POST_ELIGIBILITY_SQL +from lineageweave.project_history import build_project_history_projection, normalize_project_key + +PROJECT_HISTORY_DEFAULT_LIMIT = 64 +PROJECT_HISTORY_MAXIMUM_LIMIT = 128 + + +class ProjectHistoryConnection(Protocol): + """Minimal asynchronous query port required by this repository.""" + + async def fetch(self, query: str, *args: object) -> Sequence[Mapping[str, Any]]: + """Execute a bounded read query and return mapping-like rows.""" + + ... + + +_ELIGIBILITY = SOURCE_POST_ELIGIBILITY_SQL.format(alias="post") +_PROJECT_MATCH = """ +( + lower(normalize(btrim(coalesce(post.source_project_code, '')), NFKC)) = $1 + or lower(normalize(btrim(coalesce(post.source_project_name, '')), NFKC)) = $1 + or exists ( + select 1 + from post_project_mention mention + where mention.post_id = post.post_id + and ( + lower(normalize(btrim(mention.project_key), NFKC)) = $1 + or lower(normalize(btrim(mention.project_name), NFKC)) = $1 + ) + ) +) +""" +_EVENT_SQL = f""" +select post.post_id, + post.post_title, + post.created_at, + post.voc_type_code, + post.source_stage_code, + post.source_detail_state_code + from source_post post + where (post.visibility_code = 'public' + or post.corporate_entity_id::text = any($2::text[])) + and {_ELIGIBILITY} + and post.created_at <= $3 + and {_PROJECT_MATCH} + order by post.created_at, post.post_id + limit $4 +""" +_FOCUS_SQL = f""" +select post.post_id, + post.post_title, + post.created_at, + post.voc_type_code, + post.source_stage_code, + post.source_detail_state_code + from source_post post + where (post.visibility_code = 'public' + or post.corporate_entity_id::text = any($2::text[])) + and {_ELIGIBILITY} + and post.created_at <= $3 + and post.post_id = $4::uuid + and {_PROJECT_MATCH} + limit 1 +""" +_MATCH_SQL = """ +select post.post_id, + 'source_project_code'::text as match_kind_code, + post.source_project_code as matched_value, + null::numeric as confidence, + null::text as ontology_iri, + 'source_post.source_project_code'::text as provenance + from source_post post + where post.post_id = any($1::uuid[]) + and lower(normalize(btrim(coalesce(post.source_project_code, '')), NFKC)) = $2 +union all +select post.post_id, + 'source_project_name'::text, + post.source_project_name, + null::numeric, + null::text, + 'source_post.source_project_name'::text + from source_post post + where post.post_id = any($1::uuid[]) + and lower(normalize(btrim(coalesce(post.source_project_name, '')), NFKC)) = $2 +union all +select mention.post_id, + 'semantic_project_key'::text, + mention.project_key, + mention.confidence, + mention.ontology_iri, + 'post_project_mention.project_key'::text + from post_project_mention mention + where mention.post_id = any($1::uuid[]) + and lower(normalize(btrim(mention.project_key), NFKC)) = $2 +union all +select mention.post_id, + 'semantic_project_name'::text, + mention.project_name, + mention.confidence, + mention.ontology_iri, + 'post_project_mention.project_name'::text + from post_project_mention mention + where mention.post_id = any($1::uuid[]) + and lower(normalize(btrim(mention.project_name), NFKC)) = $2 +order by post_id, match_kind_code, matched_value +""" +_ROLE_SQL = """ +select role.post_id, + role.actor_name, + role.responsibility, + role.actor_type_code, + role.affiliated_organization_name, + role.cataloged_person_id, + role.cataloged_team_id, + role.cataloged_corporate_entity_id + from post_summary_role role + where role.post_id = any($1::uuid[]) + order by role.post_id, role.actor_type_code, role.actor_name, role.responsibility +""" +_EDGE_SQL = """ +select edge.parent_post_id, edge.child_post_id, edge.fused_score + from post_lineage_edge edge + where edge.parent_post_id = any($1::uuid[]) + and edge.child_post_id = any($1::uuid[]) + order by edge.child_post_id, edge.parent_post_id +""" + + +class ProjectHistoryNotFound(LookupError): + """No authorized project history matched the requested identity.""" + + +async def fetch_project_history_projection( + conn: ProjectHistoryConnection, + *, + project_key: str, + focus_post_id: str | None, + knowledge_cutoff: datetime, + corporate_entity_ids: Sequence[str], + limit: int = PROJECT_HISTORY_DEFAULT_LIMIT, +) -> dict[str, Any]: + """Return a bounded project history from authorized PostgreSQL evidence. + + The query applies source eligibility, cutoff, and ABAC before selecting + event IDs. All subsequent match, role, and lineage reads are constrained to + that visible ID set, so hidden rows cannot affect counts, transitions, or + prior-history paths. An authorized focus event remains in a truncated + projection even when it falls beyond the earliest page. + """ + + if limit < 1 or limit > PROJECT_HISTORY_MAXIMUM_LIMIT: + raise ValueError("project history limit is outside the supported bound") + normalized_key = normalize_project_key(project_key) + rows = list( + await conn.fetch( + _EVENT_SQL, + normalized_key, + list(corporate_entity_ids), + knowledge_cutoff, + limit + 1, + ) + ) + truncated = len(rows) > limit + event_rows = rows[:limit] + if not event_rows: + raise ProjectHistoryNotFound(project_key) + visible_ids = [str(row["post_id"]) for row in event_rows] + if focus_post_id is not None and focus_post_id not in set(visible_ids): + focus_rows = list( + await conn.fetch( + _FOCUS_SQL, + normalized_key, + list(corporate_entity_ids), + knowledge_cutoff, + focus_post_id, + ) + ) + if not focus_rows: + raise ProjectHistoryNotFound(project_key) + truncated = True + event_rows = (event_rows[: limit - 1] if limit > 1 else []) + [focus_rows[0]] + event_rows.sort(key=lambda row: (row["created_at"], str(row["post_id"]))) + visible_ids = [str(row["post_id"]) for row in event_rows] + + match_rows, role_rows, edge_rows = await _fetch_project_children( + conn, + visible_ids=visible_ids, + normalized_key=normalized_key, + ) + return build_project_history_projection( + project_key=project_key, + focus_event_id=focus_post_id, + event_rows=event_rows, + match_rows=match_rows, + role_rows=role_rows, + edge_rows=edge_rows, + truncated=truncated, + ) + + +async def _fetch_project_children( + conn: ProjectHistoryConnection, + *, + visible_ids: Sequence[str], + normalized_key: str, +) -> tuple[list[Mapping[str, Any]], list[Mapping[str, Any]], list[Mapping[str, Any]]]: + """Fetch only child evidence whose endpoints are already authorized.""" + + matches = list(await conn.fetch(_MATCH_SQL, list(visible_ids), normalized_key)) + roles = list(await conn.fetch(_ROLE_SQL, list(visible_ids))) + edges = list(await conn.fetch(_EDGE_SQL, list(visible_ids))) + return matches, roles, edges diff --git a/frontend/src/components/ProjectHistoryTimeline.css b/frontend/src/components/ProjectHistoryTimeline.css new file mode 100644 index 000000000..0ff7a031e --- /dev/null +++ b/frontend/src/components/ProjectHistoryTimeline.css @@ -0,0 +1,241 @@ +.project-history { + display: grid; + gap: 1rem; + min-width: 0; +} + +.project-history-header, +.project-history-detail-heading { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; +} + +.project-history-header h3, +.project-history-detail-heading h4 { + margin: 0; +} + +.project-history-counts, +.project-history-time-basis, +.project-history-warning, +.project-history-boundary { + margin: 0; +} + +.project-history-warning, +.project-history-boundary { + border-inline-start: 0.25rem solid currentColor; + padding-inline-start: 0.75rem; +} + +.project-history-tabs { + display: grid; + grid-auto-flow: column; + grid-auto-columns: minmax(10rem, 1fr); + overflow-x: auto; + padding: 1.5rem 0 0.5rem; + position: relative; +} + +.project-history-tabs::before { + content: ""; + position: absolute; + inset-inline: 1rem; + top: 2rem; + border-top: 2px solid var(--border-color, #9aa4b2); +} + +.project-history-tab { + appearance: none; + background: transparent; + border: 0; + color: inherit; + display: grid; + gap: 0.35rem; + justify-items: center; + min-height: 7rem; + padding: 0; + position: relative; + text-align: center; +} + +.project-history-tab:focus-visible { + outline: 3px solid currentColor; + outline-offset: 0.25rem; +} + +.project-history-marker { + background: currentColor; + border: 0.25rem solid var(--surface-color, #fff); + border-radius: 50%; + box-shadow: 0 0 0 2px currentColor; + height: 1rem; + width: 1rem; + z-index: 1; +} + +.project-history-tab-current .project-history-marker { + height: 1.25rem; + width: 1.25rem; +} + +.project-history-tab[aria-selected="true"] strong { + text-decoration: underline; + text-underline-offset: 0.25rem; +} + +.project-history-detail { + border: 1px solid var(--border-color, #c8d0da); + border-radius: 0.75rem; + display: grid; + gap: 1rem; + padding: 1rem; +} + +.project-history-detail section { + display: grid; + gap: 0.5rem; +} + +.project-history-detail h5 { + margin: 0; +} + +.project-history-facts { + display: grid; + gap: 0.75rem; + grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); + margin: 0; +} + +.project-history-facts div { + display: grid; + gap: 0.25rem; +} + +.project-history-facts dt { + font-weight: 700; +} + +.project-history-facts dd { + margin: 0; +} + +.project-history-transition { + font-weight: 700; +} + +.project-history-responsibilities, +.project-history-paths { + display: grid; + gap: 0.5rem; + list-style: none; + margin: 0; + padding: 0; +} + +.project-history-responsibilities li, +.project-history-paths li { + border: 1px solid var(--border-color, #d7dde5); + border-radius: 0.5rem; + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + padding: 0.75rem; +} + +.project-history-responsibilities li span:not(.project-history-truth) { + flex-basis: 100%; +} + +.project-history-paths p { + flex: 1 1 20rem; + margin: 0; +} + +.project-history-truth { + border: 1px solid currentColor; + border-radius: 999px; + font-size: 0.8rem; + padding: 0.1rem 0.5rem; +} + +.project-history-exact-values summary { + cursor: pointer; + font-weight: 700; +} + +.project-history-table-scroll { + overflow-x: auto; + padding-top: 0.75rem; +} + +.project-history-table-scroll table { + border-collapse: collapse; + min-width: 54rem; + width: 100%; +} + +.project-history-table-scroll th, +.project-history-table-scroll td { + border: 1px solid var(--border-color, #c8d0da); + padding: 0.5rem; + text-align: start; + vertical-align: top; +} + +@media (max-width: 48rem) { + .project-history-tabs { + grid-auto-flow: row; + grid-auto-rows: auto; + overflow: visible; + padding: 0; + } + + .project-history-tabs::before { + border-inline-start: 2px solid var(--border-color, #9aa4b2); + border-top: 0; + inset-block: 1rem; + inset-inline-start: 0.75rem; + } + + .project-history-tab { + grid-template-columns: 1.5rem minmax(5rem, auto) 1fr; + justify-items: start; + min-height: auto; + padding: 0.5rem 0.5rem 0.5rem 0; + text-align: start; + } + + .project-history-tab > span:last-child { + grid-column: 3; + } + + .project-history-header, + .project-history-detail-heading { + align-items: stretch; + flex-direction: column; + } +} + +@media print { + .project-history-tabs, + .project-history-detail-heading button { + display: none; + } + + .project-history-exact-values, + .project-history-exact-values > * { + display: block !important; + } + + .project-history-table-scroll { + overflow: visible; + } + + .project-history-table-scroll table { + min-width: 0; + } +} diff --git a/frontend/src/components/ProjectHistoryTimeline.stories.tsx b/frontend/src/components/ProjectHistoryTimeline.stories.tsx new file mode 100644 index 000000000..ca6b1cb38 --- /dev/null +++ b/frontend/src/components/ProjectHistoryTimeline.stories.tsx @@ -0,0 +1,97 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import type { ProjectHistoryProjection } from "../projectHistory"; +import { ProjectHistoryTimeline } from "./ProjectHistoryTimeline"; + +const event = ( + eventId: string, + title: string, + type: string, + occurredAt: string, + transition: "continuous" | "handoff" | "assignment_gap" | null, + actorName?: string, +) => ({ + event_id: eventId, + source_post_id: `post-${eventId}`, + event_title: title, + event_type_code: type, + event_type_basis_code: "display_classification" as const, + occurred_at: occurredAt, + time_basis_code: "document_time" as const, + voc_type_code: eventId === "voc" ? "voc" : "vom", + source_stage_code: null, + source_detail_state_code: null, + project_matches: [], + observed_responsibilities: actorName + ? [ + { + actor_key: `actor:${actorName}`, + actor_name: actorName, + actor_type_code: "prov_person", + affiliated_organization_name: "Demo Corp", + responsibility: `Own ${title.toLowerCase()}`, + truth_status_code: "observed" as const, + provenance: "post_summary_role" as const, + }, + ] + : [], + responsibility_transition_code: transition, + related_prior_paths: [], +}); + +const projection: ProjectHistoryProjection = { + contract_version: 1, + project_key: "P-100", + normalized_project_key: "p-100", + project_name: "Northridge renewal", + focus_event_id: "voc", + time_basis_code: "document_time", + event_count: 5, + distinct_observed_actor_count: 3, + truncated: false, + events: [ + event("award", "Contract awarded", "contract_awarded", "2022-03-11T09:00:00Z", null, "Ada West"), + event( + "spec", + "Specification revision requested", + "specification_changed", + "2023-06-15T09:00:00Z", + "continuous", + "Ada West", + ), + event("delivery", "Delivery confirmed", "delivered", "2024-02-20T09:00:00Z", "handoff", "Priya Nair"), + event("voc", "VOC received", "voc_received", "2026-07-30T09:00:00Z", "assignment_gap"), + event("rebid", "Rebid started", "rebid_started", "2026-08-10T09:00:00Z", "assignment_gap", "Bid team"), + ], +}; + +projection.events[3].related_prior_paths = [ + { + source_event_id: "award", + target_event_id: "voc", + event_ids: ["award", "spec", "delivery", "voc"], + edges: [ + { parent_event_id: "award", child_event_id: "spec", fused_score: 0.91 }, + { parent_event_id: "spec", child_event_id: "delivery", fused_score: 0.82 }, + { parent_event_id: "delivery", child_event_id: "voc", fused_score: 0.73 }, + ], + minimum_fused_score: 0.73, + truth_status_code: "inferred", + source_relation_code: "post_lineage_edge", + provenance: "post_lineage_edge.fused_score", + }, +]; + +const meta = { + title: "Buyer/Project History Timeline", + component: ProjectHistoryTimeline, + args: { + projection, + onOpenPost: () => undefined, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const AwardToRebid: Story = {}; diff --git a/frontend/src/components/ProjectHistoryTimeline.tsx b/frontend/src/components/ProjectHistoryTimeline.tsx new file mode 100644 index 000000000..f222e0ab2 --- /dev/null +++ b/frontend/src/components/ProjectHistoryTimeline.tsx @@ -0,0 +1,292 @@ +import { useRef, useState, type KeyboardEvent } from "react"; + +import { useLocale } from "../i18n"; +import { + type ProjectHistoryEvent, + type ProjectHistoryProjection, + projectHistoryEventTypeLabel, + projectHistoryText, + projectHistoryTransitionLabel, +} from "../projectHistory"; +import "./ProjectHistoryTimeline.css"; + +function formatDate(value: string): string { + const parsed = new Date(value); + return Number.isNaN(parsed.valueOf()) ? value : parsed.toISOString().slice(0, 10); +} + +function minimumPathScore(event: ProjectHistoryEvent): number | null { + if (event.related_prior_paths.length === 0) return null; + return Math.min(...event.related_prior_paths.map((path) => path.minimum_fused_score)); +} + +export function ProjectHistoryTimeline({ + projection, + onOpenPost, +}: { + projection: ProjectHistoryProjection; + onOpenPost: (postId: string) => void; +}) { + const locale = useLocale(); + const initialEvent = + projection.events.find((event) => event.event_id === projection.focus_event_id) ?? + projection.events[0]; + const [selectedEventId, setSelectedEventId] = useState(initialEvent?.event_id ?? ""); + const tabRefs = useRef>([]); + const eventById = new Map(projection.events.map((event) => [event.event_id, event])); + const selectedEvent = eventById.get(selectedEventId) ?? initialEvent; + + function selectAt(index: number) { + const bounded = Math.max(0, Math.min(index, projection.events.length - 1)); + const event = projection.events[bounded]; + if (!event) return; + setSelectedEventId(event.event_id); + tabRefs.current[bounded]?.focus(); + } + + function handleTabKey(event: KeyboardEvent, index: number) { + let target: number | null = null; + switch (event.key) { + case "ArrowLeft": + case "ArrowUp": + target = index === 0 ? projection.events.length - 1 : index - 1; + break; + case "ArrowRight": + case "ArrowDown": + target = index === projection.events.length - 1 ? 0 : index + 1; + break; + case "Home": + target = 0; + break; + case "End": + target = projection.events.length - 1; + break; + default: + return; + } + event.preventDefault(); + selectAt(target); + } + + const selectedPanelId = `project-history-panel-${projection.normalized_project_key.replace(/[^a-z0-9_-]+/g, "-")}`; + + return ( +
+
+
+

{projection.project_name}

+

{projectHistoryText(locale, "heading")}

+
+

+ {projectHistoryText(locale, "summaryCounts", { + events: projection.event_count, + actors: projection.distinct_observed_actor_count, + })} +

+
+ +

{projectHistoryText(locale, "documentTime")}

+ {projection.truncated ? ( +

+ {projectHistoryText(locale, "truncated")} +

+ ) : null} + +
+ {projection.events.map((event, index) => { + const selected = event.event_id === selectedEvent?.event_id; + const current = event.event_id === projection.focus_event_id; + return ( + + ); + })} +
+ + {selectedEvent ? ( +
+
+
+

{projectHistoryText(locale, "eventDetail")}

+

{selectedEvent.event_title}

+
+ +
+ +
+
+
{projectHistoryText(locale, "eventDate")}
+
{formatDate(selectedEvent.occurred_at)}
+
+
+
{projectHistoryText(locale, "eventType")}
+
{projectHistoryEventTypeLabel(locale, selectedEvent.event_type_code)}
+
+ {selectedEvent.responsibility_transition_code ? ( +
+
{projectHistoryText(locale, "columnTransition")}
+
+ {projectHistoryTransitionLabel( + locale, + selectedEvent.responsibility_transition_code, + )} +
+
+ ) : null} +
+ +
+
+ {projectHistoryText(locale, "responsibilityEvidence")} +
+ {selectedEvent.observed_responsibilities.length > 0 ? ( +
    + {selectedEvent.observed_responsibilities.map((responsibility) => ( +
  • + {responsibility.actor_name} + {responsibility.affiliated_organization_name + ? ` · ${responsibility.affiliated_organization_name}` + : ""} + {responsibility.responsibility} + + {projectHistoryText(locale, "observed")} + +
  • + ))} +
+ ) : ( +

{projectHistoryText(locale, "noResponsibilityEvidence")}

+ )} +
+ +
+
{projectHistoryText(locale, "priorHistory")}
+ {selectedEvent.related_prior_paths.length > 0 ? ( +
    + {selectedEvent.related_prior_paths.map((path) => ( +
  • +

    + {path.event_ids + .map((eventId) => eventById.get(eventId)?.event_title ?? eventId) + .join(" → ")} +

    + {path.minimum_fused_score.toFixed(3)} + + {projectHistoryText(locale, "inferred")} + +
  • + ))} +
+ ) : ( +

{projectHistoryText(locale, "noPriorHistory")}

+ )} +

+ {projectHistoryText(locale, "inferredBoundary")} +

+
+ + {selectedEvent.project_matches.length > 0 ? ( +
+
+ {projectHistoryText(locale, "projectEvidence")} +
+
    + {selectedEvent.project_matches.map((match) => ( +
  • + {match.matched_value} · {match.provenance} ·{" "} + {projectHistoryText(locale, match.truth_status_code)} +
  • + ))} +
+
+ ) : null} +
+ ) : null} + +
+ {projectHistoryText(locale, "exactValues")} +
+ + + + + + + + + + + + + {projection.events.map((event) => { + const pathScore = minimumPathScore(event); + return ( + + + + + + + + + ); + })} + +
{projectHistoryText(locale, "columnDate")}{projectHistoryText(locale, "columnEvent")}{projectHistoryText(locale, "columnType")}{projectHistoryText(locale, "columnTransition")}{projectHistoryText(locale, "columnActors")}{projectHistoryText(locale, "columnPathScore")}
{formatDate(event.occurred_at)}{event.event_title}{projectHistoryEventTypeLabel(locale, event.event_type_code)} + {projectHistoryTransitionLabel(locale, event.responsibility_transition_code)} + + {event.observed_responsibilities.length > 0 + ? event.observed_responsibilities.map((row) => row.actor_name).join(", ") + : projectHistoryText(locale, "notApplicable")} + + {pathScore === null + ? projectHistoryText(locale, "notApplicable") + : pathScore.toFixed(3)} +
+
+
+
+ ); +} diff --git a/frontend/src/projectHistory.ts b/frontend/src/projectHistory.ts new file mode 100644 index 000000000..03cb823f6 --- /dev/null +++ b/frontend/src/projectHistory.ts @@ -0,0 +1,391 @@ +import type { ProjectEvidence } from "./api"; +import type { Locale } from "./i18n"; + +export type ProjectHistoryTruthStatus = "observed" | "inferred"; +export type ResponsibilityTransitionCode = "continuous" | "handoff" | "assignment_gap"; + +export interface ProjectHistoryMatch { + match_kind_code: string; + matched_value: string; + truth_status_code: ProjectHistoryTruthStatus; + confidence: number | null; + ontology_iri: string | null; + provenance: string; +} + +export interface ProjectHistoryResponsibility { + actor_key: string; + actor_name: string; + actor_type_code: string; + affiliated_organization_name: string | null; + responsibility: string; + truth_status_code: "observed"; + provenance: "post_summary_role"; +} + +export interface ProjectHistoryPathEdge { + parent_event_id: string; + child_event_id: string; + fused_score: number; +} + +export interface ProjectHistoryPriorPath { + source_event_id: string; + target_event_id: string; + event_ids: string[]; + edges: ProjectHistoryPathEdge[]; + minimum_fused_score: number; + truth_status_code: "inferred"; + source_relation_code: "post_lineage_edge"; + provenance: "post_lineage_edge.fused_score"; +} + +export interface ProjectHistoryEvent { + event_id: string; + source_post_id: string; + event_title: string; + event_type_code: string; + event_type_basis_code: "display_classification"; + occurred_at: string; + time_basis_code: "document_time"; + voc_type_code: string | null; + source_stage_code: string | null; + source_detail_state_code: string | null; + project_matches: ProjectHistoryMatch[]; + observed_responsibilities: ProjectHistoryResponsibility[]; + responsibility_transition_code: ResponsibilityTransitionCode | null; + related_prior_paths: ProjectHistoryPriorPath[]; +} + +export interface ProjectHistoryProjection { + contract_version: 1; + project_key: string; + normalized_project_key: string; + project_name: string; + focus_event_id: string; + time_basis_code: "document_time"; + event_count: number; + distinct_observed_actor_count: number; + truncated: boolean; + events: ProjectHistoryEvent[]; +} + +export interface ProjectEvidenceGroup { + normalizedProjectKey: string; + projectKey: string; + projectName: string; + evidence: ProjectEvidence[]; +} + +function normalizeProjectIdentity(value: string): string { + return value.normalize("NFKC").trim().toLocaleLowerCase("en-US"); +} + +function evidenceOrder(evidence: ProjectEvidence): number { + if (evidence.extraction_method === "source_field_hint") return 0; + if (evidence.resolution_status === "hint_only") return 1; + return 2; +} + +export function groupProjectEvidence(evidence: ProjectEvidence[]): ProjectEvidenceGroup[] { + const groups = new Map(); + for (const item of evidence) { + const normalizedProjectKey = normalizeProjectIdentity(item.project_key || item.project_name); + if (!normalizedProjectKey) continue; + const existing = groups.get(normalizedProjectKey); + if (!existing) { + groups.set(normalizedProjectKey, { + normalizedProjectKey, + projectKey: item.project_key, + projectName: item.project_name, + evidence: [item], + }); + continue; + } + existing.evidence.push(item); + if (evidenceOrder(item) < evidenceOrder(existing.evidence[0])) { + existing.projectKey = item.project_key; + existing.projectName = item.project_name; + } + } + return Array.from(groups.values()) + .map((group) => ({ + ...group, + evidence: [...group.evidence].sort( + (left, right) => + evidenceOrder(left) - evidenceOrder(right) || + left.project_name.localeCompare(right.project_name) || + left.provenance.localeCompare(right.provenance), + ), + })) + .sort((left, right) => left.projectName.localeCompare(right.projectName)); +} + +const MESSAGE_KEYS = [ + "heading", + "summaryCounts", + "documentTime", + "truncated", + "eventDetail", + "eventType", + "eventDate", + "responsibilityEvidence", + "noResponsibilityEvidence", + "continuous", + "handoff", + "assignmentGap", + "priorHistory", + "noPriorHistory", + "inferredBoundary", + "projectEvidence", + "observed", + "inferred", + "openSourceRecord", + "exactValues", + "exactTableLabel", + "columnDate", + "columnEvent", + "columnType", + "columnTransition", + "columnActors", + "columnPathScore", + "notApplicable", + "contractAwarded", + "specificationChanged", + "delivered", + "handoffRecorded", + "vocReceived", + "rebidStarted", + "sourceRecorded", +] as const; + +export const PROJECT_HISTORY_MESSAGE_KEYS = MESSAGE_KEYS; +export type ProjectHistoryMessageKey = (typeof MESSAGE_KEYS)[number]; + +type MessageParams = Record; + +const EN: Record = { + heading: "Project event timeline", + summaryCounts: "{events} events · {actors} observed actors", + documentTime: "Dates use document time; they are not asserted event-occurrence times.", + truncated: "This bounded timeline is truncated. The selected event remains included.", + eventDetail: "Event detail", + eventType: "Display event type", + eventDate: "Document date", + responsibilityEvidence: "Observed responsibility evidence", + noResponsibilityEvidence: "No responsibility evidence is recorded for this event.", + continuous: "Responsibility continued", + handoff: "Responsibility handoff", + assignmentGap: "Assignment evidence gap", + priorHistory: "Related prior history", + noPriorHistory: "No visible prior lineage path is recorded for this event.", + inferredBoundary: "This is inferred related history, not causality or an authoritative assignment record.", + projectEvidence: "Project identity evidence", + observed: "Observed", + inferred: "Inferred", + openSourceRecord: "Open source record: {title}", + exactValues: "Exact values", + exactTableLabel: "Project history exact values", + columnDate: "Date", + columnEvent: "Event", + columnType: "Type", + columnTransition: "Responsibility transition", + columnActors: "Observed actors", + columnPathScore: "Minimum lineage score", + notApplicable: "Not applicable", + contractAwarded: "Contract awarded", + specificationChanged: "Specification changed", + delivered: "Delivered", + handoffRecorded: "Handoff recorded", + vocReceived: "VOC received", + rebidStarted: "Rebid started", + sourceRecorded: "Source record", +}; + +const MESSAGES: Record> = { + en: EN, + ko: { + heading: "프로젝트 이벤트 타임라인", + summaryCounts: "이벤트 {events}건 · 관찰된 담당자 {actors}명", + documentTime: "날짜는 문서 시각이며 실제 사건 발생 시각으로 단정하지 않습니다.", + truncated: "이 제한된 타임라인은 일부만 표시합니다. 선택한 이벤트는 계속 포함됩니다.", + eventDetail: "이벤트 상세", + eventType: "표시용 이벤트 유형", + eventDate: "문서 날짜", + responsibilityEvidence: "관찰된 담당 근거", + noResponsibilityEvidence: "이 이벤트에는 기록된 담당 근거가 없습니다.", + continuous: "담당 유지", + handoff: "담당 변경", + assignmentGap: "담당 근거 공백", + priorHistory: "관련 과거 이력", + noPriorHistory: "이 이벤트로 이어지는 공개 가능한 이전 계보가 없습니다.", + inferredBoundary: "이는 추론된 관련 이력이며 인과관계나 권위 있는 인사 배정 기록이 아닙니다.", + projectEvidence: "프로젝트 식별 근거", + observed: "관찰됨", + inferred: "추론됨", + openSourceRecord: "원천 기록 열기: {title}", + exactValues: "정확한 값", + exactTableLabel: "프로젝트 이력 정확한 값", + columnDate: "날짜", + columnEvent: "이벤트", + columnType: "유형", + columnTransition: "담당 변화", + columnActors: "관찰된 담당자", + columnPathScore: "최소 계보 점수", + notApplicable: "해당 없음", + contractAwarded: "수주 확정", + specificationChanged: "사양 변경", + delivered: "납품", + handoffRecorded: "인수인계 기록", + vocReceived: "VOC 접수", + rebidStarted: "재입찰 시작", + sourceRecorded: "원천 기록", + }, + zh: { + heading: "项目事件时间线", + summaryCounts: "{events} 个事件 · {actors} 名已观察责任人", + documentTime: "日期采用文档时间,不声称为事件实际发生时间。", + truncated: "此有界时间线已截断,但所选事件仍保留。", + eventDetail: "事件详情", + eventType: "显示事件类型", + eventDate: "文档日期", + responsibilityEvidence: "已观察的责任证据", + noResponsibilityEvidence: "此事件没有记录责任证据。", + continuous: "责任持续", + handoff: "责任交接", + assignmentGap: "责任证据缺口", + priorHistory: "相关既往历史", + noPriorHistory: "此事件没有可见的既往谱系路径。", + inferredBoundary: "这是推断的相关历史,并非因果关系或权威任命记录。", + projectEvidence: "项目身份依据", + observed: "已观察", + inferred: "已推断", + openSourceRecord: "打开源记录:{title}", + exactValues: "精确值", + exactTableLabel: "项目历史精确值", + columnDate: "日期", + columnEvent: "事件", + columnType: "类型", + columnTransition: "责任变化", + columnActors: "已观察责任人", + columnPathScore: "最低谱系分数", + notApplicable: "不适用", + contractAwarded: "合同授予", + specificationChanged: "规格变更", + delivered: "已交付", + handoffRecorded: "已记录交接", + vocReceived: "收到客户之声", + rebidStarted: "重新投标开始", + sourceRecorded: "源记录", + }, + ja: { + heading: "プロジェクトイベントのタイムライン", + summaryCounts: "イベント {events}件 · 観察された担当者 {actors}名", + documentTime: "日付は文書時刻であり、実際のイベント発生時刻とは断定しません。", + truncated: "この上限付きタイムラインは省略されていますが、選択イベントは保持されます。", + eventDetail: "イベント詳細", + eventType: "表示用イベント種別", + eventDate: "文書日付", + responsibilityEvidence: "観察された担当根拠", + noResponsibilityEvidence: "このイベントには担当根拠が記録されていません。", + continuous: "担当継続", + handoff: "担当引継ぎ", + assignmentGap: "担当根拠の空白", + priorHistory: "関連する過去履歴", + noPriorHistory: "このイベントに至る可視の過去系譜はありません。", + inferredBoundary: "これは推論された関連履歴であり、因果関係や権威ある配属記録ではありません。", + projectEvidence: "プロジェクト識別根拠", + observed: "観察済み", + inferred: "推論済み", + openSourceRecord: "原資料を開く: {title}", + exactValues: "正確な値", + exactTableLabel: "プロジェクト履歴の正確な値", + columnDate: "日付", + columnEvent: "イベント", + columnType: "種別", + columnTransition: "担当変化", + columnActors: "観察担当者", + columnPathScore: "最小系譜スコア", + notApplicable: "該当なし", + contractAwarded: "受注確定", + specificationChanged: "仕様変更", + delivered: "納品", + handoffRecorded: "引継ぎ記録", + vocReceived: "VOC受付", + rebidStarted: "再入札開始", + sourceRecorded: "原資料", + }, + vi: { + heading: "Dòng thời gian sự kiện dự án", + summaryCounts: "{events} sự kiện · {actors} người phụ trách được quan sát", + documentTime: "Ngày dùng thời gian tài liệu, không khẳng định là thời điểm sự kiện thực tế.", + truncated: "Dòng thời gian có giới hạn này đã bị rút gọn nhưng vẫn giữ sự kiện đang chọn.", + eventDetail: "Chi tiết sự kiện", + eventType: "Loại sự kiện hiển thị", + eventDate: "Ngày tài liệu", + responsibilityEvidence: "Bằng chứng trách nhiệm quan sát được", + noResponsibilityEvidence: "Không có bằng chứng trách nhiệm được ghi cho sự kiện này.", + continuous: "Trách nhiệm được duy trì", + handoff: "Bàn giao trách nhiệm", + assignmentGap: "Khoảng trống bằng chứng phân công", + priorHistory: "Lịch sử trước đó có liên quan", + noPriorHistory: "Không có đường dẫn lịch sử trước đó khả kiến cho sự kiện này.", + inferredBoundary: "Đây là lịch sử liên quan được suy luận, không phải quan hệ nhân quả hay hồ sơ phân công có thẩm quyền.", + projectEvidence: "Bằng chứng nhận dạng dự án", + observed: "Đã quan sát", + inferred: "Đã suy luận", + openSourceRecord: "Mở bản ghi nguồn: {title}", + exactValues: "Giá trị chính xác", + exactTableLabel: "Giá trị chính xác của lịch sử dự án", + columnDate: "Ngày", + columnEvent: "Sự kiện", + columnType: "Loại", + columnTransition: "Thay đổi trách nhiệm", + columnActors: "Người phụ trách được quan sát", + columnPathScore: "Điểm dòng dõi tối thiểu", + notApplicable: "Không áp dụng", + contractAwarded: "Đã trao hợp đồng", + specificationChanged: "Đã thay đổi đặc tả", + delivered: "Đã bàn giao sản phẩm", + handoffRecorded: "Đã ghi nhận bàn giao", + vocReceived: "Đã nhận ý kiến khách hàng", + rebidStarted: "Đã bắt đầu đấu thầu lại", + sourceRecorded: "Bản ghi nguồn", + }, +}; + +export function projectHistoryText( + locale: Locale, + key: ProjectHistoryMessageKey, + params: MessageParams = {}, +): string { + let value = MESSAGES[locale][key]; + for (const [name, replacement] of Object.entries(params)) { + value = value.replaceAll(`{${name}}`, String(replacement)); + } + return value; +} + +export function projectHistoryEventTypeLabel(locale: Locale, code: string): string { + const keyByCode: Record = { + contract_awarded: "contractAwarded", + specification_changed: "specificationChanged", + delivered: "delivered", + handoff_recorded: "handoffRecorded", + voc_received: "vocReceived", + rebid_started: "rebidStarted", + source_recorded: "sourceRecorded", + }; + const key = keyByCode[code]; + return key ? projectHistoryText(locale, key) : code; +} + +export function projectHistoryTransitionLabel( + locale: Locale, + code: ResponsibilityTransitionCode | null, +): string { + if (code === "continuous") return projectHistoryText(locale, "continuous"); + if (code === "handoff") return projectHistoryText(locale, "handoff"); + if (code === "assignment_gap") return projectHistoryText(locale, "assignmentGap"); + return projectHistoryText(locale, "notApplicable"); +} diff --git a/lineageweave/project_history.py b/lineageweave/project_history.py new file mode 100644 index 000000000..b594c3d0f --- /dev/null +++ b/lineageweave/project_history.py @@ -0,0 +1,396 @@ +"""Build evidence-bound project histories from already-authorized rows. + +The module is deliberately storage-agnostic. Callers must apply RBAC, ABAC, +source eligibility, and knowledge-cutoff filtering before invoking it. It then +orders visible source records, keeps explicit and semantic project matches +separate, projects observed responsibility evidence, and explains persisted +lineage paths without promoting them to causal or authoritative facts. +""" + +from __future__ import annotations + +from collections import deque +from collections.abc import Mapping, Sequence +from datetime import datetime, timezone +from decimal import Decimal +from typing import Any +from unicodedata import normalize + +PROJECT_HISTORY_CONTRACT_VERSION = 1 +PROJECT_HISTORY_TIME_BASIS = "document_time" +PROJECT_HISTORY_MAX_DEPTH = 8 +PROJECT_HISTORY_MAX_PATHS_PER_EVENT = 32 + +_EVENT_PATTERNS: tuple[tuple[str, tuple[str, ...]], ...] = ( + ("rebid_started", ("rebid", "re-bid", "retender", "re-tender", "재입찰")), + ( + "handoff_recorded", + ("handoff", "hand-off", "transferred ownership", "operational transfer", "인수인계"), + ), + ( + "specification_changed", + ( + "specification change", + "specification revision", + "revised specification", + "spec revision", + "사양 변경", + "사양변경", + ), + ), + ( + "delivered", + ( + "delivery confirmed", + "delivery completed", + "delivered", + "shipment completed", + "납품 완료", + "납품완료", + ), + ), + ( + "contract_awarded", + ( + "contract awarded", + "award confirmed", + "order confirmation", + "purchase order received", + "수주 확정", + "수주확정", + ), + ), +) +_VOC_CODES = frozenset({"voc", "vocc", "voco", "vom", "vop"}) + + +def normalize_project_key(value: str) -> str: + """Return the exact project-identity comparison key. + + Compatibility normalization lets full-width and compatibility forms match + while preserving a deterministic, locale-neutral lower-case comparison. + Empty values are rejected rather than becoming a match-all key. + """ + + normalized = normalize("NFKC", value).strip().lower() + if not normalized: + raise ValueError("project key must not be empty") + if len(normalized.encode("utf-8")) > 256: + raise ValueError("project key exceeds 256 UTF-8 bytes") + return normalized + + +def classify_project_event( + *, + title: str, + source_stage_code: str | None, + source_detail_state_code: str | None, + voc_type_code: str | None, + is_focus: bool, +) -> str: + """Classify a display event from explicit source text and codes. + + The code is presentation metadata only. It never creates a new event or + changes the truth status of the source record. + """ + + text = " ".join( + part.strip().lower() + for part in (title, source_stage_code or "", source_detail_state_code or "") + if part.strip() + ) + for event_code, patterns in _EVENT_PATTERNS: + if any(pattern in text for pattern in patterns): + return event_code + if is_focus and (voc_type_code or "").strip().lower() in _VOC_CODES: + return "voc_received" + return "source_recorded" + + +def responsibility_transition_code( + previous_actor_keys: Sequence[str], current_actor_keys: Sequence[str] +) -> str: + """Classify adjacent observed responsibility evidence. + + Missing evidence on either event is an ``assignment_gap``. Equal non-empty + actor sets are ``continuous``; different non-empty sets are ``handoff``. + The result describes document evidence, not an HR assignment fact. + """ + + previous = frozenset(key for key in previous_actor_keys if key) + current = frozenset(key for key in current_actor_keys if key) + if not previous or not current: + return "assignment_gap" + if previous == current: + return "continuous" + return "handoff" + + +def _as_utc(value: datetime) -> str: + """Serialize a datetime as canonical UTC RFC 3339 text.""" + + aware = value if value.tzinfo is not None else value.replace(tzinfo=timezone.utc) + return aware.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") + + +def _actor_key(role: Mapping[str, Any]) -> str: + """Return a stable key for one observed R&R actor.""" + + catalog_fields = ( + ("person", role.get("cataloged_person_id")), + ("team", role.get("cataloged_team_id")), + ("organization", role.get("cataloged_corporate_entity_id")), + ) + for prefix, value in catalog_fields: + if value: + return f"{prefix}:{value}" + parts = ( + str(role.get("actor_type_code") or "unknown"), + str(role.get("actor_name") or ""), + str(role.get("affiliated_organization_name") or ""), + ) + return "text:" + "\u001f".join(normalize("NFKC", part).strip().lower() for part in parts) + + +def _score(value: object) -> float: + """Return a finite JSON-compatible lineage score.""" + + if isinstance(value, bool) or not isinstance(value, (int, float, Decimal)): + raise ValueError("lineage score must be numeric") + result = float(value) + if result != result or result in (float("inf"), float("-inf")): + raise ValueError("lineage score must be finite") + return result + + +def _prior_paths( + ordered_event_ids: Sequence[str], + edge_rows: Sequence[Mapping[str, Any]], + *, + maximum_depth: int, + maximum_paths_per_event: int, +) -> dict[str, list[dict[str, Any]]]: + """Return one deterministic shortest visible path per prior event.""" + + event_index = {event_id: index for index, event_id in enumerate(ordered_event_ids)} + reverse_edges: dict[str, list[dict[str, Any]]] = {event_id: [] for event_id in ordered_event_ids} + for row in edge_rows: + parent = str(row["parent_post_id"]) + child = str(row["child_post_id"]) + if parent not in event_index or child not in event_index: + continue + if event_index[parent] >= event_index[child]: + continue + reverse_edges[child].append( + { + "parent_event_id": parent, + "child_event_id": child, + "fused_score": _score(row["fused_score"]), + } + ) + for edges in reverse_edges.values(): + edges.sort(key=lambda edge: (event_index[edge["parent_event_id"]], edge["parent_event_id"])) + + result: dict[str, list[dict[str, Any]]] = {} + for target in ordered_event_ids: + queue: deque[tuple[str, tuple[str, ...], tuple[dict[str, Any], ...]]] = deque( + [(target, (target,), ())] + ) + best_depth = {target: 0} + paths: list[dict[str, Any]] = [] + while queue and len(paths) < maximum_paths_per_event: + current, reverse_event_path, reverse_edge_path = queue.popleft() + depth = len(reverse_edge_path) + if depth >= maximum_depth: + continue + for edge in reverse_edges[current]: + parent = edge["parent_event_id"] + if parent in reverse_event_path: + continue + next_depth = depth + 1 + if best_depth.get(parent, maximum_depth + 1) <= next_depth: + continue + best_depth[parent] = next_depth + next_events = reverse_event_path + (parent,) + next_edges = reverse_edge_path + (edge,) + ordered_events = list(reversed(next_events)) + ordered_edges = list(reversed(next_edges)) + paths.append( + { + "source_event_id": parent, + "target_event_id": target, + "event_ids": ordered_events, + "edges": ordered_edges, + "minimum_fused_score": min(item["fused_score"] for item in ordered_edges), + "truth_status_code": "inferred", + "source_relation_code": "post_lineage_edge", + "provenance": "post_lineage_edge.fused_score", + } + ) + queue.append((parent, next_events, next_edges)) + if len(paths) >= maximum_paths_per_event: + break + paths.sort( + key=lambda path: ( + len(path["edges"]), + event_index[path["source_event_id"]], + tuple(path["event_ids"]), + ) + ) + result[target] = paths + return result + + +def build_project_history_projection( + *, + project_key: str, + focus_event_id: str | None, + event_rows: Sequence[Mapping[str, Any]], + match_rows: Sequence[Mapping[str, Any]], + role_rows: Sequence[Mapping[str, Any]], + edge_rows: Sequence[Mapping[str, Any]], + truncated: bool = False, + maximum_depth: int = PROJECT_HISTORY_MAX_DEPTH, + maximum_paths_per_event: int = PROJECT_HISTORY_MAX_PATHS_PER_EVENT, +) -> dict[str, Any]: + """Build the versioned Buyer project-history projection. + + All input rows must already be visible, eligible, and within the requested + knowledge cutoff. Duplicate event rows are collapsed by ``post_id`` and the + final chronology is stable on ``(created_at, post_id)``. + """ + + normalized_key = normalize_project_key(project_key) + if maximum_depth < 1 or maximum_depth > PROJECT_HISTORY_MAX_DEPTH: + raise ValueError("maximum_depth is outside the supported bound") + if maximum_paths_per_event < 1 or maximum_paths_per_event > PROJECT_HISTORY_MAX_PATHS_PER_EVENT: + raise ValueError("maximum_paths_per_event is outside the supported bound") + + deduplicated: dict[str, Mapping[str, Any]] = {} + for row in event_rows: + event_id = str(row["post_id"]) + current = deduplicated.get(event_id) + if current is None or (row["created_at"], event_id) < (current["created_at"], event_id): + deduplicated[event_id] = row + ordered_rows = sorted(deduplicated.values(), key=lambda row: (row["created_at"], str(row["post_id"]))) + if not ordered_rows: + raise ValueError("project history requires at least one visible event") + ordered_ids = [str(row["post_id"]) for row in ordered_rows] + effective_focus = focus_event_id or ordered_ids[-1] + if effective_focus not in set(ordered_ids): + raise ValueError("focus event is not in the visible project history") + + matches_by_event: dict[str, list[dict[str, Any]]] = {event_id: [] for event_id in ordered_ids} + display_names: list[str] = [] + seen_matches: set[tuple[str, str, str]] = set() + for row in match_rows: + event_id = str(row["post_id"]) + if event_id not in matches_by_event: + continue + matched_value = str(row["matched_value"]) + if normalize_project_key(matched_value) != normalized_key: + continue + kind = str(row["match_kind_code"]) + key = (event_id, kind, matched_value) + if key in seen_matches: + continue + seen_matches.add(key) + confidence = row.get("confidence") + if confidence is not None: + confidence = _score(confidence) + truth = "observed" if kind.startswith("source_") else "inferred" + matches_by_event[event_id].append( + { + "match_kind_code": kind, + "matched_value": matched_value, + "truth_status_code": truth, + "confidence": confidence, + "ontology_iri": row.get("ontology_iri"), + "provenance": str(row["provenance"]), + } + ) + if kind.endswith("name"): + display_names.append(matched_value) + for matches in matches_by_event.values(): + matches.sort(key=lambda item: (item["truth_status_code"], item["match_kind_code"], item["matched_value"])) + + roles_by_event: dict[str, list[dict[str, Any]]] = {event_id: [] for event_id in ordered_ids} + actor_keys_by_event: dict[str, list[str]] = {event_id: [] for event_id in ordered_ids} + distinct_actor_keys: set[str] = set() + for row in role_rows: + event_id = str(row["post_id"]) + if event_id not in roles_by_event: + continue + actor_key = _actor_key(row) + distinct_actor_keys.add(actor_key) + actor_keys_by_event[event_id].append(actor_key) + roles_by_event[event_id].append( + { + "actor_key": actor_key, + "actor_name": str(row["actor_name"]), + "actor_type_code": str(row["actor_type_code"]), + "affiliated_organization_name": row.get("affiliated_organization_name"), + "responsibility": str(row["responsibility"]), + "truth_status_code": "observed", + "provenance": "post_summary_role", + } + ) + for roles in roles_by_event.values(): + roles.sort(key=lambda role: (role["actor_type_code"], role["actor_name"], role["actor_key"])) + + paths_by_event = _prior_paths( + ordered_ids, + edge_rows, + maximum_depth=maximum_depth, + maximum_paths_per_event=maximum_paths_per_event, + ) + + events: list[dict[str, Any]] = [] + previous_actor_keys: Sequence[str] | None = None + for row in ordered_rows: + event_id = str(row["post_id"]) + current_actor_keys = actor_keys_by_event[event_id] + transition = ( + None + if previous_actor_keys is None + else responsibility_transition_code(previous_actor_keys, current_actor_keys) + ) + events.append( + { + "event_id": event_id, + "source_post_id": event_id, + "event_title": str(row["post_title"]), + "event_type_code": classify_project_event( + title=str(row["post_title"]), + source_stage_code=row.get("source_stage_code"), + source_detail_state_code=row.get("source_detail_state_code"), + voc_type_code=row.get("voc_type_code"), + is_focus=event_id == effective_focus, + ), + "event_type_basis_code": "display_classification", + "occurred_at": _as_utc(row["created_at"]), + "time_basis_code": PROJECT_HISTORY_TIME_BASIS, + "voc_type_code": row.get("voc_type_code"), + "source_stage_code": row.get("source_stage_code"), + "source_detail_state_code": row.get("source_detail_state_code"), + "project_matches": matches_by_event[event_id], + "observed_responsibilities": roles_by_event[event_id], + "responsibility_transition_code": transition, + "related_prior_paths": paths_by_event[event_id], + } + ) + previous_actor_keys = current_actor_keys + + project_name = display_names[0] if display_names else project_key.strip() + return { + "contract_version": PROJECT_HISTORY_CONTRACT_VERSION, + "project_key": project_key.strip(), + "normalized_project_key": normalized_key, + "project_name": project_name, + "focus_event_id": effective_focus, + "time_basis_code": PROJECT_HISTORY_TIME_BASIS, + "event_count": len(events), + "distinct_observed_actor_count": len(distinct_actor_keys), + "truncated": bool(truncated), + "events": events, + } From 103b99a4bd775e7b9b8a4f1c6c042396ad5a1028 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 22:57:07 -0700 Subject: [PATCH 05/26] fix(projects): keep lifecycle projection evidence-bound --- lineageweave/project_history.py | 93 +++++++++++++++++++++------------ 1 file changed, 61 insertions(+), 32 deletions(-) diff --git a/lineageweave/project_history.py b/lineageweave/project_history.py index b594c3d0f..49eb01af2 100644 --- a/lineageweave/project_history.py +++ b/lineageweave/project_history.py @@ -1,10 +1,10 @@ """Build evidence-bound project histories from already-authorized rows. -The module is deliberately storage-agnostic. Callers must apply RBAC, ABAC, -source eligibility, and knowledge-cutoff filtering before invoking it. It then -orders visible source records, keeps explicit and semantic project matches -separate, projects observed responsibility evidence, and explains persisted -lineage paths without promoting them to causal or authoritative facts. +Callers must apply RBAC, ABAC, source eligibility, and knowledge-cutoff +filtering before invoking this module. The pure projection layer then orders +visible source records, preserves explicit and semantic project evidence, +compares observed responsibility evidence, and exposes persisted lineage as +related history without promoting it to causality or an HR assignment ledger. """ from __future__ import annotations @@ -17,7 +17,7 @@ from unicodedata import normalize PROJECT_HISTORY_CONTRACT_VERSION = 1 -PROJECT_HISTORY_TIME_BASIS = "document_time" +PROJECT_HISTORY_TIME_BASIS = "source_post_created_at_fallback" PROJECT_HISTORY_MAX_DEPTH = 8 PROJECT_HISTORY_MAX_PATHS_PER_EVENT = 32 @@ -62,14 +62,16 @@ ), ) _VOC_CODES = frozenset({"voc", "vocc", "voco", "vom", "vop"}) +_TRUTH_ORDER = {"observed": 0, "inferred": 1} +_DISPLAY_NAME_ORDER = {"source_project_name": 0, "semantic_project_name": 1} def normalize_project_key(value: str) -> str: """Return the exact project-identity comparison key. - Compatibility normalization lets full-width and compatibility forms match - while preserving a deterministic, locale-neutral lower-case comparison. - Empty values are rejected rather than becoming a match-all key. + Unicode compatibility normalization lets full-width and compatibility + forms match without introducing fuzzy identity. Empty and oversized keys + fail closed. """ normalized = normalize("NFKC", value).strip().lower() @@ -88,12 +90,15 @@ def classify_project_event( voc_type_code: str | None, is_focus: bool, ) -> str: - """Classify a display event from explicit source text and codes. + """Return a non-authoritative display classification for one source row. - The code is presentation metadata only. It never creates a new event or - changes the truth status of the source record. + Explicit title/stage/state markers take precedence. Every already-visible + VOC-family row is labelled as VOC, not only the currently selected row. + ``is_focus`` is retained for contract compatibility but never changes the + truth status or creates an event. """ + del is_focus text = " ".join( part.strip().lower() for part in (title, source_stage_code or "", source_detail_state_code or "") @@ -102,7 +107,7 @@ def classify_project_event( for event_code, patterns in _EVENT_PATTERNS: if any(pattern in text for pattern in patterns): return event_code - if is_focus and (voc_type_code or "").strip().lower() in _VOC_CODES: + if (voc_type_code or "").strip().lower() in _VOC_CODES: return "voc_received" return "source_recorded" @@ -110,11 +115,11 @@ def classify_project_event( def responsibility_transition_code( previous_actor_keys: Sequence[str], current_actor_keys: Sequence[str] ) -> str: - """Classify adjacent observed responsibility evidence. + """Compare adjacent observed responsibility evidence. - Missing evidence on either event is an ``assignment_gap``. Equal non-empty - actor sets are ``continuous``; different non-empty sets are ``handoff``. - The result describes document evidence, not an HR assignment fact. + Missing evidence on either row is an ``assignment_gap`` evidence state, + not proof of an operational or HR vacancy. Equal non-empty actor sets are + continuous; different non-empty sets are a handoff. """ previous = frozenset(key for key in previous_actor_keys if key) @@ -127,14 +132,14 @@ def responsibility_transition_code( def _as_utc(value: datetime) -> str: - """Serialize a datetime as canonical UTC RFC 3339 text.""" + """Serialize a source clock as canonical UTC RFC 3339 text.""" aware = value if value.tzinfo is not None else value.replace(tzinfo=timezone.utc) return aware.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") def _actor_key(role: Mapping[str, Any]) -> str: - """Return a stable key for one observed R&R actor.""" + """Return a stable key for one observed role actor.""" catalog_fields = ( ("person", role.get("cataloged_person_id")), @@ -170,7 +175,7 @@ def _prior_paths( maximum_depth: int, maximum_paths_per_event: int, ) -> dict[str, list[dict[str, Any]]]: - """Return one deterministic shortest visible path per prior event.""" + """Return deterministic shortest visible predecessor paths per event.""" event_index = {event_id: index for index, event_id in enumerate(ordered_event_ids)} reverse_edges: dict[str, list[dict[str, Any]]] = {event_id: [] for event_id in ordered_event_ids} @@ -255,9 +260,9 @@ def build_project_history_projection( ) -> dict[str, Any]: """Build the versioned Buyer project-history projection. - All input rows must already be visible, eligible, and within the requested - knowledge cutoff. Duplicate event rows are collapsed by ``post_id`` and the - final chronology is stable on ``(created_at, post_id)``. + Inputs must already be visible, eligible, and within the requested cutoff. + Duplicate source rows and role rows are collapsed deterministically. An + observed source project name outranks an inferred semantic display name. """ normalized_key = normalize_project_key(project_key) @@ -272,16 +277,20 @@ def build_project_history_projection( current = deduplicated.get(event_id) if current is None or (row["created_at"], event_id) < (current["created_at"], event_id): deduplicated[event_id] = row - ordered_rows = sorted(deduplicated.values(), key=lambda row: (row["created_at"], str(row["post_id"]))) + ordered_rows = sorted( + deduplicated.values(), + key=lambda row: (row["created_at"], str(row["post_id"])), + ) if not ordered_rows: raise ValueError("project history requires at least one visible event") ordered_ids = [str(row["post_id"]) for row in ordered_rows] + event_index = {event_id: index for index, event_id in enumerate(ordered_ids)} effective_focus = focus_event_id or ordered_ids[-1] - if effective_focus not in set(ordered_ids): + if effective_focus not in event_index: raise ValueError("focus event is not in the visible project history") matches_by_event: dict[str, list[dict[str, Any]]] = {event_id: [] for event_id in ordered_ids} - display_names: list[str] = [] + display_names: list[tuple[int, int, str, str]] = [] seen_matches: set[tuple[str, str, str]] = set() for row in match_rows: event_id = str(row["post_id"]) @@ -309,19 +318,38 @@ def build_project_history_projection( "provenance": str(row["provenance"]), } ) - if kind.endswith("name"): - display_names.append(matched_value) + if kind in _DISPLAY_NAME_ORDER: + display_names.append( + ( + _DISPLAY_NAME_ORDER[kind], + event_index[event_id], + normalize_project_key(matched_value), + matched_value, + ) + ) for matches in matches_by_event.values(): - matches.sort(key=lambda item: (item["truth_status_code"], item["match_kind_code"], item["matched_value"])) + matches.sort( + key=lambda item: ( + _TRUTH_ORDER[item["truth_status_code"]], + item["match_kind_code"], + item["matched_value"], + ) + ) roles_by_event: dict[str, list[dict[str, Any]]] = {event_id: [] for event_id in ordered_ids} actor_keys_by_event: dict[str, list[str]] = {event_id: [] for event_id in ordered_ids} distinct_actor_keys: set[str] = set() + seen_roles: set[tuple[str, str, str]] = set() for row in role_rows: event_id = str(row["post_id"]) if event_id not in roles_by_event: continue actor_key = _actor_key(row) + responsibility = str(row["responsibility"]) + role_key = (event_id, actor_key, responsibility) + if role_key in seen_roles: + continue + seen_roles.add(role_key) distinct_actor_keys.add(actor_key) actor_keys_by_event[event_id].append(actor_key) roles_by_event[event_id].append( @@ -330,13 +358,14 @@ def build_project_history_projection( "actor_name": str(row["actor_name"]), "actor_type_code": str(row["actor_type_code"]), "affiliated_organization_name": row.get("affiliated_organization_name"), - "responsibility": str(row["responsibility"]), + "responsibility": responsibility, "truth_status_code": "observed", "provenance": "post_summary_role", } ) - for roles in roles_by_event.values(): + for event_id, roles in roles_by_event.items(): roles.sort(key=lambda role: (role["actor_type_code"], role["actor_name"], role["actor_key"])) + actor_keys_by_event[event_id] = sorted(set(actor_keys_by_event[event_id])) paths_by_event = _prior_paths( ordered_ids, @@ -381,7 +410,7 @@ def build_project_history_projection( ) previous_actor_keys = current_actor_keys - project_name = display_names[0] if display_names else project_key.strip() + project_name = min(display_names)[3] if display_names else project_key.strip() return { "contract_version": PROJECT_HISTORY_CONTRACT_VERSION, "project_key": project_key.strip(), From 29d088aeeb21535cab016367a1ad1a0e583c65ba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 23:00:36 -0700 Subject: [PATCH 06/26] fix(ui): keep project-history selection and tab semantics current --- .../src/components/ProjectHistoryTimeline.tsx | 57 ++++++++++++------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/frontend/src/components/ProjectHistoryTimeline.tsx b/frontend/src/components/ProjectHistoryTimeline.tsx index f222e0ab2..94b53840a 100644 --- a/frontend/src/components/ProjectHistoryTimeline.tsx +++ b/frontend/src/components/ProjectHistoryTimeline.tsx @@ -1,4 +1,4 @@ -import { useRef, useState, type KeyboardEvent } from "react"; +import { useEffect, useId, useRef, useState, type KeyboardEvent } from "react"; import { useLocale } from "../i18n"; import { @@ -20,6 +20,14 @@ function minimumPathScore(event: ProjectHistoryEvent): number | null { return Math.min(...event.related_prior_paths.map((path) => path.minimum_fused_score)); } +function initialEventId(projection: ProjectHistoryProjection): string { + return ( + projection.events.find((event) => event.event_id === projection.focus_event_id)?.event_id ?? + projection.events[0]?.event_id ?? + "" + ); +} + export function ProjectHistoryTimeline({ projection, onOpenPost, @@ -28,13 +36,23 @@ export function ProjectHistoryTimeline({ onOpenPost: (postId: string) => void; }) { const locale = useLocale(); - const initialEvent = - projection.events.find((event) => event.event_id === projection.focus_event_id) ?? - projection.events[0]; - const [selectedEventId, setSelectedEventId] = useState(initialEvent?.event_id ?? ""); + const instanceId = useId(); + const panelId = `${instanceId}-project-history-panel`; + const headingId = `${instanceId}-project-history-heading`; + const [selectedEventId, setSelectedEventId] = useState(() => initialEventId(projection)); const tabRefs = useRef>([]); const eventById = new Map(projection.events.map((event) => [event.event_id, event])); - const selectedEvent = eventById.get(selectedEventId) ?? initialEvent; + const selectedEvent = + eventById.get(selectedEventId) ?? eventById.get(initialEventId(projection)) ?? null; + const selectedIndex = selectedEvent + ? projection.events.findIndex((event) => event.event_id === selectedEvent.event_id) + : -1; + const selectedTabId = selectedIndex >= 0 ? `${instanceId}-project-history-tab-${selectedIndex}` : undefined; + + useEffect(() => { + setSelectedEventId(initialEventId(projection)); + tabRefs.current = []; + }, [projection.normalized_project_key, projection.focus_event_id, projection.events]); function selectAt(index: number) { const bounded = Math.max(0, Math.min(index, projection.events.length - 1)); @@ -68,14 +86,12 @@ export function ProjectHistoryTimeline({ selectAt(target); } - const selectedPanelId = `project-history-panel-${projection.normalized_project_key.replace(/[^a-z0-9_-]+/g, "-")}`; - return ( -
+

{projection.project_name}

-

{projectHistoryText(locale, "heading")}

+

{projectHistoryText(locale, "heading")}

{projectHistoryText(locale, "summaryCounts", { @@ -96,13 +112,14 @@ export function ProjectHistoryTimeline({ className="project-history-tabs" role="tablist" aria-label={projectHistoryText(locale, "heading")} - aria-orientation="horizontal" > {projection.events.map((event, index) => { const selected = event.event_id === selectedEvent?.event_id; const current = event.event_id === projection.focus_event_id; + const tabId = `${instanceId}-project-history-tab-${index}`; return ( + ))} + + {projectHistoryError ?

{projectHistoryError}

: null} + {projectHistory ? ( + onSelectPost?.(sourcePostId)} + /> + ) : null} +
+ ) : null} +

{t("Summary")}

{summary ? ( diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 2c812ccaa..80fc75d02 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -465,6 +465,20 @@ export class BackendError extends Error { } } +export function fetchProjectHistory( + accessToken: string, + projectKey: string, + focusPostId: string, + knowledgeCutoff?: string | null, +): Promise { + const query = new URLSearchParams({ focus_post_id: focusPostId }); + if (knowledgeCutoff) query.set("knowledge_cutoff", knowledgeCutoff); + return backendFetch( + `/api/projects/${encodeURIComponent(projectKey)}/history?${query.toString()}`, + accessToken, + ); +} + async function backendFetch( path: string, accessToken: string, diff --git a/frontend/src/components/ProjectHistoryTimeline.stories.tsx b/frontend/src/components/ProjectHistoryTimeline.stories.tsx index ca6b1cb38..6395d0aa0 100644 --- a/frontend/src/components/ProjectHistoryTimeline.stories.tsx +++ b/frontend/src/components/ProjectHistoryTimeline.stories.tsx @@ -6,7 +6,6 @@ import { ProjectHistoryTimeline } from "./ProjectHistoryTimeline"; const event = ( eventId: string, title: string, - type: string, occurredAt: string, transition: "continuous" | "handoff" | "assignment_gap" | null, actorName?: string, @@ -14,11 +13,11 @@ const event = ( event_id: eventId, source_post_id: `post-${eventId}`, event_title: title, - event_type_code: type, - event_type_basis_code: "display_classification" as const, + event_type_code: eventId === "voc" ? "voc_received" : "source_recorded", + event_type_basis_code: "controlled_source_code" as const, occurred_at: occurredAt, time_basis_code: "document_time" as const, - voc_type_code: eventId === "voc" ? "voc" : "vom", + voc_type_code: eventId === "voc" ? "voc" : null, source_stage_code: null, source_detail_state_code: null, project_matches: [], @@ -50,18 +49,17 @@ const projection: ProjectHistoryProjection = { distinct_observed_actor_count: 3, truncated: false, events: [ - event("award", "Contract awarded", "contract_awarded", "2022-03-11T09:00:00Z", null, "Ada West"), + event("award", "Contract awarded", "2022-03-11T09:00:00Z", null, "Ada West"), event( "spec", "Specification revision requested", - "specification_changed", "2023-06-15T09:00:00Z", "continuous", "Ada West", ), - event("delivery", "Delivery confirmed", "delivered", "2024-02-20T09:00:00Z", "handoff", "Priya Nair"), - event("voc", "VOC received", "voc_received", "2026-07-30T09:00:00Z", "assignment_gap"), - event("rebid", "Rebid started", "rebid_started", "2026-08-10T09:00:00Z", "assignment_gap", "Bid team"), + event("delivery", "Delivery confirmed", "2024-02-20T09:00:00Z", "handoff", "Priya Nair"), + event("voc", "VOC received", "2026-07-30T09:00:00Z", "assignment_gap"), + event("rebid", "Rebid started", "2026-08-10T09:00:00Z", "assignment_gap", "Bid team"), ], }; diff --git a/frontend/src/components/ProjectHistoryTimeline.test.tsx b/frontend/src/components/ProjectHistoryTimeline.test.tsx index 5428969eb..2ed42d032 100644 --- a/frontend/src/components/ProjectHistoryTimeline.test.tsx +++ b/frontend/src/components/ProjectHistoryTimeline.test.tsx @@ -19,8 +19,8 @@ const projection: ProjectHistoryProjection = { event_id: "award", source_post_id: "post-award", event_title: "Contract awarded", - event_type_code: "contract_awarded", - event_type_basis_code: "display_classification", + event_type_code: "source_recorded", + event_type_basis_code: "controlled_source_code", occurred_at: "2022-03-11T09:00:00Z", time_basis_code: "source_post_created_at_fallback", voc_type_code: null, @@ -45,8 +45,8 @@ const projection: ProjectHistoryProjection = { event_id: "spec", source_post_id: "post-spec", event_title: "Specification changed", - event_type_code: "specification_changed", - event_type_basis_code: "display_classification", + event_type_code: "source_recorded", + event_type_basis_code: "controlled_source_code", occurred_at: "2023-06-15T09:00:00Z", time_basis_code: "source_post_created_at_fallback", voc_type_code: null, @@ -72,7 +72,7 @@ const projection: ProjectHistoryProjection = { source_post_id: "post-voc", event_title: "VOC received", event_type_code: "voc_received", - event_type_basis_code: "display_classification", + event_type_basis_code: "controlled_source_code", occurred_at: "2026-02-02T09:00:00Z", time_basis_code: "source_post_created_at_fallback", voc_type_code: "voc", @@ -108,8 +108,8 @@ describe("ProjectHistoryTimeline", () => { const vocTab = screen.getByRole("tab", { name: /VOC received/ }); expect(vocTab).toHaveAttribute("aria-selected", "true"); expect(vocTab).toHaveAttribute("aria-current", "step"); - expect(screen.getByText(/evidence gap/i)).toBeInTheDocument(); - expect(screen.getByText(/related history, not causation/i)).toBeInTheDocument(); + expect(screen.getAllByText(/evidence gap/i).length).toBeGreaterThan(0); + expect(screen.getByText(/related history, not causality/i)).toBeInTheDocument(); fireEvent.click(screen.getByRole("button", { name: /open source record: VOC received/i })); expect(onOpenPost).toHaveBeenCalledWith("post-voc"); diff --git a/frontend/src/components/ProjectHistoryTimeline.tsx b/frontend/src/components/ProjectHistoryTimeline.tsx index 94b53840a..eaee5032c 100644 --- a/frontend/src/components/ProjectHistoryTimeline.tsx +++ b/frontend/src/components/ProjectHistoryTimeline.tsx @@ -20,10 +20,10 @@ function minimumPathScore(event: ProjectHistoryEvent): number | null { return Math.min(...event.related_prior_paths.map((path) => path.minimum_fused_score)); } -function initialEventId(projection: ProjectHistoryProjection): string { +function initialEventId(events: ProjectHistoryEvent[], focusEventId: string | null): string { return ( - projection.events.find((event) => event.event_id === projection.focus_event_id)?.event_id ?? - projection.events[0]?.event_id ?? + events.find((event) => event.event_id === focusEventId)?.event_id ?? + events[0]?.event_id ?? "" ); } @@ -39,19 +39,22 @@ export function ProjectHistoryTimeline({ const instanceId = useId(); const panelId = `${instanceId}-project-history-panel`; const headingId = `${instanceId}-project-history-heading`; - const [selectedEventId, setSelectedEventId] = useState(() => initialEventId(projection)); + const [selectedEventId, setSelectedEventId] = useState(() => + initialEventId(projection.events, projection.focus_event_id), + ); const tabRefs = useRef>([]); const eventById = new Map(projection.events.map((event) => [event.event_id, event])); const selectedEvent = - eventById.get(selectedEventId) ?? eventById.get(initialEventId(projection)) ?? null; + eventById.get(selectedEventId) ?? + eventById.get(initialEventId(projection.events, projection.focus_event_id)) ?? + null; const selectedIndex = selectedEvent ? projection.events.findIndex((event) => event.event_id === selectedEvent.event_id) : -1; const selectedTabId = selectedIndex >= 0 ? `${instanceId}-project-history-tab-${selectedIndex}` : undefined; useEffect(() => { - setSelectedEventId(initialEventId(projection)); - tabRefs.current = []; + setSelectedEventId(initialEventId(projection.events, projection.focus_event_id)); }, [projection.normalized_project_key, projection.focus_event_id, projection.events]); function selectAt(index: number) { diff --git a/frontend/src/projectHistory.ts b/frontend/src/projectHistory.ts index ac7392361..e3bfe81e5 100644 --- a/frontend/src/projectHistory.ts +++ b/frontend/src/projectHistory.ts @@ -46,7 +46,7 @@ export interface ProjectHistoryEvent { source_post_id: string; event_title: string; event_type_code: string; - event_type_basis_code: "display_classification"; + event_type_basis_code: "controlled_source_code"; occurred_at: string; time_basis_code: ProjectHistoryTimeBasis; voc_type_code: string | null; diff --git a/lineageweave/project_history.py b/lineageweave/project_history.py index 49eb01af2..3bc5683d9 100644 --- a/lineageweave/project_history.py +++ b/lineageweave/project_history.py @@ -21,46 +21,6 @@ PROJECT_HISTORY_MAX_DEPTH = 8 PROJECT_HISTORY_MAX_PATHS_PER_EVENT = 32 -_EVENT_PATTERNS: tuple[tuple[str, tuple[str, ...]], ...] = ( - ("rebid_started", ("rebid", "re-bid", "retender", "re-tender", "재입찰")), - ( - "handoff_recorded", - ("handoff", "hand-off", "transferred ownership", "operational transfer", "인수인계"), - ), - ( - "specification_changed", - ( - "specification change", - "specification revision", - "revised specification", - "spec revision", - "사양 변경", - "사양변경", - ), - ), - ( - "delivered", - ( - "delivery confirmed", - "delivery completed", - "delivered", - "shipment completed", - "납품 완료", - "납품완료", - ), - ), - ( - "contract_awarded", - ( - "contract awarded", - "award confirmed", - "order confirmation", - "purchase order received", - "수주 확정", - "수주확정", - ), - ), -) _VOC_CODES = frozenset({"voc", "vocc", "voco", "vom", "vop"}) _TRUTH_ORDER = {"observed": 0, "inferred": 1} _DISPLAY_NAME_ORDER = {"source_project_name": 0, "semantic_project_name": 1} @@ -92,21 +52,15 @@ def classify_project_event( ) -> str: """Return a non-authoritative display classification for one source row. - Explicit title/stage/state markers take precedence. Every already-visible - VOC-family row is labelled as VOC, not only the currently selected row. - ``is_focus`` is retained for contract compatibility but never changes the - truth status or creates an event. + Only the persisted controlled VOC code is classified. Free-text titles, + stages, and detail states remain evidence fields; this projection never + guesses lifecycle semantics from words. ``is_focus`` is retained for + contract compatibility but never changes the truth status or creates an + event. """ del is_focus - text = " ".join( - part.strip().lower() - for part in (title, source_stage_code or "", source_detail_state_code or "") - if part.strip() - ) - for event_code, patterns in _EVENT_PATTERNS: - if any(pattern in text for pattern in patterns): - return event_code + del title, source_stage_code, source_detail_state_code if (voc_type_code or "").strip().lower() in _VOC_CODES: return "voc_received" return "source_recorded" @@ -396,7 +350,7 @@ def build_project_history_projection( voc_type_code=row.get("voc_type_code"), is_focus=event_id == effective_focus, ), - "event_type_basis_code": "display_classification", + "event_type_basis_code": "controlled_source_code", "occurred_at": _as_utc(row["created_at"]), "time_basis_code": PROJECT_HISTORY_TIME_BASIS, "voc_type_code": row.get("voc_type_code"), diff --git a/tests/test_project_history.py b/tests/test_project_history.py index 5e731585f..364a382e2 100644 --- a/tests/test_project_history.py +++ b/tests/test_project_history.py @@ -19,8 +19,8 @@ def test_project_identity_is_exact_but_unicode_compatible() -> None: normalize_project_key(" ") -def test_event_display_classification_does_not_create_authority() -> None: - """The lifecycle label is presentation metadata over an existing post.""" +def test_event_display_classification_uses_only_controlled_evidence() -> None: + """Free text cannot manufacture a lifecycle event classification.""" assert ( classify_project_event( title="Contract awarded", @@ -29,7 +29,7 @@ def test_event_display_classification_does_not_create_authority() -> None: voc_type_code=None, is_focus=False, ) - == "contract_awarded" + == "source_recorded" ) for is_focus in (False, True): assert ( diff --git a/tests/test_project_history_ingestion.py b/tests/test_project_history_ingestion.py new file mode 100644 index 000000000..f36847cc9 --- /dev/null +++ b/tests/test_project_history_ingestion.py @@ -0,0 +1,52 @@ +"""Authorization-bound project-history query tests.""" + +from __future__ import annotations + +import asyncio +from datetime import datetime, timezone + +from backend.app.project_history import fetch_project_history_projection + + +class _Connection: + """Record projection queries and return one synthetic visible event.""" + + def __init__(self) -> None: + self.calls: list[tuple[str, tuple[object, ...]]] = [] + + async def fetch(self, query: str, *args: object): + """Return the minimum rows required by each bounded query.""" + + self.calls.append((" ".join(query.split()), args)) + if "select post.post_id, post.post_title" in " ".join(query.split()): + return [ + { + "post_id": "00000000-0000-0000-0000-000000000001", + "post_title": "Synthetic project record", + "created_at": datetime(2026, 1, 1, tzinfo=timezone.utc), + "voc_type_code": None, + "source_stage_code": "observed-stage", + "source_detail_state_code": None, + } + ] + return [] + + +def test_project_history_query_binds_corporate_and_process_scopes() -> None: + """Private project evidence must bind both dimensions before child reads.""" + + connection = _Connection() + result = asyncio.run( + fetch_project_history_projection( + connection, + project_key="P-100", + focus_post_id=None, + knowledge_cutoff=datetime(2026, 2, 1, tzinfo=timezone.utc), + corporate_entity_ids=["corp-1"], + process_unit_ids=["pu-1"], + ) + ) + event_query, event_args = connection.calls[0] + assert "post.process_unit_id::text = any($3::text[])" in event_query + assert event_args[1:3] == (["corp-1"], ["pu-1"]) + assert result["events"][0]["event_type_code"] == "source_recorded" From 97c5d397255f2e49a2e0119972be033a2acd9308 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 26 Aug 2026 07:50:02 +0900 Subject: [PATCH 09/26] docs(gaps): record project-history delivery evidence --- docs/product-technical-gap-baseline.md | 40 +++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 974bef3ac..8c36bca08 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,7 +1,7 @@ # Product & Technical Gap Baseline -> Dashboard delivery snapshot: 2026-08-26 06:20 KST. Protected `main` was -> `04e6b610655d0db91d5f7ba9486bdda1440e0b19`. This local branch is not +> Dashboard delivery snapshot: 2026-08-26 07:45 KST. Protected `main` was +> `494b54e2245040bcf02b45376f221c37cd437e76`. This local branch is not > protected-main release evidence. ## Operations Dashboard PRD/TRD traceability @@ -11,7 +11,7 @@ | Claim cause delay: order, specification change, originating order, sales pool, Event/post counts | ADR 0206; contextual-orchestrator case classification with cited spans; Event Lineage context | Candidate implementation; authenticated runtime acceptance pending | | Rebid/handover: discussion, counterparties, our owner, decisions, Event/post counts | ADR 0206; normalized case facts plus persisted summary actions/roles | Candidate implementation; corpus backfill pending | | External information count/rate and sales/project relation | ADR 0206; semantic `external_information` classification inside Dashboard GNB | Candidate implementation; no separate Board by product decision | -| Project-specific journey | Explicit source/semantic project membership plus event-time ordering | Candidate API and ordered journey UI implemented; authenticated runtime acceptance pending | +| Project-specific journey | ADR 0224; exact source/semantic project membership, source-post clock disclosure, responsibility evidence, and non-causal Event Lineage paths | PR #668 candidate API and post-detail timeline; exact-head review, protected merge, and authenticated runtime acceptance pending | | Repeat issue to design improvement | `repeat_issue`, `issue_pattern`, and `improvement_action` cited facts | Candidate semantic contract; design-system connector acceptance pending | | Natural-language Ask with evidence, report, alert, MCP | Persisted semantic-unit embeddings plus versioned delivery/resource contract | Candidate implementation uses whole-question embedding retrieval with no lexical fallback; authenticated runtime acceptance pending | | Similar VOC, customer cohort, prior action | Persisted repeat-issue candidate semantics plus orchestrator pair adjudication and extractive evidence | Candidate live post endpoint and post-detail UI implemented; authenticated runtime acceptance pending | @@ -60,14 +60,14 @@ only aggregate, non-identifying evidence to this repository. ### Exact open-PR boundary -At this snapshot there were 11 open PRs and 10 open issues. The exact-head +At this snapshot there were 12 open PRs and 10 open issues. The exact-head inventory in section 1 is authoritative for this snapshot. Every open head remained blocked on hosted gates and/or independent review. These observations are not merge readiness. Re-fetch exact heads, unresolved threads, checks, approvals, rulesets, and merge SHA before any lifecycle claim. -> Audit snapshot: 2026-08-26 06:20 KST (refreshed by the autonomous merge +> Audit snapshot: 2026-08-26 07:45 KST (refreshed by the autonomous merge > loop). This repository records synthetic fixtures and aggregate, > non-identifying runtime evidence only. Open PRs and local checks are not > protected-default-branch release evidence. Identifying post identifiers, @@ -76,26 +76,26 @@ lifecycle claim. ## 1. Exact-head and governance evidence -The protected default branch was `04e6b610655d0db91d5f7ba9486bdda1440e0b19` -when this baseline was refreshed. The live queue contained 11 open PRs and 10 +The protected default branch was `494b54e2245040bcf02b45376f221c37cd437e76` +when this baseline was refreshed. The live queue contained 12 open PRs and 10 open issues. The exact-head inventory below supersedes older per-PR snapshots elsewhere in this document; those older rows remain useful historical delivery context only. | PR | Exact observed head | Merge/check state at this snapshot | | ---: | --- | --- | -| #663 | `ab330a0f` | project ontology traversal plus cutoff/snapshot-frozen project focus and labels; exact-head checks/review required | -| #660 | `24fda085` | backend runtime repair plus semantic-unit stack; exact-head checks/review required | -| #659 | `0739b9d7` | ontology node readability and tokenized UI fills; branch is DIRTY against current main and requires conflict repair | +| #668 | `5ef8db83` | ADR 0224 evidence-bound project-history API and post-detail timeline; exact-head checks/review required | +| #667 | `3bf57fed` | gap-baseline refresh; exact-head checks/review required | +| #663 | `d5edd2b9` | project ontology traversal plus cutoff/snapshot-frozen project focus and labels; exact-head checks/review required | | #658 | `fe830b0a` | evidence-honest Global Ask cutoff with revision-interval live-after semantics; exact-head checks/review required | -| #657 | `64f48679` | Dashboard case-metric contract work; exact-head checks/review required | -| #644 | `d9ff9980` | native-surface code splitting with modal-focus regression coverage; exact-head checks/review required | -| #643 | `0a1f8ec1` | accessible status-notice surfaces; exact-head checks/review required | -| #640 | `2d50fa01` | operations-dashboard contract alignment; exact-head checks/review required | -| #639 | `aee02dca` | exact-head checks/review required | -| #632 | `a4059113` | active semantic provenance repair head; exact-head checks/review required | -| #631 | `1d9ac825` | documentation decomposition; branch is DIRTY against current main and requires conflict repair | -| #629 | `4b4d6707` | exact-head checks/review required | +| #657 | `a59a2023` | TEPP asynchronous lifecycle evidence; exact-head checks/review required | +| #644 | `ed8d97f3` | native-surface code splitting with modal-focus regression coverage; exact-head checks/review required | +| #643 | `7fb4d18c` | accessible status-notice surfaces; exact-head checks/review required | +| #640 | `361641ec` | operations-dashboard contract alignment; exact-head checks/review required | +| #639 | `8da485d3` | exact-head checks/review required | +| #632 | `cad4debf` | active semantic provenance repair head; exact-head checks/review required | +| #631 | `e6b4f0c4` | documentation decomposition; exact-head checks/review required | +| #629 | `48496ff6` | provider-work release and bounded landing reads; exact-head checks/review required | No row above is merge evidence. Immediately before any lifecycle action, re-fetch the head, unresolved threads, formal reviews, rulesets, and same-head @@ -356,8 +356,8 @@ this file per §3.5 of the prior snapshot). | #272 | Verify Global Ask KG/ontology/semantic claims with public SearXNG evidence | Ask stack | | #274 | Persist and explain Event Lineage channel evidence | #387 | | #277 | TEPP: persist accepted receipts, poll completed results, keep measurement authority distinct | #468, #417 | -| #280 | Full project-lifecycle history and handover intervals | Tracked with issue #284; no active delivery PR confirmed | -| #284 | Authoritative lifecycle ingestion and idempotent reconciliation | No active delivery PR confirmed | +| #280 | Evidence-bound project history with responsibility changes and related prior paths | #668; read projection only, exact-head protected delivery pending | +| #284 | Authoritative lifecycle ingestion and idempotent reconciliation | No owned source-adapter contract or active delivery PR; ADR 0224 forbids the read projection from inventing this authority | | #289 | Activate the optional lineage LLM channel through a bounded asynchronous rebuild | #434 | | #336 | Replace pseudo-CalDAV feed with a Naruon-owned calendar projection | Contract on `main` (#355); operator consume wiring in historical branch `feat/naruon-calendar-buyer-wiring-v2170` | | #338 | Evidence-bounded email/project lineage contract for Naruon consumption | #355 | From e9318cacc765cb4ba936f60b863b0c16aa3a6d06 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 26 Aug 2026 08:01:16 +0900 Subject: [PATCH 10/26] fix(frontend): normalize project history actions --- frontend/src/App.tsx | 12 ++++++------ frontend/src/projectHistory.test.ts | 22 ++++++++++++++++++++++ frontend/src/projectHistory.ts | 18 ++++++++++++++++++ 3 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 frontend/src/projectHistory.test.ts diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b0092366c..fb1b7f2eb 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -90,6 +90,7 @@ import { } from "./api"; import { CitationChip } from "./components/CitationChip"; import { ProjectHistoryTimeline } from "./components/ProjectHistoryTimeline"; +import { projectHistoryKeys } from "./projectHistory"; import type { ProjectHistoryProjection } from "./projectHistory"; import { OrganizationAliasChip } from "./components/OrganizationAliasChip"; import { organizationAliasCaption } from "./components/organizationAliasCaption"; @@ -2345,12 +2346,11 @@ function PostDetailPopup({

{t("Project history")}

- {(post.project_evidence?.map((project) => project.project_key) ?? [ - post.source_project_code ?? post.source_project_name ?? "", - ]) - .filter((projectKey, index, values) => - Boolean(projectKey) && values.indexOf(projectKey) === index, - ) + {projectHistoryKeys( + post.project_evidence, + post.source_project_code, + post.source_project_name, + ) .map((projectKey) => (
+
+
{projectHistoryText(locale, "timeBasisCode")}
+
{selectedEvent.time_basis_code}
+
+
+
{projectHistoryText(locale, "sourceStageCode")}
+
{selectedEvent.source_stage_code ?? projectHistoryText(locale, "notApplicable")}
+
+
+
{projectHistoryText(locale, "sourceDetailStateCode")}
+
+ {selectedEvent.source_detail_state_code ?? projectHistoryText(locale, "notApplicable")} +
+
{selectedEvent.responsibility_transition_code ? (
{projectHistoryText(locale, "columnTransition")}
diff --git a/frontend/src/projectHistory.ts b/frontend/src/projectHistory.ts index 3e4538e50..8836c566f 100644 --- a/frontend/src/projectHistory.ts +++ b/frontend/src/projectHistory.ts @@ -150,6 +150,9 @@ const MESSAGE_KEYS = [ "eventDetail", "eventType", "eventDate", + "timeBasisCode", + "sourceStageCode", + "sourceDetailStateCode", "responsibilityEvidence", "noResponsibilityEvidence", "continuous", @@ -193,6 +196,9 @@ const EN: Record = { eventDetail: "Event detail", eventType: "Display event type", eventDate: "Source-post date", + timeBasisCode: "Time basis code", + sourceStageCode: "Source stage code", + sourceDetailStateCode: "Source detail-state code", responsibilityEvidence: "Observed responsibility evidence", noResponsibilityEvidence: "No responsibility evidence is recorded for this event.", continuous: "Responsibility evidence continued", @@ -233,6 +239,9 @@ const MESSAGES: Record> = { eventDetail: "이벤트 상세", eventType: "표시용 이벤트 유형", eventDate: "원천 게시물 날짜", + timeBasisCode: "시간 기준 코드", + sourceStageCode: "원천 단계 코드", + sourceDetailStateCode: "원천 세부 상태 코드", responsibilityEvidence: "관찰된 담당 근거", noResponsibilityEvidence: "이 이벤트에는 기록된 담당 근거가 없습니다.", continuous: "담당 근거 유지", @@ -270,6 +279,9 @@ const MESSAGES: Record> = { eventDetail: "事件详情", eventType: "显示事件类型", eventDate: "源帖子日期", + timeBasisCode: "时间基准代码", + sourceStageCode: "来源阶段代码", + sourceDetailStateCode: "来源详细状态代码", responsibilityEvidence: "已观察的责任证据", noResponsibilityEvidence: "此事件没有记录责任证据。", continuous: "责任证据持续", @@ -307,6 +319,9 @@ const MESSAGES: Record> = { eventDetail: "イベント詳細", eventType: "表示用イベント種別", eventDate: "原資料の日付", + timeBasisCode: "時間基準コード", + sourceStageCode: "ソース段階コード", + sourceDetailStateCode: "ソース詳細状態コード", responsibilityEvidence: "観察された担当根拠", noResponsibilityEvidence: "このイベントには担当根拠が記録されていません。", continuous: "担当根拠が継続", @@ -344,6 +359,9 @@ const MESSAGES: Record> = { eventDetail: "Chi tiết sự kiện", eventType: "Loại sự kiện hiển thị", eventDate: "Ngày bài nguồn", + timeBasisCode: "Mã cơ sở thời gian", + sourceStageCode: "Mã giai đoạn nguồn", + sourceDetailStateCode: "Mã trạng thái chi tiết nguồn", responsibilityEvidence: "Bằng chứng trách nhiệm quan sát được", noResponsibilityEvidence: "Không có bằng chứng trách nhiệm được ghi cho sự kiện này.", continuous: "Bằng chứng trách nhiệm tiếp tục", From 153add77dc5aefd260c770d5bdd15610a75b1125 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 26 Aug 2026 10:02:39 +0900 Subject: [PATCH 16/26] fix(ui): use theme tokens for project history timeline --- frontend/src/components/ProjectHistoryTimeline.css | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/ProjectHistoryTimeline.css b/frontend/src/components/ProjectHistoryTimeline.css index 0ff7a031e..4ecd339d3 100644 --- a/frontend/src/components/ProjectHistoryTimeline.css +++ b/frontend/src/components/ProjectHistoryTimeline.css @@ -44,7 +44,7 @@ position: absolute; inset-inline: 1rem; top: 2rem; - border-top: 2px solid var(--border-color, #9aa4b2); + border-top: 2px solid var(--color-border); } .project-history-tab { @@ -68,7 +68,7 @@ .project-history-marker { background: currentColor; - border: 0.25rem solid var(--surface-color, #fff); + border: 0.25rem solid var(--color-background); border-radius: 50%; box-shadow: 0 0 0 2px currentColor; height: 1rem; @@ -87,7 +87,7 @@ } .project-history-detail { - border: 1px solid var(--border-color, #c8d0da); + border: 1px solid var(--color-border); border-radius: 0.75rem; display: grid; gap: 1rem; @@ -138,7 +138,7 @@ .project-history-responsibilities li, .project-history-paths li { - border: 1px solid var(--border-color, #d7dde5); + border: 1px solid var(--color-border); border-radius: 0.5rem; display: flex; flex-wrap: wrap; @@ -180,7 +180,7 @@ .project-history-table-scroll th, .project-history-table-scroll td { - border: 1px solid var(--border-color, #c8d0da); + border: 1px solid var(--color-border); padding: 0.5rem; text-align: start; vertical-align: top; @@ -195,7 +195,7 @@ } .project-history-tabs::before { - border-inline-start: 2px solid var(--border-color, #9aa4b2); + border-inline-start: 2px solid var(--color-border); border-top: 0; inset-block: 1rem; inset-inline-start: 0.75rem; From e452bf38a33455606e044b20f3be4e00909d0c6a Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 26 Aug 2026 10:56:40 +0900 Subject: [PATCH 17/26] fix(projects): make projection checks explicit --- backend/app/project_history.py | 4 ++-- lineageweave/project_history.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/app/project_history.py b/backend/app/project_history.py index 813c96179..76f6008c8 100644 --- a/backend/app/project_history.py +++ b/backend/app/project_history.py @@ -1,4 +1,4 @@ -"""ABAC-safe PostgreSQL projection for Buyer project histories.""" +"""ABAC-safe PostgreSQL projection for customer-facing project histories.""" from __future__ import annotations @@ -19,7 +19,7 @@ class ProjectHistoryConnection(Protocol): async def fetch(self, query: str, *args: object) -> Sequence[Mapping[str, Any]]: """Execute a bounded read query and return mapping-like rows.""" - ... + pass _ELIGIBILITY = SOURCE_POST_ELIGIBILITY_SQL.format(alias="post") diff --git a/lineageweave/project_history.py b/lineageweave/project_history.py index 3bc5683d9..c4e3e08cd 100644 --- a/lineageweave/project_history.py +++ b/lineageweave/project_history.py @@ -13,6 +13,7 @@ from collections.abc import Mapping, Sequence from datetime import datetime, timezone from decimal import Decimal +import math from typing import Any from unicodedata import normalize @@ -117,7 +118,7 @@ def _score(value: object) -> float: if isinstance(value, bool) or not isinstance(value, (int, float, Decimal)): raise ValueError("lineage score must be numeric") result = float(value) - if result != result or result in (float("inf"), float("-inf")): + if math.isnan(result) or result in (float("inf"), float("-inf")): raise ValueError("lineage score must be finite") return result From 1a2fae22f37b93d05032b1394215d1052bc66d67 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 26 Aug 2026 13:38:16 +0900 Subject: [PATCH 18/26] docs(adr): assign unique project-history decision id --- CHANGELOG.md | 2 +- ...md => 0232-evidence-bound-project-history-projection.md} | 2 +- docs/adr/README.md | 2 +- docs/product-technical-gap-baseline.md | 6 +++--- docs/storybook-inventory.md | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) rename docs/adr/{0226-evidence-bound-project-history-projection.md => 0232-evidence-bound-project-history-projection.md} (97%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f57b669b..d02027a31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ All notable changes to this project are documented here. Format follows ### Added - An evidence-bound project-history projection and post-detail timeline - (ADR 0226 / #280) reuse normalized project, responsibility, and Event + (ADR 0232 / #280) reuse normalized project, responsibility, and Event Lineage rows under RBAC/ABAC and cutoff filtering. Free text never creates a lifecycle fact; unsupported authoritative ingestion remains issue #284. - Persist explicit paragraph, list, table, MathML formula, and caller-parsed diff --git a/docs/adr/0226-evidence-bound-project-history-projection.md b/docs/adr/0232-evidence-bound-project-history-projection.md similarity index 97% rename from docs/adr/0226-evidence-bound-project-history-projection.md rename to docs/adr/0232-evidence-bound-project-history-projection.md index 8ff0083b5..12e7a14b9 100644 --- a/docs/adr/0226-evidence-bound-project-history-projection.md +++ b/docs/adr/0232-evidence-bound-project-history-projection.md @@ -1,4 +1,4 @@ -# ADR 0226: Evidence-bound project history projection +# ADR 0232: Evidence-bound project history projection - Status: Accepted - Date: 2026-08-26 diff --git a/docs/adr/README.md b/docs/adr/README.md index c6f8f2ef3..84bb3ab1b 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -17,7 +17,7 @@ decision from them. | [`ONTOLOGY_NAMESPACE_INVENTORY.md`](../doctoring/ONTOLOGY_NAMESPACE_INVENTORY.md) | [0207](0207-repository-case-ontology-namespace-canonical.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), [0184](0184-ontology-provenance-explorer.md) | -| Project-history timeline | [0226](0226-evidence-bound-project-history-projection.md) | +| Project-history timeline | [0232](0232-evidence-bound-project-history-projection.md) | | [`POSTGRESQL_CONCURRENCY_REFERENCES.md`](../doctoring/POSTGRESQL_CONCURRENCY_REFERENCES.md) | [0204](0204-analysis-run-short-transaction-delivery.md) | | [`operability/http-concurrency-evidence.md`](../operability/http-concurrency-evidence.md) | [0204](0204-analysis-run-short-transaction-delivery.md), [0212](0212-single-query-authorized-post-filter-options.md) | | Evidence operations Dashboard (`/`) | [0206](0206-evidence-operations-dashboard.md) | diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 816348a9d..2e890abf5 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -11,7 +11,7 @@ | Claim cause delay: order, specification change, originating order, sales pool, Event/post counts | ADR 0206; contextual-orchestrator case classification with cited spans; Event Lineage context | Candidate implementation; authenticated runtime acceptance pending | | Rebid/handover: discussion, counterparties, our owner, decisions, Event/post counts | ADR 0206; normalized case facts plus persisted summary actions/roles | Candidate implementation; corpus backfill pending | | External information count/rate and sales/project relation | ADR 0206; semantic `external_information` classification inside Dashboard GNB | Candidate implementation; no separate Board by product decision | -| Project-specific journey | ADR 0226; exact source/semantic project membership, source-post clock disclosure, responsibility evidence, and non-causal Event Lineage paths | PR #668 candidate API and post-detail timeline; exact-head review, protected merge, and authenticated runtime acceptance pending | +| Project-specific journey | ADR 0232; exact source/semantic project membership, source-post clock disclosure, responsibility evidence, and non-causal Event Lineage paths | PR #668 candidate API and post-detail timeline; exact-head review, protected merge, and authenticated runtime acceptance pending | | Repeat issue to design improvement | `repeat_issue`, `issue_pattern`, and `improvement_action` cited facts | Candidate semantic contract; design-system connector acceptance pending | | Natural-language Ask with evidence, report, alert, MCP | Persisted semantic-unit embeddings plus versioned delivery/resource contract | Candidate implementation uses whole-question embedding retrieval with no lexical fallback; authenticated runtime acceptance pending | | Similar VOC, customer cohort, prior action | Persisted repeat-issue candidate semantics plus orchestrator pair adjudication and extractive evidence | Candidate live post endpoint and post-detail UI implemented; authenticated runtime acceptance pending | @@ -84,7 +84,7 @@ context only. | PR | Exact observed head | Merge/check state at this snapshot | | ---: | --- | --- | -| #668 | `5ef8db83` | ADR 0226 evidence-bound project-history API and post-detail timeline; exact-head checks/review required | +| #668 | `5ef8db83` | ADR 0232 evidence-bound project-history API and post-detail timeline; exact-head checks/review required | | #667 | `3bf57fed` | gap-baseline refresh; exact-head checks/review required | | #663 | `d5edd2b9` | project ontology traversal plus cutoff/snapshot-frozen project focus and labels; exact-head checks/review required | | #658 | `fe830b0a` | evidence-honest Global Ask cutoff with revision-interval live-after semantics; exact-head checks/review required | @@ -357,7 +357,7 @@ this file per §3.5 of the prior snapshot). | #274 | Persist and explain Event Lineage channel evidence | #387 | | #277 | TEPP: persist accepted receipts, poll completed results, keep measurement authority distinct | #468, #417 | | #280 | Evidence-bound project history with responsibility changes and related prior paths | #668; read projection only, exact-head protected delivery pending | -| #284 | Authoritative lifecycle ingestion and idempotent reconciliation | No owned source-adapter contract or active delivery PR; ADR 0226 forbids the read projection from inventing this authority | +| #284 | Authoritative lifecycle ingestion and idempotent reconciliation | No owned source-adapter contract or active delivery PR; ADR 0232 forbids the read projection from inventing this authority | | #289 | Activate the optional lineage LLM channel through a bounded asynchronous rebuild | #434 | | #336 | Replace pseudo-CalDAV feed with a Naruon-owned calendar projection | Contract on `main` (#355); operator consume wiring in historical branch `feat/naruon-calendar-buyer-wiring-v2170` | | #338 | Evidence-bounded email/project lineage contract for Naruon consumption | #355 | diff --git a/docs/storybook-inventory.md b/docs/storybook-inventory.md index df55de456..bba2e1cd8 100644 --- a/docs/storybook-inventory.md +++ b/docs/storybook-inventory.md @@ -37,4 +37,4 @@ World Wide Web Consortium. (2024). *Web Content Accessibility Guidelines `ProjectHistoryTimeline.stories.tsx` covers the evidence-bearing default timeline and its exact-value table. Keyboard roving focus, the current event, responsibility-evidence gaps, non-causal lineage paths, and source-record -actions are executable component-test states governed by ADR 0226. +actions are executable component-test states governed by ADR 0232. From 4664246ee60339c7384217fbb73f9e84f9c47589 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 26 Aug 2026 13:56:46 +0900 Subject: [PATCH 19/26] fix(projects): use event time and remove internal UI codes --- backend/app/project_history.py | 12 ++- ...idence-bound-project-history-projection.md | 3 +- .../project-history-time-source-desktop.png | Bin 0 -> 107670 bytes .../project-history-time-source-mobile.png | Bin 0 -> 101202 bytes docs/storybook-inventory.md | 7 +- .../ProjectHistoryTimeline.stories.tsx | 2 +- .../ProjectHistoryTimeline.test.tsx | 3 +- .../src/components/ProjectHistoryTimeline.tsx | 13 ++- frontend/src/projectHistory.ts | 91 ++++++------------ lineageweave/project_history.py | 37 +++++-- tests/test_project_history.py | 2 +- tests/test_project_history_ingestion.py | 4 + 12 files changed, 92 insertions(+), 82 deletions(-) create mode 100644 docs/screenshots/project-history-time-source-desktop.png create mode 100644 docs/screenshots/project-history-time-source-mobile.png diff --git a/backend/app/project_history.py b/backend/app/project_history.py index 76f6008c8..a18c2720f 100644 --- a/backend/app/project_history.py +++ b/backend/app/project_history.py @@ -42,6 +42,7 @@ async def fetch(self, query: str, *args: object) -> Sequence[Mapping[str, Any]]: select post.post_id, post.post_title, post.created_at, + post.event_occurred_at, post.voc_type_code, post.source_stage_code, post.source_detail_state_code @@ -53,13 +54,14 @@ async def fetch(self, query: str, *args: object) -> Sequence[Mapping[str, Any]]: and {_ELIGIBILITY} and post.created_at <= $4 and {_PROJECT_MATCH} - order by post.created_at, post.post_id + order by coalesce(post.event_occurred_at, post.created_at), post.created_at, post.post_id limit $5 """ _FOCUS_SQL = f""" select post.post_id, post.post_title, post.created_at, + post.event_occurred_at, post.voc_type_code, post.source_stage_code, post.source_detail_state_code @@ -194,7 +196,13 @@ async def fetch_project_history_projection( raise ProjectHistoryNotFound(project_key) truncated = True event_rows = (event_rows[: limit - 1] if limit > 1 else []) + [focus_rows[0]] - event_rows.sort(key=lambda row: (row["created_at"], str(row["post_id"]))) + event_rows.sort( + key=lambda row: ( + row.get("event_occurred_at") or row["created_at"], + row["created_at"], + str(row["post_id"]), + ) + ) visible_ids = [str(row["post_id"]) for row in event_rows] match_rows, role_rows, edge_rows = await _fetch_project_children( diff --git a/docs/adr/0232-evidence-bound-project-history-projection.md b/docs/adr/0232-evidence-bound-project-history-projection.md index 12e7a14b9..3c6b2d406 100644 --- a/docs/adr/0232-evidence-bound-project-history-projection.md +++ b/docs/adr/0232-evidence-bound-project-history-projection.md @@ -26,7 +26,8 @@ navigation destination. Controlled VOC codes may label VOC evidence. Other records remain `source_recorded`; source stage and detail-state codes are shown without inferred lifecycle meaning. Adjacent responsibility rows describe document evidence only. Persisted Event Lineage paths are labelled related and -non-causal. Dates disclose that `source_post.created_at` is the fallback clock. +non-causal. Dates use `source_post.event_occurred_at` when recorded and disclose +`source_post.created_at` as the fallback clock. The projection is bounded and declares truncation. A missing or unauthorized project is indistinguishable as HTTP 404. The Figma identifier records the diff --git a/docs/screenshots/project-history-time-source-desktop.png b/docs/screenshots/project-history-time-source-desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..63a670cdc4deb77a378eac85da5e63ed2a4758d9 GIT binary patch literal 107670 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%0A0^oW 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 { expect(vocTab).toHaveAttribute("aria-current", "step"); expect(screen.getAllByText(/evidence gap/i).length).toBeGreaterThan(0); expect(screen.getByText(/related history, not causality/i)).toBeInTheDocument(); - expect(screen.getByText("document_time")).toBeInTheDocument(); + expect(screen.getByText("Recorded event time")).toBeInTheDocument(); + expect(screen.queryByText("document_time")).not.toBeInTheDocument(); expect(screen.getByText("delivery")).toBeInTheDocument(); expect(screen.getByText("delivered")).toBeInTheDocument(); diff --git a/frontend/src/components/ProjectHistoryTimeline.tsx b/frontend/src/components/ProjectHistoryTimeline.tsx index adb8bf293..ee3cb3210 100644 --- a/frontend/src/components/ProjectHistoryTimeline.tsx +++ b/frontend/src/components/ProjectHistoryTimeline.tsx @@ -105,9 +105,7 @@ export function ProjectHistoryTimeline({

- {projection.time_basis_code === "source_post_created_at_fallback" - ? projectHistoryText(locale, "documentTime") - : `${projectHistoryText(locale, "timeBasisCode")}: ${projection.time_basis_code}`} + {projectHistoryText(locale, "documentTime")}

{projection.truncated ? (

@@ -188,7 +186,14 @@ export function ProjectHistoryTimeline({

{projectHistoryText(locale, "timeBasisCode")}
-
{selectedEvent.time_basis_code}
+
+ {projectHistoryText( + locale, + selectedEvent.time_basis_code === "document_time" + ? "recordedEventTime" + : "sourceCreationTime", + )} +
{projectHistoryText(locale, "sourceStageCode")}
diff --git a/frontend/src/projectHistory.ts b/frontend/src/projectHistory.ts index 8836c566f..fb846f251 100644 --- a/frontend/src/projectHistory.ts +++ b/frontend/src/projectHistory.ts @@ -71,13 +71,6 @@ export interface ProjectHistoryProjection { events: ProjectHistoryEvent[]; } -export interface ProjectEvidenceGroup { - normalizedProjectKey: string; - projectKey: string; - projectName: string; - evidence: ProjectEvidence[]; -} - function normalizeProjectIdentity(value: string): string { return value.normalize("NFKC").trim().toLocaleLowerCase("en-US"); } @@ -100,48 +93,6 @@ export function projectHistoryKeys( }); } -function evidenceOrder(evidence: ProjectEvidence): number { - if (evidence.extraction_method === "source_field_hint") return 0; - if (evidence.resolution_status === "hint_only") return 1; - return 2; -} - -function compareEvidence(left: ProjectEvidence, right: ProjectEvidence): number { - return ( - evidenceOrder(left) - evidenceOrder(right) || - left.project_name.localeCompare(right.project_name) || - left.project_key.localeCompare(right.project_key) || - left.provenance.localeCompare(right.provenance) - ); -} - -export function groupProjectEvidence(evidence: ProjectEvidence[]): ProjectEvidenceGroup[] { - const groups = new Map(); - for (const item of evidence) { - const normalizedProjectKey = normalizeProjectIdentity(item.project_key || item.project_name); - if (!normalizedProjectKey) continue; - const rows = groups.get(normalizedProjectKey) ?? []; - rows.push(item); - groups.set(normalizedProjectKey, rows); - } - return Array.from(groups.entries()) - .map(([normalizedProjectKey, rows]) => { - const ordered = [...rows].sort(compareEvidence); - const representative = ordered[0]; - return { - normalizedProjectKey, - projectKey: representative.project_key || representative.project_name, - projectName: representative.project_name || representative.project_key, - evidence: ordered, - }; - }) - .sort( - (left, right) => - left.projectName.localeCompare(right.projectName) || - left.normalizedProjectKey.localeCompare(right.normalizedProjectKey), - ); -} - const MESSAGE_KEYS = [ "heading", "summaryCounts", @@ -151,6 +102,8 @@ const MESSAGE_KEYS = [ "eventType", "eventDate", "timeBasisCode", + "recordedEventTime", + "sourceCreationTime", "sourceStageCode", "sourceDetailStateCode", "responsibilityEvidence", @@ -191,12 +144,14 @@ type MessageParams = Record; const EN: Record = { heading: "Project event timeline", summaryCounts: "{events} events · {actors} observed actors", - documentTime: "Dates use source-post creation time because a separate event clock is not recorded.", + documentTime: "Dates use the recorded event time when available and the source creation time otherwise.", truncated: "This bounded timeline is truncated. The selected event remains included.", eventDetail: "Event detail", eventType: "Display event type", - eventDate: "Source-post date", - timeBasisCode: "Time basis code", + eventDate: "Event date", + timeBasisCode: "Time source", + recordedEventTime: "Recorded event time", + sourceCreationTime: "Source creation time", sourceStageCode: "Source stage code", sourceDetailStateCode: "Source detail-state code", responsibilityEvidence: "Observed responsibility evidence", @@ -234,12 +189,14 @@ const MESSAGES: Record> = { ko: { heading: "프로젝트 이벤트 타임라인", summaryCounts: "이벤트 {events}건 · 관찰된 담당자 {actors}명", - documentTime: "별도 사건 시각이 없어 날짜는 원천 게시물 생성 시각을 사용합니다.", + documentTime: "기록된 사건 시각을 우선 사용하고, 없으면 원천 생성 시각을 사용합니다.", truncated: "이 제한된 타임라인은 일부만 표시합니다. 선택한 이벤트는 계속 포함됩니다.", eventDetail: "이벤트 상세", eventType: "표시용 이벤트 유형", - eventDate: "원천 게시물 날짜", - timeBasisCode: "시간 기준 코드", + eventDate: "이벤트 날짜", + timeBasisCode: "시간 출처", + recordedEventTime: "기록된 사건 시각", + sourceCreationTime: "원천 생성 시각", sourceStageCode: "원천 단계 코드", sourceDetailStateCode: "원천 세부 상태 코드", responsibilityEvidence: "관찰된 담당 근거", @@ -274,12 +231,14 @@ const MESSAGES: Record> = { zh: { heading: "项目事件时间线", summaryCounts: "{events} 个事件 · {actors} 名已观察责任人", - documentTime: "未记录独立事件时钟,因此日期采用源帖子创建时间。", + documentTime: "优先使用已记录的事件时间;若无,则使用来源创建时间。", truncated: "此有界时间线已截断,但所选事件仍保留。", eventDetail: "事件详情", eventType: "显示事件类型", - eventDate: "源帖子日期", - timeBasisCode: "时间基准代码", + eventDate: "事件日期", + timeBasisCode: "时间来源", + recordedEventTime: "已记录的事件时间", + sourceCreationTime: "来源创建时间", sourceStageCode: "来源阶段代码", sourceDetailStateCode: "来源详细状态代码", responsibilityEvidence: "已观察的责任证据", @@ -314,12 +273,14 @@ const MESSAGES: Record> = { ja: { heading: "プロジェクトイベントのタイムライン", summaryCounts: "イベント {events}件 · 観察された担当者 {actors}名", - documentTime: "独立したイベント時刻がないため、原資料の作成時刻を使用します。", + documentTime: "記録されたイベント時刻を優先し、ない場合は原資料の作成時刻を使います。", truncated: "この上限付きタイムラインは省略されていますが、選択イベントは保持されます。", eventDetail: "イベント詳細", eventType: "表示用イベント種別", - eventDate: "原資料の日付", - timeBasisCode: "時間基準コード", + eventDate: "イベント日", + timeBasisCode: "時刻の出典", + recordedEventTime: "記録されたイベント時刻", + sourceCreationTime: "原資料の作成時刻", sourceStageCode: "ソース段階コード", sourceDetailStateCode: "ソース詳細状態コード", responsibilityEvidence: "観察された担当根拠", @@ -354,12 +315,14 @@ const MESSAGES: Record> = { vi: { heading: "Dòng thời gian sự kiện dự án", summaryCounts: "{events} sự kiện · {actors} người phụ trách được quan sát", - documentTime: "Không có đồng hồ sự kiện riêng, nên dùng thời gian tạo bài nguồn.", + documentTime: "Ưu tiên thời gian sự kiện đã ghi; nếu thiếu thì dùng thời gian tạo nguồn.", truncated: "Dòng thời gian có giới hạn này đã bị rút gọn nhưng vẫn giữ sự kiện đang chọn.", eventDetail: "Chi tiết sự kiện", eventType: "Loại sự kiện hiển thị", - eventDate: "Ngày bài nguồn", - timeBasisCode: "Mã cơ sở thời gian", + eventDate: "Ngày sự kiện", + timeBasisCode: "Nguồn thời gian", + recordedEventTime: "Thời gian sự kiện đã ghi", + sourceCreationTime: "Thời gian tạo nguồn", sourceStageCode: "Mã giai đoạn nguồn", sourceDetailStateCode: "Mã trạng thái chi tiết nguồn", responsibilityEvidence: "Bằng chứng trách nhiệm quan sát được", diff --git a/lineageweave/project_history.py b/lineageweave/project_history.py index c4e3e08cd..625ed91fc 100644 --- a/lineageweave/project_history.py +++ b/lineageweave/project_history.py @@ -19,6 +19,7 @@ PROJECT_HISTORY_CONTRACT_VERSION = 1 PROJECT_HISTORY_TIME_BASIS = "source_post_created_at_fallback" +PROJECT_HISTORY_DOCUMENT_TIME_BASIS = "document_time" PROJECT_HISTORY_MAX_DEPTH = 8 PROJECT_HISTORY_MAX_PATHS_PER_EVENT = 32 @@ -213,7 +214,7 @@ def build_project_history_projection( maximum_depth: int = PROJECT_HISTORY_MAX_DEPTH, maximum_paths_per_event: int = PROJECT_HISTORY_MAX_PATHS_PER_EVENT, ) -> dict[str, Any]: - """Build the versioned Buyer project-history projection. + """Build the versioned project-history projection. Inputs must already be visible, eligible, and within the requested cutoff. Duplicate source rows and role rows are collapsed deterministically. An @@ -230,11 +231,25 @@ def build_project_history_projection( for row in event_rows: event_id = str(row["post_id"]) current = deduplicated.get(event_id) - if current is None or (row["created_at"], event_id) < (current["created_at"], event_id): + row_clock = row.get("event_occurred_at") or row["created_at"] + current_clock = ( + current.get("event_occurred_at") or current["created_at"] + if current is not None + else None + ) + if current is None or (row_clock, row["created_at"], event_id) < ( + current_clock, + current["created_at"], + event_id, + ): deduplicated[event_id] = row ordered_rows = sorted( deduplicated.values(), - key=lambda row: (row["created_at"], str(row["post_id"])), + key=lambda row: ( + row.get("event_occurred_at") or row["created_at"], + row["created_at"], + str(row["post_id"]), + ), ) if not ordered_rows: raise ValueError("project history requires at least one visible event") @@ -333,6 +348,12 @@ def build_project_history_projection( previous_actor_keys: Sequence[str] | None = None for row in ordered_rows: event_id = str(row["post_id"]) + event_occurred_at = row.get("event_occurred_at") + time_basis_code = ( + PROJECT_HISTORY_DOCUMENT_TIME_BASIS + if event_occurred_at is not None + else PROJECT_HISTORY_TIME_BASIS + ) current_actor_keys = actor_keys_by_event[event_id] transition = ( None @@ -352,8 +373,8 @@ def build_project_history_projection( is_focus=event_id == effective_focus, ), "event_type_basis_code": "controlled_source_code", - "occurred_at": _as_utc(row["created_at"]), - "time_basis_code": PROJECT_HISTORY_TIME_BASIS, + "occurred_at": _as_utc(event_occurred_at or row["created_at"]), + "time_basis_code": time_basis_code, "voc_type_code": row.get("voc_type_code"), "source_stage_code": row.get("source_stage_code"), "source_detail_state_code": row.get("source_detail_state_code"), @@ -372,7 +393,11 @@ def build_project_history_projection( "normalized_project_key": normalized_key, "project_name": project_name, "focus_event_id": effective_focus, - "time_basis_code": PROJECT_HISTORY_TIME_BASIS, + "time_basis_code": ( + PROJECT_HISTORY_DOCUMENT_TIME_BASIS + if all(event["time_basis_code"] == PROJECT_HISTORY_DOCUMENT_TIME_BASIS for event in events) + else PROJECT_HISTORY_TIME_BASIS + ), "event_count": len(events), "distinct_observed_actor_count": len(distinct_actor_keys), "truncated": bool(truncated), diff --git a/tests/test_project_history.py b/tests/test_project_history.py index 364a382e2..c1882ef5e 100644 --- a/tests/test_project_history.py +++ b/tests/test_project_history.py @@ -1,4 +1,4 @@ -"""RED contracts for the Buyer project-history timeline.""" +"""RED contracts for the customer-facing project-history timeline.""" from __future__ import annotations diff --git a/tests/test_project_history_ingestion.py b/tests/test_project_history_ingestion.py index f36847cc9..b785eb1f9 100644 --- a/tests/test_project_history_ingestion.py +++ b/tests/test_project_history_ingestion.py @@ -24,6 +24,7 @@ async def fetch(self, query: str, *args: object): "post_id": "00000000-0000-0000-0000-000000000001", "post_title": "Synthetic project record", "created_at": datetime(2026, 1, 1, tzinfo=timezone.utc), + "event_occurred_at": datetime(2025, 12, 20, tzinfo=timezone.utc), "voc_type_code": None, "source_stage_code": "observed-stage", "source_detail_state_code": None, @@ -48,5 +49,8 @@ def test_project_history_query_binds_corporate_and_process_scopes() -> None: ) event_query, event_args = connection.calls[0] assert "post.process_unit_id::text = any($3::text[])" in event_query + assert "coalesce(post.event_occurred_at, post.created_at)" in event_query assert event_args[1:3] == (["corp-1"], ["pu-1"]) assert result["events"][0]["event_type_code"] == "source_recorded" + assert result["events"][0]["occurred_at"] == "2025-12-20T00:00:00Z" + assert result["events"][0]["time_basis_code"] == "document_time" From 7a7a7a6411e2b0de56e4299b47e50583ab769cea Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 26 Aug 2026 14:15:41 +0900 Subject: [PATCH 20/26] fix(projects): announce history loading state --- frontend/src/App.test.tsx | 52 +++++++++++++++++++++++++++++++-- frontend/src/App.tsx | 8 +++++ frontend/src/i18n.ts | 1 + lineageweave/project_history.py | 2 +- tests/test_project_history.py | 19 ++++++++++++ 5 files changed, 79 insertions(+), 3 deletions(-) diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 2dee4513d..ab9cef6b2 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -109,6 +109,7 @@ describe("App, authenticated", () => { pluralAffiliations?: boolean; deferMe?: boolean; deferPostOne?: boolean; + deferProjectHistory?: boolean; meFailed?: boolean; postBody?: string; manyCustomerHints?: number; @@ -121,7 +122,11 @@ describe("App, authenticated", () => { askImageCitation?: boolean; askDelivery?: boolean; lineageIsolationReason?: "comparison_candidates_available" | "no_comparison_group"; - }): ReturnType & { releaseMe: () => void; releasePostOne: () => void } { + }): ReturnType & { + releaseMe: () => void; + releasePostOne: () => void; + releaseProjectHistory: () => void; + } { const statusLabel: Record = { open: "Open", in_progress: "In progress", @@ -162,6 +167,13 @@ describe("App, authenticated", () => { }) : Promise.resolve(); + let releaseProjectHistory = () => {}; + const projectHistoryReady = options?.deferProjectHistory + ? new Promise((resolve) => { + releaseProjectHistory = resolve; + }) + : Promise.resolve(); + const fetchMock = vi.fn((input: RequestInfo | URL, init?: RequestInit) => { const url = String(input); const method = init?.method ?? "GET"; @@ -1130,6 +1142,23 @@ describe("App, authenticated", () => { }), ); } + const projectHistoryUrl = new URL(url, "https://backend.test"); + if (projectHistoryUrl.pathname === "/api/projects/source-project/history") { + return projectHistoryReady.then(() => + jsonResponse({ + contract_version: 1, + project_key: "source-project", + normalized_project_key: "source-project", + project_name: "Semantic project", + focus_event_id: "post-1", + time_basis_code: "document_time", + event_count: 0, + distinct_observed_actor_count: 0, + truncated: false, + events: [], + }), + ); + } const postsUrl = new URL(url, "https://backend.test"); if (postsUrl.pathname === "/api/posts") { return Promise.resolve( @@ -1945,9 +1974,28 @@ describe("App, authenticated", () => { return Promise.reject(new Error(`unexpected fetch: ${method} ${url}`)); }); vi.stubGlobal("fetch", fetchMock); - return Object.assign(fetchMock, { releaseMe, releasePostOne }); + return Object.assign(fetchMock, { releaseMe, releasePostOne, releaseProjectHistory }); } + it("announces the next step while project history is loading", async () => { + const backend = stubBackend({ deferProjectHistory: true }); + setLocale("ko"); + render(); + + await userEvent.click(await screen.findByRole("button", { name: "글 보기: Public post" })); + await userEvent.click(await screen.findByRole("button", { name: "Open project history: source-project" })); + + expect(screen.getByText("프로젝트 이력을 불러오는 중입니다. 표시되면 타임라인을 확인하세요.")).toHaveAttribute( + "role", + "status", + ); + + backend.releaseProjectHistory(); + await waitFor(() => + expect(screen.queryByText("프로젝트 이력을 불러오는 중입니다. 표시되면 타임라인을 확인하세요.")).not.toBeInTheDocument(), + ); + }); + it("renders safe Ask Agent evidence under each cited post", async () => { stubBackend(); render(); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3791addcc..482a2a69a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1834,6 +1834,7 @@ function PostDetailPopup({ const [focusTeam, setFocusTeam] = useState<{ teamId: string; teamName: string } | null>(null); const [projectHistory, setProjectHistory] = useState(null); const [projectHistoryError, setProjectHistoryError] = useState(null); + const [projectHistoryLoading, setProjectHistoryLoading] = useState(false); const projectHistoryRequestRef = useRef(0); const contentReloadRef = useRef<() => void>(() => undefined); const dialogRef = useRef(null); @@ -1844,11 +1845,14 @@ function PostDetailPopup({ const requestedPostId = postId; setProjectHistory(null); setProjectHistoryError(null); + setProjectHistoryLoading(true); try { const projection = await fetchProjectHistory(accessToken, projectKey, requestedPostId, knowledgeCutoff); if (projectHistoryRequestRef.current === requestId) setProjectHistory(projection); } catch (historyError) { if (projectHistoryRequestRef.current === requestId) setProjectHistoryError(String(historyError)); + } finally { + if (projectHistoryRequestRef.current === requestId) setProjectHistoryLoading(false); } } @@ -1863,6 +1867,7 @@ function PostDetailPopup({ dialogRef.current?.focus(); setProjectHistory(null); setProjectHistoryError(null); + setProjectHistoryLoading(false); projectHistoryRequestRef.current += 1; }, [postId, knowledgeCutoff]); @@ -2366,6 +2371,9 @@ function PostDetailPopup({ ))}
{projectHistoryError ?

{projectHistoryError}

: null} + {projectHistoryLoading ? ( +

{t("Loading project history. Review the timeline when it appears.")}

+ ) : null} {projectHistory ? ( >> = { "Authorized customer scope": "권한이 있는 고객 범위", "Customer entities available to this account.": "이 계정에서 사용할 수 있는 고객 엔터티입니다.", "Loading customer master...": "고객 마스터를 불러오는 중...", + "Loading project history. Review the timeline when it appears.": "프로젝트 이력을 불러오는 중입니다. 표시되면 타임라인을 확인하세요.", "Customer master could not be loaded.": "고객 마스터를 불러오지 못했습니다.", "No customer entities are connected to this account.": "이 계정에 연결된 고객 엔터티가 없습니다.", "Observed customer evidence": "관찰된 고객 증거", diff --git a/lineageweave/project_history.py b/lineageweave/project_history.py index 625ed91fc..bfa5334c2 100644 --- a/lineageweave/project_history.py +++ b/lineageweave/project_history.py @@ -131,7 +131,7 @@ def _prior_paths( maximum_depth: int, maximum_paths_per_event: int, ) -> dict[str, list[dict[str, Any]]]: - """Return deterministic shortest visible predecessor paths per event.""" + """Return one deterministic shortest visible predecessor path per source event.""" event_index = {event_id: index for index, event_id in enumerate(ordered_event_ids)} reverse_edges: dict[str, list[dict[str, Any]]] = {event_id: [] for event_id in ordered_event_ids} diff --git a/tests/test_project_history.py b/tests/test_project_history.py index c1882ef5e..8eedec3fe 100644 --- a/tests/test_project_history.py +++ b/tests/test_project_history.py @@ -5,6 +5,7 @@ import pytest from lineageweave.project_history import ( + _prior_paths, classify_project_event, normalize_project_key, responsibility_transition_code, @@ -49,3 +50,21 @@ def test_responsibility_transition_describes_document_evidence_only() -> None: assert responsibility_transition_code(["person:a"], ["person:a"]) == "continuous" assert responsibility_transition_code(["person:a"], ["person:b"]) == "handoff" assert responsibility_transition_code(["person:a"], []) == "assignment_gap" + + +def test_prior_paths_keep_the_first_deterministic_shortest_route_per_predecessor() -> None: + """A tied route reports one stable path for a prior event rather than duplicate history.""" + paths = _prior_paths( + ["award", "spec-a", "spec-b", "delivery"], + [ + {"parent_post_id": "award", "child_post_id": "spec-a", "fused_score": 0.9}, + {"parent_post_id": "award", "child_post_id": "spec-b", "fused_score": 0.8}, + {"parent_post_id": "spec-a", "child_post_id": "delivery", "fused_score": 0.7}, + {"parent_post_id": "spec-b", "child_post_id": "delivery", "fused_score": 0.6}, + ], + maximum_depth=8, + maximum_paths_per_event=32, + ) + + award_paths = [path for path in paths["delivery"] if path["source_event_id"] == "award"] + assert [path["event_ids"] for path in award_paths] == [["award", "spec-a", "delivery"]] From 8b4ed41d4d7c4242cf95f5c55dace0e9fbe3f127 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 26 Aug 2026 14:40:33 +0900 Subject: [PATCH 21/26] fix(projects): suppress transitions across gaps --- backend/app/project_history.py | 3 + ...idence-bound-project-history-projection.md | 5 ++ frontend/src/App.test.tsx | 2 +- frontend/src/i18n.test.ts | 13 +++ frontend/src/i18n.ts | 8 ++ lineageweave/project_history.py | 6 +- tests/test_project_history_ingestion.py | 81 +++++++++++++++++++ 7 files changed, 116 insertions(+), 2 deletions(-) diff --git a/backend/app/project_history.py b/backend/app/project_history.py index a18c2720f..c7252f57d 100644 --- a/backend/app/project_history.py +++ b/backend/app/project_history.py @@ -178,6 +178,7 @@ async def fetch_project_history_projection( ) truncated = len(rows) > limit event_rows = rows[:limit] + transition_suppressed_event_ids: set[str] = set() if not event_rows: raise ProjectHistoryNotFound(project_key) visible_ids = [str(row["post_id"]) for row in event_rows] @@ -196,6 +197,7 @@ async def fetch_project_history_projection( raise ProjectHistoryNotFound(project_key) truncated = True event_rows = (event_rows[: limit - 1] if limit > 1 else []) + [focus_rows[0]] + transition_suppressed_event_ids.add(str(focus_rows[0]["post_id"])) event_rows.sort( key=lambda row: ( row.get("event_occurred_at") or row["created_at"], @@ -218,6 +220,7 @@ async def fetch_project_history_projection( role_rows=role_rows, edge_rows=edge_rows, truncated=truncated, + transition_suppressed_event_ids=transition_suppressed_event_ids, ) diff --git a/docs/adr/0232-evidence-bound-project-history-projection.md b/docs/adr/0232-evidence-bound-project-history-projection.md index 3c6b2d406..45ef99599 100644 --- a/docs/adr/0232-evidence-bound-project-history-projection.md +++ b/docs/adr/0232-evidence-bound-project-history-projection.md @@ -29,6 +29,11 @@ document evidence only. Persisted Event Lineage paths are labelled related and non-causal. Dates use `source_post.event_occurred_at` when recorded and disclose `source_post.created_at` as the fallback clock. +Responsibility change is shown only when two displayed records are adjacent in +the authorized source ordering. If truncation retains a focus record but omits +intermediate records, that focus record has no responsibility-transition code; +the projection must not imply a direct handover or continuity across the gap. + The projection is bounded and declares truncation. A missing or unauthorized project is indistinguishable as HTTP 404. The Figma identifier records the design authority; Storybook remains the executable state inventory. diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index ab9cef6b2..f53507185 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -1983,7 +1983,7 @@ describe("App, authenticated", () => { render(); await userEvent.click(await screen.findByRole("button", { name: "글 보기: Public post" })); - await userEvent.click(await screen.findByRole("button", { name: "Open project history: source-project" })); + await userEvent.click(await screen.findByRole("button", { name: "프로젝트 이력 열기: source-project" })); expect(screen.getByText("프로젝트 이력을 불러오는 중입니다. 표시되면 타임라인을 확인하세요.")).toHaveAttribute( "role", diff --git a/frontend/src/i18n.test.ts b/frontend/src/i18n.test.ts index 18485aba9..e50904838 100644 --- a/frontend/src/i18n.test.ts +++ b/frontend/src/i18n.test.ts @@ -75,6 +75,8 @@ describe("i18n", () => { "Title overlap", "RankWeave fused newest-first and title-overlap ranks. This is not a calibrated score.", "Workspace navigation", + "Project history", + "Open project history: {name}", "Observed calendar events", "No observed calendar events are available.", "Open this observed occurrence. It is not a LineageWeave commitment.", @@ -126,6 +128,17 @@ describe("i18n", () => { expect(document.documentElement.lang).toBe(locale); }); + it.each([ + ["ko", "프로젝트 이력", "프로젝트 이력 열기: DEMO"], + ["zh", "项目历史", "打开项目历史:DEMO"], + ["ja", "プロジェクト履歴", "プロジェクト履歴を開く: DEMO"], + ["vi", "Lịch sử dự án", "Mở lịch sử dự án: DEMO"], + ] as const)("translates project history actions in %s", (locale, heading, action) => { + setLocale(locale); + expect(t("Project history")).toBe(heading); + expect(tf("Open project history: {name}", { name: "DEMO" })).toBe(action); + }); + it.each([ ["ko", "글"], ["zh", "文章"], diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index bd4b0bcca..9c4d4d23f 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -155,6 +155,8 @@ const TRANSLATIONS: Partial>> = { "Authorized customer scope": "권한이 있는 고객 범위", "Customer entities available to this account.": "이 계정에서 사용할 수 있는 고객 엔터티입니다.", "Loading customer master...": "고객 마스터를 불러오는 중...", + "Project history": "프로젝트 이력", + "Open project history: {name}": "프로젝트 이력 열기: {name}", "Loading project history. Review the timeline when it appears.": "프로젝트 이력을 불러오는 중입니다. 표시되면 타임라인을 확인하세요.", "Customer master could not be loaded.": "고객 마스터를 불러오지 못했습니다.", "No customer entities are connected to this account.": "이 계정에 연결된 고객 엔터티가 없습니다.", @@ -661,6 +663,8 @@ const TRANSLATIONS: Partial>> = { "Authorized customer scope": "已授权的客户范围", "Customer entities available to this account.": "此账户可用的客户实体。", "Loading customer master...": "正在加载客户主数据...", + "Project history": "项目历史", + "Open project history: {name}": "打开项目历史:{name}", "Customer master could not be loaded.": "无法加载客户主数据。", "No customer entities are connected to this account.": "此账户没有连接的客户实体。", "Observed customer evidence": "观测到的客户证据", @@ -1182,6 +1186,8 @@ const TRANSLATIONS: Partial>> = { "Authorized customer scope": "許可された顧客範囲", "Customer entities available to this account.": "このアカウントで利用できる顧客エンティティです。", "Loading customer master...": "顧客マスターを読み込んでいます...", + "Project history": "プロジェクト履歴", + "Open project history: {name}": "プロジェクト履歴を開く: {name}", "Customer master could not be loaded.": "顧客マスターを読み込めませんでした。", "No customer entities are connected to this account.": "このアカウントに接続された顧客エンティティはありません。", "Observed customer evidence": "観測された顧客証拠", @@ -1682,6 +1688,8 @@ const TRANSLATIONS: Partial>> = { "Authorized customer scope": "Phạm vi khách hàng được cấp quyền", "Customer entities available to this account.": "Các thực thể khách hàng mà tài khoản này được phép sử dụng.", "Loading customer master...": "Đang tải danh mục khách hàng...", + "Project history": "Lịch sử dự án", + "Open project history: {name}": "Mở lịch sử dự án: {name}", "Customer master could not be loaded.": "Không thể tải danh mục khách hàng.", "No customer entities are connected to this account.": "Tài khoản này chưa được kết nối với thực thể khách hàng nào.", "Observed customer evidence": "Bằng chứng khách hàng được quan sát", diff --git a/lineageweave/project_history.py b/lineageweave/project_history.py index bfa5334c2..264cfa4e5 100644 --- a/lineageweave/project_history.py +++ b/lineageweave/project_history.py @@ -211,6 +211,7 @@ def build_project_history_projection( role_rows: Sequence[Mapping[str, Any]], edge_rows: Sequence[Mapping[str, Any]], truncated: bool = False, + transition_suppressed_event_ids: set[str] | None = None, maximum_depth: int = PROJECT_HISTORY_MAX_DEPTH, maximum_paths_per_event: int = PROJECT_HISTORY_MAX_PATHS_PER_EVENT, ) -> dict[str, Any]: @@ -219,6 +220,8 @@ def build_project_history_projection( Inputs must already be visible, eligible, and within the requested cutoff. Duplicate source rows and role rows are collapsed deterministically. An observed source project name outranks an inferred semantic display name. + A transition is omitted for an event whose predecessor was excluded from + the supplied sequence, rather than treating the displayed rows as adjacent. """ normalized_key = normalize_project_key(project_key) @@ -258,6 +261,7 @@ def build_project_history_projection( effective_focus = focus_event_id or ordered_ids[-1] if effective_focus not in event_index: raise ValueError("focus event is not in the visible project history") + suppressed_transition_ids = transition_suppressed_event_ids or set() matches_by_event: dict[str, list[dict[str, Any]]] = {event_id: [] for event_id in ordered_ids} display_names: list[tuple[int, int, str, str]] = [] @@ -357,7 +361,7 @@ def build_project_history_projection( current_actor_keys = actor_keys_by_event[event_id] transition = ( None - if previous_actor_keys is None + if previous_actor_keys is None or event_id in suppressed_transition_ids else responsibility_transition_code(previous_actor_keys, current_actor_keys) ) events.append( diff --git a/tests/test_project_history_ingestion.py b/tests/test_project_history_ingestion.py index b785eb1f9..5817211af 100644 --- a/tests/test_project_history_ingestion.py +++ b/tests/test_project_history_ingestion.py @@ -54,3 +54,84 @@ def test_project_history_query_binds_corporate_and_process_scopes() -> None: assert result["events"][0]["event_type_code"] == "source_recorded" assert result["events"][0]["occurred_at"] == "2025-12-20T00:00:00Z" assert result["events"][0]["time_basis_code"] == "document_time" + + +def test_truncated_focus_does_not_claim_a_responsibility_transition_across_omitted_events() -> None: + """A retained focus event loses its transition when hidden events break adjacency.""" + + class _TruncatedConnection: + async def fetch(self, query: str, *args: object): + compact_query = " ".join(query.split()) + early = { + "post_id": "00000000-0000-0000-0000-000000000001", + "post_title": "Early record", + "created_at": datetime(2026, 1, 1, tzinfo=timezone.utc), + "event_occurred_at": None, + "voc_type_code": None, + "source_stage_code": None, + "source_detail_state_code": None, + } + omitted = { + "post_id": "00000000-0000-0000-0000-000000000002", + "post_title": "Omitted record", + "created_at": datetime(2026, 1, 2, tzinfo=timezone.utc), + "event_occurred_at": None, + "voc_type_code": None, + "source_stage_code": None, + "source_detail_state_code": None, + } + focus = { + "post_id": "00000000-0000-0000-0000-000000000003", + "post_title": "Focus record", + "created_at": datetime(2026, 1, 3, tzinfo=timezone.utc), + "event_occurred_at": None, + "voc_type_code": None, + "source_stage_code": None, + "source_detail_state_code": None, + } + if "post.post_id = $5::uuid" in compact_query: + return [focus] + if "limit $5" in compact_query: + return [early, omitted, focus] + if "from post_summary_role" in compact_query: + return [ + { + "post_id": early["post_id"], + "actor_name": "Early owner", + "responsibility": "Own early work", + "actor_type_code": "prov_person", + "affiliated_organization_name": None, + "cataloged_person_id": None, + "cataloged_team_id": None, + "cataloged_corporate_entity_id": None, + }, + { + "post_id": focus["post_id"], + "actor_name": "Focus owner", + "responsibility": "Own focus work", + "actor_type_code": "prov_person", + "affiliated_organization_name": None, + "cataloged_person_id": None, + "cataloged_team_id": None, + "cataloged_corporate_entity_id": None, + }, + ] + return [] + + result = asyncio.run( + fetch_project_history_projection( + _TruncatedConnection(), + project_key="P-100", + focus_post_id="00000000-0000-0000-0000-000000000003", + knowledge_cutoff=datetime(2026, 2, 1, tzinfo=timezone.utc), + corporate_entity_ids=["corp-1"], + process_unit_ids=["pu-1"], + limit=2, + ) + ) + + assert [event["event_id"] for event in result["events"]] == [ + "00000000-0000-0000-0000-000000000001", + "00000000-0000-0000-0000-000000000003", + ] + assert result["events"][-1]["responsibility_transition_code"] is None From dd7bd3f0125e56500a13be48a5dde08164dba8dd Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 26 Aug 2026 14:58:51 +0900 Subject: [PATCH 22/26] fix(i18n): localize project history loading guidance --- frontend/src/i18n.test.ts | 1 + frontend/src/i18n.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/frontend/src/i18n.test.ts b/frontend/src/i18n.test.ts index e50904838..2b7daa736 100644 --- a/frontend/src/i18n.test.ts +++ b/frontend/src/i18n.test.ts @@ -77,6 +77,7 @@ describe("i18n", () => { "Workspace navigation", "Project history", "Open project history: {name}", + "Loading project history. Review the timeline when it appears.", "Observed calendar events", "No observed calendar events are available.", "Open this observed occurrence. It is not a LineageWeave commitment.", diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 9c4d4d23f..5122d0473 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -665,6 +665,7 @@ const TRANSLATIONS: Partial>> = { "Loading customer master...": "正在加载客户主数据...", "Project history": "项目历史", "Open project history: {name}": "打开项目历史:{name}", + "Loading project history. Review the timeline when it appears.": "正在加载项目历史。显示后请查看时间线。", "Customer master could not be loaded.": "无法加载客户主数据。", "No customer entities are connected to this account.": "此账户没有连接的客户实体。", "Observed customer evidence": "观测到的客户证据", @@ -1188,6 +1189,7 @@ const TRANSLATIONS: Partial>> = { "Loading customer master...": "顧客マスターを読み込んでいます...", "Project history": "プロジェクト履歴", "Open project history: {name}": "プロジェクト履歴を開く: {name}", + "Loading project history. Review the timeline when it appears.": "プロジェクト履歴を読み込んでいます。表示されたらタイムラインを確認してください。", "Customer master could not be loaded.": "顧客マスターを読み込めませんでした。", "No customer entities are connected to this account.": "このアカウントに接続された顧客エンティティはありません。", "Observed customer evidence": "観測された顧客証拠", @@ -1690,6 +1692,7 @@ const TRANSLATIONS: Partial>> = { "Loading customer master...": "Đang tải danh mục khách hàng...", "Project history": "Lịch sử dự án", "Open project history: {name}": "Mở lịch sử dự án: {name}", + "Loading project history. Review the timeline when it appears.": "Đang tải lịch sử dự án. Khi dòng thời gian xuất hiện, hãy xem lại.", "Customer master could not be loaded.": "Không thể tải danh mục khách hàng.", "No customer entities are connected to this account.": "Tài khoản này chưa được kết nối với thực thể khách hàng nào.", "Observed customer evidence": "Bằng chứng khách hàng được quan sát", From afb7a19be6685be1c5d1f1f469e6a1607a9ed976 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 26 Aug 2026 17:34:42 +0900 Subject: [PATCH 23/26] docs(gaps): reconcile exact snapshot inventory --- docs/product-technical-gap-baseline.md | 28 -------------------------- 1 file changed, 28 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index d3178bb22..1ccfeba65 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,8 +1,6 @@ # Product & Technical Gap Baseline > Dashboard delivery snapshot: 2026-08-26 07:45 KST. Protected `main` was - -> Dashboard delivery snapshot: 2026-08-26 07:15 KST. Protected `main` was > `494b54e2245040bcf02b45376f221c37cd437e76`. This local branch is not > protected-main release evidence. @@ -70,15 +68,6 @@ unresolved threads, checks, approvals, rulesets, and merge SHA before any lifecycle claim. > Audit snapshot: 2026-08-26 07:45 KST (refreshed by the autonomous merge - -At this snapshot there were 11 open PRs and 10 open issues. PRs #660 and #659 -merged to protected `main`; PR #666 remains only non-default-branch stack -composition inside #663. Every remaining open head required refreshed hosted -gates and/or independent review after the base changed. These observations are -not merge readiness. Re-fetch exact heads, unresolved threads, checks, -approvals, rulesets, and merge SHA before any lifecycle claim. - -> Audit snapshot: 2026-08-26 07:15 KST (refreshed by the autonomous merge > loop). This repository records synthetic fixtures and aggregate, > non-identifying runtime evidence only. Open PRs and local checks are not > protected-default-branch release evidence. Identifying post identifiers, @@ -91,11 +80,6 @@ The protected default branch was `494b54e2245040bcf02b45376f221c37cd437e76` when this baseline was refreshed. The live queue contained 12 open PRs and 10 open issues in the newer 07:45 KST snapshot whose inventory is retained below. -when this baseline was refreshed. The live queue contained 11 open PRs and 10 -open issues. The exact-head inventory below supersedes older per-PR snapshots -elsewhere in this document; those older rows remain useful historical delivery -context only. - | PR | Exact observed head | Merge/check state at this snapshot | | ---: | --- | --- | | #668 | `5ef8db83` | ADR 0232 evidence-bound project-history API and post-detail timeline; exact-head checks/review required | @@ -111,18 +95,6 @@ context only. | #631 | `e6b4f0c4` | documentation decomposition; exact-head checks/review required | | #629 | `48496ff6` | provider-work release and bounded landing reads; exact-head checks/review required | -| #667 | `3bc662d7` | refreshes protected-main and open-queue documentation evidence; base conflict remains to be repaired | -| #663 | `6fd2f701` | combined Project ontology candidate plus #666's non-default-branch removal of sampled region-coverage arithmetic; base conflict remains to be repaired | -| #658 | `f007a5ed` | evidence-honest Global Ask cutoff; hosted checks and independent review required | -| #657 | `2d9b43b7` | TEPP asynchronous lifecycle persistence while unpublished producer work stays unavailable; hosted checks and independent review required | -| #644 | `ed8d97f3` | native frontend surface code splitting; hosted checks and independent review required | -| #643 | `7fb4d18c` | shared token-backed status notice; hosted checks and independent review required | -| #640 | `2d50fa01` | dashboard case metrics and project journeys; base conflict remains to be repaired | -| #639 | `48065ad1` | restores Running action and Compose contracts; hosted checks and independent review required | -| #632 | `29aee18d` | graph-fact provenance, public verification, MCP admission, and k6 evidence; hosted checks and independent review required | -| #631 | `665046dc` (observed parent) | decomposes closed PR #490; this merge refresh advances its head and restarts hosted review evidence | -| #629 | `0138db5f` | provider-work release and bounded landing reads refreshed onto protected `main`; hosted checks and independent review restarted | - No row above is merge evidence. Immediately before any lifecycle action, re-fetch the head, unresolved threads, formal reviews, rulesets, and same-head check conclusions. In particular, queued checks are infrastructure state and From f9c4bd6542b6d151969b7e629128ff04daa00363 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 26 Aug 2026 17:51:09 +0900 Subject: [PATCH 24/26] fix(project-history): retain explicit source identity --- docs/product-technical-gap-baseline.md | 7 ------- frontend/src/projectHistory.test.ts | 23 ++++++++++++++++++++++- frontend/src/projectHistory.ts | 5 ++--- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 1ccfeba65..7cb121e4f 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -362,13 +362,6 @@ this file per §3.5 of the prior snapshot). | #359 | Centralize Global Ask session storage access | Ask stack | | #363 | Continue ontology neighborhoods beyond the bounded source window | Protected `main` via #349; issue closed | | #372 | Reconcile lowercase and repository-case public namespace IRIs | Protected `main` via #616; issue closed, with term-kind hardening on #618 | - -| #271 | Evidence-honest knowledge-cutoff scope on Global Ask | #658; still open and not protected-main evidence | -| #272 | Verify Global Ask KG/ontology/semantic claims with public SearXNG evidence | #632 preserves internal provenance; public verification acceptance remains open | -| #277 | TEPP: persist accepted receipts, poll completed results, keep measurement authority distinct | #657 consumer lifecycle; executable producer route remains unavailable | -| #280 | Full project-lifecycle history and handover intervals | #640 adds case/project journeys and #663 adds evidence-backed Project exploration; authoritative lifecycle reconciliation remains #284 | -| #284 | Authoritative lifecycle ingestion and idempotent reconciliation | No active delivery PR confirmed | -| #338 | Evidence-bounded email/project lineage contract for Naruon consumption | Missing on protected `main`; #343 merged only into a non-default stack, while #355 is a distinct calendar-consumer contract and is not delivery evidence for email/project lineage | | #611 | Decompose closed PR #490 ADR 0133–0137 evidence without transferring stale branch state | #631 supplies the current-main inventory only; focused implementation PRs and tests for every unmet criterion are still required | ## 5. Open product and technical gaps diff --git a/frontend/src/projectHistory.test.ts b/frontend/src/projectHistory.test.ts index 19fc384c4..95895fa7e 100644 --- a/frontend/src/projectHistory.test.ts +++ b/frontend/src/projectHistory.test.ts @@ -16,7 +16,28 @@ describe("projectHistoryKeys", () => { ).toEqual(["P-100"]); }); - it("uses a source fallback only when project evidence is empty", () => { + it("uses a source identity when project evidence is empty", () => { expect(projectHistoryKeys([], " ", " P-200 ")).toEqual([" P-200 "]); }); + + it("keeps a distinct explicit source identity beside semantic evidence", () => { + expect( + projectHistoryKeys( + [ + { + project_key: "semantic-project", + project_name: "Semantic project", + evidence: "semantic", + confidence: null, + ontology_iri: "", + extraction_method: "semantic", + resolution_status: "resolved", + provenance: "test", + }, + ], + "SOURCE-200", + "Source project", + ), + ).toEqual(["semantic-project", "SOURCE-200"]); + }); }); diff --git a/frontend/src/projectHistory.ts b/frontend/src/projectHistory.ts index fb846f251..afd1c6dbc 100644 --- a/frontend/src/projectHistory.ts +++ b/frontend/src/projectHistory.ts @@ -81,9 +81,8 @@ export function projectHistoryKeys( sourceProjectCode: string | null | undefined, sourceProjectName: string | null | undefined, ): string[] { - const candidates = evidence?.length - ? evidence.map((project) => project.project_key) - : [sourceProjectCode?.trim() ? sourceProjectCode : sourceProjectName ?? ""]; + const sourceIdentity = sourceProjectCode?.trim() ? sourceProjectCode : sourceProjectName ?? ""; + const candidates = [...(evidence ?? []).map((project) => project.project_key), sourceIdentity]; const seen = new Set(); return candidates.filter((candidate) => { const normalized = normalizeProjectIdentity(candidate); From 1194f44d87f465519ef1db0715040473ce3cd3cf Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 26 Aug 2026 20:03:07 +0900 Subject: [PATCH 25/26] docs(adr): reserve project history identity 0243 --- CHANGELOG.md | 2 +- ...md => 0243-evidence-bound-project-history-projection.md} | 2 +- docs/adr/README.md | 2 +- docs/product-technical-gap-baseline.md | 6 +++--- docs/storybook-inventory.md | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) rename docs/adr/{0232-evidence-bound-project-history-projection.md => 0243-evidence-bound-project-history-projection.md} (97%) diff --git a/CHANGELOG.md b/CHANGELOG.md index d02027a31..47f457c87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ All notable changes to this project are documented here. Format follows ### Added - An evidence-bound project-history projection and post-detail timeline - (ADR 0232 / #280) reuse normalized project, responsibility, and Event + (ADR 0243 / #280) reuse normalized project, responsibility, and Event Lineage rows under RBAC/ABAC and cutoff filtering. Free text never creates a lifecycle fact; unsupported authoritative ingestion remains issue #284. - Persist explicit paragraph, list, table, MathML formula, and caller-parsed diff --git a/docs/adr/0232-evidence-bound-project-history-projection.md b/docs/adr/0243-evidence-bound-project-history-projection.md similarity index 97% rename from docs/adr/0232-evidence-bound-project-history-projection.md rename to docs/adr/0243-evidence-bound-project-history-projection.md index 45ef99599..295bc5e85 100644 --- a/docs/adr/0232-evidence-bound-project-history-projection.md +++ b/docs/adr/0243-evidence-bound-project-history-projection.md @@ -1,4 +1,4 @@ -# ADR 0232: Evidence-bound project history projection +# ADR 0243: Evidence-bound project history projection - Status: Accepted - Date: 2026-08-26 diff --git a/docs/adr/README.md b/docs/adr/README.md index e3f53f661..106632e6c 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -17,7 +17,7 @@ decision from them. | [`ONTOLOGY_NAMESPACE_INVENTORY.md`](../doctoring/ONTOLOGY_NAMESPACE_INVENTORY.md) | [0207](0207-repository-case-ontology-namespace-canonical.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), [0184](0184-ontology-provenance-explorer.md), [0222](0222-project-nodes-in-ontology-neighborhood.md) | -| Project-history timeline | [0232](0232-evidence-bound-project-history-projection.md) | +| Project-history timeline | [0243](0243-evidence-bound-project-history-projection.md) | | [`POSTGRESQL_CONCURRENCY_REFERENCES.md`](../doctoring/POSTGRESQL_CONCURRENCY_REFERENCES.md) | [0204](0204-analysis-run-short-transaction-delivery.md), [0213](0213-global-ask-embedding-pool-release.md) | | [`GLOBAL_ASK_PUBLIC_VERIFICATION_REFERENCES.md`](../doctoring/GLOBAL_ASK_PUBLIC_VERIFICATION_REFERENCES.md) | [0215](0215-global-ask-public-claim-verification.md) | | [`GLOBAL_ASK_KNOWLEDGE_CUTOFF_REFERENCES.md`](../doctoring/GLOBAL_ASK_KNOWLEDGE_CUTOFF_REFERENCES.md) | [0216](0216-global-ask-knowledge-cutoff.md) | diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 7cb121e4f..c6417633a 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -11,7 +11,7 @@ | Claim cause delay: order, specification change, originating order, sales pool, Event/post counts | ADR 0206; contextual-orchestrator case classification with cited spans; Event Lineage context | Candidate implementation; authenticated runtime acceptance pending | | Rebid/handover: discussion, counterparties, our owner, decisions, Event/post counts | ADR 0206; normalized case facts plus persisted summary actions/roles | Candidate implementation; corpus backfill pending | | External information count/rate and sales/project relation | ADR 0206; semantic `external_information` classification inside Dashboard GNB | Candidate implementation; no separate Board by product decision | -| Project-specific journey | ADR 0232; exact source/semantic project membership, source-post clock disclosure, responsibility evidence, and non-causal Event Lineage paths | PR #668 candidate API and post-detail timeline; exact-head review, protected merge, and authenticated runtime acceptance pending | +| Project-specific journey | ADR 0243; exact source/semantic project membership, source-post clock disclosure, responsibility evidence, and non-causal Event Lineage paths | PR #668 candidate API and post-detail timeline; exact-head review, protected merge, and authenticated runtime acceptance pending | | Repeat issue to design improvement | `repeat_issue`, `issue_pattern`, and `improvement_action` cited facts | Candidate semantic contract; design-system connector acceptance pending | | Natural-language Ask with evidence, report, alert, MCP | Persisted semantic-unit embeddings plus versioned delivery/resource contract | Candidate implementation uses whole-question embedding retrieval with no lexical fallback; authenticated runtime acceptance pending | | Similar VOC, customer cohort, prior action | Persisted repeat-issue candidate semantics plus orchestrator pair adjudication and extractive evidence | Candidate live post endpoint and post-detail UI implemented; authenticated runtime acceptance pending | @@ -82,7 +82,7 @@ open issues in the newer 07:45 KST snapshot whose inventory is retained below. | PR | Exact observed head | Merge/check state at this snapshot | | ---: | --- | --- | -| #668 | `5ef8db83` | ADR 0232 evidence-bound project-history API and post-detail timeline; exact-head checks/review required | +| #668 | `5ef8db83` | ADR 0243 evidence-bound project-history API and post-detail timeline; exact-head checks/review required | | #667 | `3bf57fed` | gap-baseline refresh; exact-head checks/review required | | #663 | `d5edd2b9` | project ontology traversal plus cutoff/snapshot-frozen project focus and labels; exact-head checks/review required | | #658 | `fe830b0a` | evidence-honest Global Ask cutoff with revision-interval live-after semantics; exact-head checks/review required | @@ -353,7 +353,7 @@ this file per §3.5 of the prior snapshot). | #274 | Persist and explain Event Lineage channel evidence | #387 | | #277 | TEPP: persist accepted receipts, poll completed results, keep measurement authority distinct | #468, #417 | | #280 | Evidence-bound project history with responsibility changes and related prior paths | #668; read projection only, exact-head protected delivery pending | -| #284 | Authoritative lifecycle ingestion and idempotent reconciliation | No owned source-adapter contract or active delivery PR; ADR 0232 forbids the read projection from inventing this authority | +| #284 | Authoritative lifecycle ingestion and idempotent reconciliation | No owned source-adapter contract or active delivery PR; ADR 0243 forbids the read projection from inventing this authority | | #289 | Activate the optional lineage LLM channel through a bounded asynchronous rebuild | #434 | | #336 | Replace pseudo-CalDAV feed with a Naruon-owned calendar projection | Contract on `main` (#355); operator consume wiring in historical branch `feat/naruon-calendar-buyer-wiring-v2170` | | #338 | Evidence-bounded email/project lineage contract for Naruon consumption | #355 | diff --git a/docs/storybook-inventory.md b/docs/storybook-inventory.md index e26bb9107..67ab2562b 100644 --- a/docs/storybook-inventory.md +++ b/docs/storybook-inventory.md @@ -39,7 +39,7 @@ World Wide Web Consortium. (2024). *Web Content Accessibility Guidelines `Projects/History Timeline` covers the evidence-bearing default timeline and its exact-value table. Keyboard roving focus, the current event, responsibility-evidence gaps, non-causal lineage paths, and source-record -actions are executable component-test states governed by ADR 0232. The +actions are executable component-test states governed by ADR 0243. The 1440×1000 and 390×844 audits are retained in `docs/screenshots/project-history-time-source-{desktop,mobile}.png`; both show the customer-readable time source without exposing the stored basis code. From 234f975ba5b0982e22d918219b505a9cd6a103e2 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 27 Aug 2026 05:36:11 +0900 Subject: [PATCH 26/26] fix(ui): keep project history storage fields internal --- .../src/components/ProjectHistoryTimeline.tsx | 4 ++- frontend/src/projectHistory.test.ts | 19 +++++++++++- frontend/src/projectHistory.ts | 31 ++++++++++++++++++- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/ProjectHistoryTimeline.tsx b/frontend/src/components/ProjectHistoryTimeline.tsx index ee3cb3210..bc83a42da 100644 --- a/frontend/src/components/ProjectHistoryTimeline.tsx +++ b/frontend/src/components/ProjectHistoryTimeline.tsx @@ -5,6 +5,7 @@ import { type ProjectHistoryEvent, type ProjectHistoryProjection, projectHistoryEventTypeLabel, + projectHistoryMatchSourceLabel, projectHistoryText, projectHistoryTransitionLabel, } from "../projectHistory"; @@ -278,7 +279,8 @@ export function ProjectHistoryTimeline({
    {selectedEvent.project_matches.map((match) => (
  • - {match.matched_value} · {match.provenance} ·{" "} + {match.matched_value} ·{" "} + {projectHistoryMatchSourceLabel(locale, match.provenance)} ·{" "} {projectHistoryText(locale, match.truth_status_code)}
  • ))} diff --git a/frontend/src/projectHistory.test.ts b/frontend/src/projectHistory.test.ts index 95895fa7e..10c0fbf6c 100644 --- a/frontend/src/projectHistory.test.ts +++ b/frontend/src/projectHistory.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it } from "vitest"; -import { projectHistoryKeys } from "./projectHistory"; +import { + projectHistoryEventTypeLabel, + projectHistoryKeys, + projectHistoryMatchSourceLabel, +} from "./projectHistory"; describe("projectHistoryKeys", () => { it("deduplicates compatibility- and case-normalized identities", () => { @@ -40,4 +44,17 @@ describe("projectHistoryKeys", () => { ), ).toEqual(["semantic-project", "SOURCE-200"]); }); + + it("keeps storage fields and unknown event codes out of customer labels", () => { + expect(projectHistoryMatchSourceLabel("en", "source_post.source_project_name")).toBe( + "Source record", + ); + expect(projectHistoryMatchSourceLabel("en", "post_project_mention.project_name")).toBe( + "Supporting record", + ); + expect(projectHistoryMatchSourceLabel("en", "future_table.future_column")).toBe( + "Recorded evidence", + ); + expect(projectHistoryEventTypeLabel("en", "future_event_code")).toBe("Source record"); + }); }); diff --git a/frontend/src/projectHistory.ts b/frontend/src/projectHistory.ts index afd1c6dbc..4dceb6fbd 100644 --- a/frontend/src/projectHistory.ts +++ b/frontend/src/projectHistory.ts @@ -114,6 +114,9 @@ const MESSAGE_KEYS = [ "noPriorHistory", "inferredBoundary", "projectEvidence", + "sourceRecordEvidence", + "supportingRecordEvidence", + "recordedEvidence", "observed", "inferred", "openSourceRecord", @@ -162,6 +165,9 @@ const EN: Record = { noPriorHistory: "No visible prior lineage path is recorded for this event.", inferredBoundary: "This is inferred related history, not causality or an authoritative assignment record.", projectEvidence: "Project identity evidence", + sourceRecordEvidence: "Source record", + supportingRecordEvidence: "Supporting record", + recordedEvidence: "Recorded evidence", observed: "Observed", inferred: "Inferred", openSourceRecord: "Open source record: {title}", @@ -207,6 +213,9 @@ const MESSAGES: Record> = { noPriorHistory: "이 이벤트로 이어지는 공개 가능한 이전 계보가 없습니다.", inferredBoundary: "이는 추론된 관련 이력이며 인과관계나 권위 있는 인사 배정 기록이 아닙니다.", projectEvidence: "프로젝트 식별 근거", + sourceRecordEvidence: "원천 기록", + supportingRecordEvidence: "뒷받침 기록", + recordedEvidence: "기록된 근거", observed: "관찰됨", inferred: "추론됨", openSourceRecord: "원천 기록 열기: {title}", @@ -249,6 +258,9 @@ const MESSAGES: Record> = { noPriorHistory: "此事件没有可见的既往谱系路径。", inferredBoundary: "这是推断的相关历史,并非因果关系或权威任命记录。", projectEvidence: "项目身份依据", + sourceRecordEvidence: "来源记录", + supportingRecordEvidence: "支持记录", + recordedEvidence: "已记录依据", observed: "已观察", inferred: "已推断", openSourceRecord: "打开源记录:{title}", @@ -291,6 +303,9 @@ const MESSAGES: Record> = { noPriorHistory: "このイベントに至る可視の過去系譜はありません。", inferredBoundary: "これは推論された関連履歴であり、因果関係や権威ある配属記録ではありません。", projectEvidence: "プロジェクト識別根拠", + sourceRecordEvidence: "元レコード", + supportingRecordEvidence: "根拠レコード", + recordedEvidence: "記録された根拠", observed: "観察済み", inferred: "推論済み", openSourceRecord: "原資料を開く: {title}", @@ -333,6 +348,9 @@ const MESSAGES: Record> = { noPriorHistory: "Không có đường dẫn lịch sử trước đó khả kiến cho sự kiện này.", inferredBoundary: "Đây là lịch sử liên quan được suy luận, không phải quan hệ nhân quả hay hồ sơ phân công có thẩm quyền.", projectEvidence: "Bằng chứng nhận dạng dự án", + sourceRecordEvidence: "Bản ghi nguồn", + supportingRecordEvidence: "Bản ghi hỗ trợ", + recordedEvidence: "Bằng chứng đã ghi nhận", observed: "Đã quan sát", inferred: "Đã suy luận", openSourceRecord: "Mở bản ghi nguồn: {title}", @@ -378,7 +396,18 @@ export function projectHistoryEventTypeLabel(locale: Locale, code: string): stri source_recorded: "sourceRecorded", }; const key = keyByCode[code]; - return key ? projectHistoryText(locale, key) : code; + return projectHistoryText(locale, key ?? "sourceRecorded"); +} + +/** Return a customer label for a persisted project-match provenance field. */ +export function projectHistoryMatchSourceLabel(locale: Locale, provenance: string): string { + if (provenance.startsWith("source_post.")) { + return projectHistoryText(locale, "sourceRecordEvidence"); + } + if (provenance.startsWith("post_project_mention.")) { + return projectHistoryText(locale, "supportingRecordEvidence"); + } + return projectHistoryText(locale, "recordedEvidence"); } export function projectHistoryTransitionLabel(