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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,7 @@ jobs:
- name: Build
working-directory: frontend
run: pnpm run build

- name: Build Storybook
working-directory: frontend
run: pnpm run build-storybook
6 changes: 6 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,12 @@ Demo Analyst and Demo Admin see "Lineage reconstruction · Succeeded ·
Demo Corp" with "3 documents" and Pending / Running / Succeeded times,
and "TEPP measurement · Failed · Demo Corp" whose detail history ends
in Failed / `tepp_not_available`.
A run-bearing registry is emptied only after
`SET ROLE analysis_run_retention_admin` and
`purge_analysis_run_registry('approved-retention-purge')` (ADR 0018);
a raw `DELETE` and a runtime role that only knows the public phrase
stay rejected. Repeated chip and close controls use
`frontend/src/styles/tokens.css` and the Storybook inventory.

## Phase 6a: fast-mlsirm dependency + Rust toolchain (infra only)

Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.d/0.86.0-retention-purge-and-tokens.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# 0.86.0 retention purge and design tokens

Operators can empty a run-bearing analysis-run registry without a superuser
trigger disable: grant `analysis_run_retention_admin`, `SET ROLE` that role,
then `select purge_analysis_run_registry('approved-retention-purge')`.
Export `analysis_run_retention_event` (session role is recorded), delete
those rows, then roll back 0019 and 0018. A raw DELETE and a runtime role
that only knows the public phrase still fail.

Designers can change chip and close-button appearance in
`frontend/src/styles/tokens.css` and preview the next click in Storybook
(`cd frontend && pnpm run storybook`).
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ All notable changes to this project are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.86.0] - 2026-08-16

### Added

- Operators can empty a run-bearing analysis-run registry without a
superuser trigger disable. Grant `analysis_run_retention_admin`,
`SET ROLE` that role, then run
`select purge_analysis_run_registry('approved-retention-purge')`.
Export `analysis_run_retention_event` (it records the invoking
session role), delete those rows, then roll back 0019 and 0018. A
raw `DELETE` and a runtime role that only knows the public phrase
still fail (ADR 0018).
- Repeated chip and close-button styles live in
`frontend/src/styles/tokens.css`. Open
`cd frontend && pnpm run storybook` and click a citation chip or
close control before changing product CSS.

## [0.84.0] - 2026-08-16

### Added
Expand Down
9 changes: 9 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
Tool-specific pointer. Policy lives in [AGENTS.md](AGENTS.md) and the
ADRs under `docs/adr/`. Do not fork those rules here.

## Analysis-run retention (v0.86.0)

To empty a run-bearing registry before rolling back 0018, grant
`analysis_run_retention_admin`, `SET ROLE analysis_run_retention_admin`,
run `select purge_analysis_run_registry('approved-retention-purge')`,
export `analysis_run_retention_event`, delete those rows, then roll
back 0019 and 0018. Do not `DISABLE TRIGGER` as superuser. Do not grant
the retention role to the application `DATABASE_URL` login.

## Analysis-run seed (v0.84.0)

`make seed` writes a Demo Corp lineage run and a TEPP run on the same
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ FastAPI backend over real `fetch()` with the token Keycloak issued.
make up
make seed
cd frontend && cp .env.example .env.local && pnpm install && pnpm run dev
# Repeated chip/close controls: pnpm run storybook
# (Node 24 via frontend/mise.toml; pnpm only)
# -> http://localhost:5173, click "Log in", redirects through the real
# Keycloak login page for demo.analyst / lineageweave-demo-only
```
Expand Down
2 changes: 2 additions & 0 deletions backend/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
_REALM = "lineageweave-demo"
_MIGRATION_PATH = Path(__file__).resolve().parents[2] / "migrations" / "0001_initial_schema.sql"
_REGISTRY_MIGRATION = Path(__file__).resolve().parents[2] / "migrations" / "0018_analysis_run_registry.sql"
_RETENTION_MIGRATION = Path(__file__).resolve().parents[2] / "migrations" / "0019_analysis_run_retention_purge.sql"


def _postgres_available() -> bool:
Expand Down Expand Up @@ -114,6 +115,7 @@ def seeded_db(demo_analyst_token):
with conn.cursor() as cur:
cur.execute(_MIGRATION_PATH.read_text())
cur.execute(_REGISTRY_MIGRATION.read_text())
cur.execute(_RETENTION_MIGRATION.read_text())
cur.execute(
"insert into common_lookup_value (lookup_category, lookup_code, lookup_label) values "
"('corporate_entity_level', 'group', 'Group'), "
Expand Down
1 change: 1 addition & 0 deletions docker/postgres-init/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ COPY migrations/0015_organization_name_resolution.sql /docker-entrypoint-initdb.
COPY migrations/0016_cross_post_actor_identity.sql /docker-entrypoint-initdb.d/17-cross-post-actor-identity.sql
COPY migrations/0017_prov_o_standard_relations.sql /docker-entrypoint-initdb.d/18-prov-o-standard-relations.sql
COPY migrations/0018_analysis_run_registry.sql /docker-entrypoint-initdb.d/19-analysis-run-registry.sql
COPY migrations/0019_analysis_run_retention_purge.sql /docker-entrypoint-initdb.d/20-analysis-run-retention-purge.sql
# Official image already drops to this account at runtime; declare it so
# the Dockerfile itself satisfies DS-0002 (explicit non-root USER).
USER postgres
8 changes: 5 additions & 3 deletions docs/adr/0013-normalized-analysis-run-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,11 @@ functional dependency and forces duplicate snapshots.

Migration replay is idempotent and rejects lookup-category collisions. The
rollback refuses to remove non-empty registry relations. Evidence must first be
exported or explicitly deleted under an approved retention procedure. An empty
rollback removes the view, tables, functions, and lookup rows and is itself
replayable.
exported, then emptied with `purge_analysis_run_registry('approved-retention-purge')`
(ADR 0018 / migration 0019). A raw `DELETE` of a run, scope, or status event
stays rejected. After purge, export and delete `analysis_run_retention_event`,
roll back 0019, then roll back 0018. An empty 0018 rollback removes the view,
tables, functions, and lookup rows and is itself replayable.

## Verification

Expand Down
74 changes: 74 additions & 0 deletions docs/adr/0018-analysis-run-retention-purge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# ADR 0018 — Approved retention purge empties an immutable analysis-run registry

**Decision status:** Accepted on this active PR; not protected-main truth until merge
**Date:** 2026-08-16
**Depends on:** ADR 0013 normalized analysis-run registry

## Context

ADR 0013 and migration `0018` make `analysis_run`, `analysis_run_scope`, and
`analysis_run_status_event` immutable. The 0018 rollback refuses to drop
non-empty registry relations and tells operators to export or delete evidence
under an approved retention procedure.

After the first `analysis_run` insert, a raw `DELETE` is rejected. Snapshot
delete is then blocked by the foreign key. Operators following the documented
procedure cannot satisfy `analysis_run_registry_not_empty` without a superuser
`DISABLE TRIGGER`. That is not a supported product path (ISO 15489-1:2016
disposition; NIST SP 800-92 protected audit records).

## Decision

Migration `0019_analysis_run_retention_purge.sql` adds:

- `purge_analysis_run_registry(approval_token text)` — `SECURITY DEFINER`,
accepts only `approved-retention-purge`, disables the three immutability
delete triggers inside that call, deletes in FK order, re-enables the
triggers, and writes one `analysis_run_retention_event`;
- `REVOKE ALL … FROM PUBLIC` plus `GRANT EXECUTE` to
`analysis_run_retention_admin` — the documented phrase is a procedure
name, not an authorization secret (NIST SP 800-53 Rev. 5 AC-3);
- `analysis_run_retention_event` — purged run/snapshot counts, the SHA-256
of the approval token, `invoking_session_role`, `invoking_current_role`,
and optional `client_network_address`. The raw phrase is never stored.

A session `SET` cannot authorize a raw `DELETE`. A table-DML runtime role
cannot call the function. After purge, export the retention event, delete
those rows, roll back 0019, then roll back 0018.

## Consequences

- A run-bearing registry can be emptied without superuser trigger disable.
- Retention remains an explicit, audited operator action, not a silent
downgrade.
- 0018 rollback stays fail-closed until the registry tables are empty.
- Production `DATABASE_URL` must not be a superuser and must not be granted
`analysis_run_retention_admin`.

## Follow-up

Bind the approval token to an authenticated administrator grant table when
the write API exists. Do not expose purge on a public HTTP route. Split the
application login from the migration owner so the product role cannot
execute the function even as table owner.

## References — APA 7th

American Institute of Certified Public Accountants. (2017). *SOC 2®: SOC
for Service Organizations: Trust Services Criteria*.

International Organization for Standardization. (2016). *ISO 15489-1:2016:
Information and documentation—Records management—Part 1: Concepts and
principles*.

Kent, K., & Souppaya, M. (2006). *Guide to computer security log management*
(NIST Special Publication 800-92). National Institute of Standards and
Technology. https://doi.org/10.6028/NIST.SP.800-92

National Institute of Standards and Technology. (2020). *Security and
privacy controls for information systems and organizations* (NIST Special
Publication 800-53 Rev. 5). https://doi.org/10.6028/NIST.SP.800-53r5

PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation:
5.8. Privileges*.
https://www.postgresql.org/docs/current/ddl-priv.html
21 changes: 18 additions & 3 deletions docs/doctoring/ANALYSIS_RUN_REGISTRY_REFERENCES.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Analysis-run registry standards and research traceability

**Status:** Active PR evidence; not protected-main truth until merge.
**Scope:** Migration 0018, ADR 0013, rollback, and real-PostgreSQL contract tests.
**Scope:** Migrations 0018–0019, ADR 0013 / 0018, rollback, and real-PostgreSQL contract tests.

## Standards mapped to implementation

Expand All @@ -11,7 +11,8 @@
| W3C Time Ontology in OWL | Keep temporal concepts explicit and avoid collapsing distinct clocks. | Evidence availability and snapshot capture remain on `analysis_source_snapshot`; analysis knowledge cutoff and request time remain on `analysis_run`; status occurrence and database record time remain distinct. `GET /api/analysis-runs/{id}` visible posts apply `created_at <= knowledge_cutoff` (ADR 0016). |
| ISO 8601-1:2019 | Use unambiguous timestamp representation and timezone-aware persistence. | PostgreSQL `timestamptz` for availability, capture, cutoff, request, occurrence, and record clocks; tests use explicit `Z` offsets. |
| PostgreSQL 18 constraints and trigger contracts | Put integrity close to durable truth and use constraints for row shape while triggers enforce cross-row state and serialization. | Digest/check constraints, category allowlists, account-scoped uniqueness, shape constraints, immutable-row triggers, shared snapshot-row locking, and serialized status transitions. |
| NIST SP 800-92 | Treat audit records as bounded, protected operational evidence rather than unstructured application logging. | Append-only status events, machine failure codes, actor identity, occurrence/record clocks, fail-closed rollback, and exclusion of raw source/provider payloads. |
| NIST SP 800-92 | Treat audit records as bounded, protected operational evidence rather than unstructured application logging. | Append-only status events, machine failure codes, actor identity, occurrence/record clocks, fail-closed rollback, `invoking_session_role` on each retention event, and exclusion of raw source/provider payloads. |
| NIST SP 800-53 Rev. 5 AC-3 | Enforce least privilege on privileged procedures; a well-known procedure name is not an authorization secret. | `REVOKE ALL` on `purge_analysis_run_registry` from `PUBLIC`; `GRANT EXECUTE` only to `analysis_run_retention_admin`. |
| OpenAPI 3.2.0 | Define explicit versioned API schemas rather than exposing database rows or implementation-specific payloads. | API intentionally deferred; ADR 0013 requires a source-redacting run list/detail contract before a product surface is claimed. |

## Temporal reasoning
Expand Down Expand Up @@ -74,10 +75,17 @@ provenance, retention, and immutable evidence rather than blanket masking.
| Request identity is stable | Reject analysis-run updates; scope and lifecycle live in their own relations. |
| Idempotency is actor-scoped | Permit identical opaque keys for two accounts and reject reuse by the same account. |
| Lifecycle is ordered | Require pending first, contiguous ordinals, monotonic time, legal transitions, terminal finality, and append-only rows. |
| Rollback does not erase audit data silently | Reject rollback with any registry rows and allow replay after explicit cleanup. |
| Rollback does not erase audit data silently | Reject 0018 rollback with any registry rows. A run-bearing registry empties only through `SET ROLE analysis_run_retention_admin` then `purge_analysis_run_registry('approved-retention-purge')`; a wrong token, a raw `DELETE`, and a runtime role that only knows the public phrase stay rejected. Export then delete `analysis_run_retention_event` before 0019 rollback. |

## APA 7th references

American Institute of Certified Public Accountants. (2017). *SOC 2®: SOC
for Service Organizations: Trust Services Criteria*.

International Organization for Standardization. (2016). *ISO 15489-1:2016:
Information and documentation—Records management—Part 1: Concepts and
principles*.

International Organization for Standardization. (2019). *ISO 8601-1:2019: Date
and time—Representations for information interchange—Part 1: Basic rules*
(confirmed 2024; Amendment 1:2022).
Expand All @@ -86,6 +94,10 @@ Kent, K., & Souppaya, M. (2006). *Guide to computer security log management*
(NIST Special Publication 800-92). National Institute of Standards and
Technology. https://doi.org/10.6028/NIST.SP.800-92

National Institute of Standards and Technology. (2020). *Security and
privacy controls for information systems and organizations* (NIST Special
Publication 800-53 Rev. 5). https://doi.org/10.6028/NIST.SP.800-53r5

Moreau, L., & Missier, P. (Eds.). (2013). *PROV-DM: The PROV data model*.
World Wide Web Consortium. https://www.w3.org/TR/prov-dm/

Expand All @@ -95,6 +107,9 @@ https://spec.openapis.org/oas/v3.2.0.html
PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation:
5.5. Constraints*. https://www.postgresql.org/docs/current/ddl-constraints.html

PostgreSQL Global Development Group. (2026). *PostgreSQL 18 documentation:
5.8. Privileges*. https://www.postgresql.org/docs/current/ddl-priv.html

World Wide Web Consortium. (2013). *PROV-O: The PROV ontology* (W3C
Recommendation). https://www.w3.org/TR/prov-o/

Expand Down
20 changes: 20 additions & 0 deletions docs/doctoring/DESIGN_TOKEN_REFERENCES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Design-token and Storybook traceability

**Status:** Active PR evidence; not protected-main truth until merge.
**Scope:** `frontend/src/styles/tokens.css`, repeated chip/close modules, and
the Storybook inventory.

## Standards mapped to implementation

| Source | Product implication | Implemented evidence |
|---|---|---|
| W3C Design Tokens Format Module 1.0 | Name color, space, type, and radius once; consume those names from repeated objects. | `frontend/src/styles/tokens.css` defines `--color-*`, `--space-*`, `--radius-chip`, and `--font-*`. `CitationChip` and `PopupCloseButton` read those names through `App.css`. |
| Storybook for React & Vite | Catalog repeated controls so a buyer can try the next click without reading `App.tsx`. | `frontend/src/components/*.stories.tsx` and `docs/storybook-inventory.md`. |

## APA 7th references

Design Tokens Community Group. (2025). *Design Tokens Format Module 1.0*
(W3C Community Group Draft Report). https://tr.designtokens.org/format/

Storybook. (2026). *Storybook for React & Vite*.
https://storybook.js.org/docs/get-started/frameworks/react-vite
21 changes: 21 additions & 0 deletions docs/storybook-inventory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Storybook inventory

Open the catalog after `cd frontend && pnpm run storybook`. Each story is a
buyer-facing control you can click before changing product CSS.

| Story | Buyer next action | Token / module |
|---|---|---|
| `Evidence/CitationChip` | Click a cited title to open that source post. | `--color-chip-border`, `--radius-chip`, `CitationChip` |
| `Chrome/PopupCloseButton` | Close the evidence panel or post popup. | `--space-close-inset`, `--font-size-close`, `PopupCloseButton` |

Repeated web objects must use `frontend/src/styles/tokens.css` and a module
under `frontend/src/components/`. Do not add a second Node package manager;
Storybook is installed with the existing pnpm pin on Node 24.

## References — APA 7th

Design Tokens Community Group. (2025). *Design Tokens Format Module 1.0*
(W3C Community Group Draft Report). https://tr.designtokens.org/format/

Storybook. (2026). *Storybook for React & Vite*.
https://storybook.js.org/docs/get-started/frameworks/react-vite
1 change: 1 addition & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ lerna-debug.log*
node_modules
dist
dist-ssr
storybook-static
*.local

# Editor directories and files
Expand Down
12 changes: 12 additions & 0 deletions frontend/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { StorybookConfig } from "@storybook/react-vite";

const config: StorybookConfig = {
stories: ["../src/**/*.stories.@(ts|tsx)"],
addons: [],
framework: {
name: "@storybook/react-vite",
options: {},
},
};

export default config;
11 changes: 11 additions & 0 deletions frontend/.storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Preview } from "@storybook/react-vite";
import "../src/index.css";
import "../src/App.css";

const preview: Preview = {
parameters: {
controls: { matchers: { color: /(background|color)$/i } },
},
};

export default preview;
8 changes: 6 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
{
"name": "frontend",
"private": true,
"version": "0.84.0",
"version": "0.86.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "oxlint",
"preview": "vite preview",
"test": "vitest run"
"test": "vitest run",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"dependencies": {
"oidc-client-ts": "^3.5.0",
Expand All @@ -17,6 +19,7 @@
"react-oidc-context": "^3.3.1"
},
"devDependencies": {
"@storybook/react-vite": "^10.5.8",
"@testing-library/jest-dom": "^7.0.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.4",
Expand All @@ -26,6 +29,7 @@
"@vitejs/plugin-react": "^6.0.4",
"jsdom": "^30.0.1",
"oxlint": "^1.75.0",
"storybook": "^10.5.8",
"typescript": "~6.0.2",
"vite": "^8.2.0",
"vitest": "^4.1.10"
Expand Down
Loading