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
4 changes: 4 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,10 @@ 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 with
`purge_analysis_run_registry('approved-retention-purge')` (ADR 0018);
a raw `DELETE` stays 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
10 changes: 10 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,10 @@
# 0.86.0 retention purge and design tokens

Operators can empty a run-bearing analysis-run registry without a superuser
trigger disable: `select purge_analysis_run_registry('approved-retention-purge')`,
export `analysis_run_retention_event`, delete those rows, then roll back 0019
and 0018. A raw DELETE still fails.

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`).
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ 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. After export, run
`select purge_analysis_run_registry('approved-retention-purge')`,
export `analysis_run_retention_event`, delete those rows, then roll
back 0019 and 0018. A raw `DELETE` of a run still fails (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
7 changes: 7 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
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, 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.

## 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
54 changes: 54 additions & 0 deletions docs/adr/0018-analysis-run-retention-purge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# 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`;
- `analysis_run_retention_event` — purged run/snapshot counts and the SHA-256
of the approval token. The raw phrase is never stored.

A session `SET` cannot authorize a raw `DELETE`. 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.

## 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.

## References — APA 7th

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
8 changes: 6 additions & 2 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 Down Expand Up @@ -74,10 +74,14 @@ 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 `purge_analysis_run_registry('approved-retention-purge')`; a wrong token and a raw `DELETE` stay rejected. Export then delete `analysis_run_retention_event` before 0019 rollback. |

## APA 7th references

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 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