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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,9 @@ pnpm run lint && pnpm run test && pnpm run build

A run-bearing analysis-run registry empties only after an unrevoked
`analysis_run_retention_grant` and `GRANT analysis_run_retention_admin`
(ADR 0020 / v0.87.0). The documented phrase is not a secret. Do not
expose purge on a public HTTP route.
(ADR 0020 / v0.87.0). The documented phrase is not a secret. The same
call empties reconstruction children when those tables exist
(ADR 0032 / v2.10.4). Do not expose purge on a public HTTP route.

`POST /api/analysis-runs` records Pending lineage only (ADR 0017 /
v2.7.1). TEPP and period-report kinds 422 before any snapshot write.
Expand Down
5 changes: 4 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,10 @@ A run-bearing registry is emptied only after an unrevoked
`analysis_run_retention_grant` and `GRANT analysis_run_retention_admin`,
then `purge_analysis_run_registry('approved-retention-purge')`
(ADR 0020); a raw `DELETE` and a runtime role that only knows the
public phrase stay rejected. Repeated chip and close controls use
public phrase stay rejected. When start reconstruction has persisted
run-scoped edges, that same call empties those children instead of
stopping on an immutable-trigger or foreign-key error (ADR 0032).
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
6 changes: 6 additions & 0 deletions CHANGELOG.d/2.10.4-retention-purge-reconstruction-children.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# 2.10.4 Empty reconstruction children during granted retention purge

After a Demo Corp lineage reconstruction has started, the same
granted retention purge empties reconstruction edges, the
reconstruction digest, and frozen snapshot members (ADR 0032).
Do not `DISABLE TRIGGER` as superuser.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ 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).

## [2.10.4] - 2026-08-17

### Fixed

- After a Demo Corp lineage reconstruction has started, the same
granted retention purge empties `analysis_run_lineage_edge`,
`analysis_run_reconstruction`, and `analysis_source_snapshot_member`
when those tables exist, including their delete-reject triggers
(ADR 0032). Follow the same grant + admin + phrase path — do not
`DISABLE TRIGGER` as superuser.

## [2.10.3] - 2026-08-17

### Fixed
Expand Down
11 changes: 6 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ To empty a run-bearing registry, insert an unrevoked
`GRANT analysis_run_retention_admin` (ADR 0020). Then
`select purge_analysis_run_registry('approved-retention-purge')`,
export `analysis_run_retention_event`, delete those rows, and roll
back 0020 then 0018. The published phrase is not a secret. Do not
`DISABLE TRIGGER` as superuser. Do not grant the admin role or a
retention grant to the application `DATABASE_URL` login. ADR 0019
is the R&R catalog-id bind, not this purge. Person catalog identity
on that role row is ADR 0027 (`cataloged_person_id`).
back 0020 then 0018. The same call empties reconstruction children
when ADR 0021 tables exist (ADR 0032). The published phrase is not a
secret. Do not `DISABLE TRIGGER` as superuser. Do not grant the admin
role or a retention grant to the application `DATABASE_URL` login.
ADR 0019 is the R&R catalog-id bind, not this purge. Person catalog
identity on that role row is ADR 0027 (`cataloged_person_id`).

## Analysis-run seed (v0.96.0)

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@ cd frontend && cp .env.example .env.local && pnpm install && pnpm run dev
# Empty a run-bearing registry: insert analysis_run_retention_grant
# for session_user, GRANT analysis_run_retention_admin, then
# select purge_analysis_run_registry('approved-retention-purge').
# The published token is not a grant (ADR 0020).
# The published token is not a grant (ADR 0020). After a Succeeded
# start, that same call also empties reconstruction children
# (ADR 0032).
# -> http://localhost:5173, click "Log in", redirects through the real
# Keycloak login page for demo.analyst / lineageweave-demo-only
```
Expand Down
9 changes: 9 additions & 0 deletions docs/adr/0020-analysis-run-retention-purge.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ operators who purge from `psql`. 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.

Start reconstruction (ADR 0021) adds `analysis_run_lineage_edge`,
`analysis_run_reconstruction`, and `analysis_source_snapshot_member`
with delete-reject triggers. This procedure already disables those
user triggers when `to_regclass` finds the tables, deletes lineage
edges, then reconstruction, then the 0018 rows, then snapshot
members, then the snapshot, and re-enables the triggers (ADR 0032).
A 0020-only database without those relations still purges. Do not
require a superuser `DISABLE TRIGGER` after a Succeeded start.

## References — APA 7th

American Institute of Certified Public Accountants. (2017). *SOC 2®: SOC
Expand Down
62 changes: 62 additions & 0 deletions docs/adr/0032-retention-purge-reconstruction-children.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# ADR 0032 — Granted retention purge empties reconstruction children

**Decision status:** Accepted on this active PR; not protected-main truth until merge
**Date:** 2026-08-17
**Depends on:** ADR 0020 granted retention purge; ADR 0021 start reconstruction

## Context

ADR 0020 added `purge_analysis_run_registry` so a run-bearing registry
can empty without a superuser `DISABLE TRIGGER`. ADR 0021 then persisted
immutable `analysis_run_lineage_edge`, `analysis_run_reconstruction`,
and `analysis_source_snapshot_member` rows. After a Demo Corp lineage
reconstruction has started, those children and their delete-reject
triggers would fail-close the published grant + admin + phrase path
(ISO 15489-1:2016 disposition; NIST SP 800-92 protected audit records).

Dirty draft #177 ported this procedure onto a stale 0.87.0-only head.
This decision is the successor on live #74 after v2.10.3. It does not
start reconstruction, invent a theta, expose purge on a public HTTP
route, or grant `analysis_run_retention_admin` to `DATABASE_URL`.

## Decision

`purge_analysis_run_registry` disables user triggers on the three
optional children when `to_regclass` finds them, deletes in this
order, then re-enables the triggers on success and in the exception
handler:

1. `analysis_run_lineage_edge`
2. `analysis_run_reconstruction`
3. migration 0018 registry rows (`analysis_run_status_event`,
`analysis_run_scope`, `analysis_run`, `analysis_source_count`)
4. `analysis_source_snapshot_member`
5. `analysis_source_snapshot`

A database without those relations still purges. Operators follow the
same unrevoked `analysis_run_retention_grant`,
`GRANT analysis_run_retention_admin`, and
`approved-retention-purge` phrase (ADR 0020). Do not `DISABLE TRIGGER`
as superuser after a Succeeded start.

## Consequences

- After a Demo Corp lineage reconstruction has started, the granted
retention purge still empties the registry.
- Missing child tables stay a no-op. A 0020-only database still
purges.
- Purge remains an audited SQL operator action, not a public 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

PostgreSQL Global Development Group. (2026). *PostgreSQL 18
documentation: 9.29. System information functions and operators*.
https://www.postgresql.org/docs/current/functions-info.html
2 changes: 1 addition & 1 deletion docs/doctoring/ANALYSIS_RUN_REGISTRY_REFERENCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ 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 0018 rollback with any registry rows. A run-bearing registry empties only through an unrevoked `analysis_run_retention_grant` plus `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 0020 rollback. |
| Rollback does not erase audit data silently | Reject 0018 rollback with any registry rows. A run-bearing registry empties only through an unrevoked `analysis_run_retention_grant` plus `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. When `analysis_run_reconstruction` children exist, that same call empties them despite delete-reject triggers (ADR 0032). Export then delete `analysis_run_retention_event` before 0020 rollback. |
| Start reconstruction recovers the designed tree | Persist edges from `lineage_edge_specs` on the A-100 fixture bag via `records_from_source_posts`; the pricing follow-up must parent both the revised quote and the delivery question. A period-report start must 422 without a theta. TEPP start submits through `tepp_client` and stays Failed (`tepp_not_available` / `tepp_result_not_persisted`) without a theta. Snapshot members exclude a later backfill. A concurrent or Running start is 409. A Succeeded retry returns the stored digest. |

## APA 7th references
Expand Down
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
"version": "2.10.3",
"version": "2.10.4",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
2 changes: 1 addition & 1 deletion lineageweave/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,4 @@
"sentence_excerpts",
]

__version__ = "2.10.3"
__version__ = "2.10.4"
47 changes: 45 additions & 2 deletions migrations/0020_analysis_run_retention_purge.sql
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,20 @@ begin
select count(*) into run_count from analysis_run;
select count(*) into snapshot_count from analysis_source_snapshot;

-- ADR 0021 start reconstruction adds immutable children. Those
-- tables are absent on a 0020-only database. When present, their
-- delete-reject triggers and FKs would otherwise force a superuser
-- DISABLE TRIGGER — the failure this procedure exists to remove.
if to_regclass('public.analysis_run_lineage_edge') is not null then
execute 'alter table analysis_run_lineage_edge disable trigger user';
end if;
if to_regclass('public.analysis_run_reconstruction') is not null then
execute 'alter table analysis_run_reconstruction disable trigger user';
end if;
if to_regclass('public.analysis_source_snapshot_member') is not null then
execute 'alter table analysis_source_snapshot_member disable trigger user';
end if;

alter table analysis_run_status_event
disable trigger analysis_run_status_event_delete_reject;
alter table analysis_run_scope
Expand All @@ -126,10 +140,19 @@ begin
disable trigger analysis_run_mutation_reject;

begin
if to_regclass('public.analysis_run_lineage_edge') is not null then
delete from analysis_run_lineage_edge;
end if;
if to_regclass('public.analysis_run_reconstruction') is not null then
delete from analysis_run_reconstruction;
end if;
delete from analysis_run_status_event;
delete from analysis_run_scope;
delete from analysis_run;
delete from analysis_source_count;
if to_regclass('public.analysis_source_snapshot_member') is not null then
delete from analysis_source_snapshot_member;
end if;
delete from analysis_source_snapshot;
exception
when others then
Expand All @@ -139,6 +162,15 @@ begin
enable trigger analysis_run_scope_mutation_reject;
alter table analysis_run_status_event
enable trigger analysis_run_status_event_delete_reject;
if to_regclass('public.analysis_source_snapshot_member') is not null then
execute 'alter table analysis_source_snapshot_member enable trigger user';
end if;
if to_regclass('public.analysis_run_reconstruction') is not null then
execute 'alter table analysis_run_reconstruction enable trigger user';
end if;
if to_regclass('public.analysis_run_lineage_edge') is not null then
execute 'alter table analysis_run_lineage_edge enable trigger user';
end if;
raise;
end;

Expand All @@ -148,6 +180,15 @@ begin
enable trigger analysis_run_scope_mutation_reject;
alter table analysis_run_status_event
enable trigger analysis_run_status_event_delete_reject;
if to_regclass('public.analysis_source_snapshot_member') is not null then
execute 'alter table analysis_source_snapshot_member enable trigger user';
end if;
if to_regclass('public.analysis_run_reconstruction') is not null then
execute 'alter table analysis_run_reconstruction enable trigger user';
end if;
if to_regclass('public.analysis_run_lineage_edge') is not null then
execute 'alter table analysis_run_lineage_edge enable trigger user';
end if;

insert into analysis_run_retention_event (
purged_run_count,
Expand All @@ -170,8 +211,10 @@ $$;
comment on function purge_analysis_run_registry(text) is
'Empties immutable registry relations after an unrevoked role grant, '
'analysis_run_retention_admin membership, and the documented approval '
'token; records one analysis_run_retention_event. Next action: export '
'that event, delete it, then roll back 0020 and 0018.';
'token; also empties analysis_run_lineage_edge, '
'analysis_run_reconstruction, and analysis_source_snapshot_member '
'when those ADR 0021 relations exist. Next action: export '
'analysis_run_retention_event, delete it, then roll back 0020 and 0018.';

revoke all on function purge_analysis_run_registry(text) from public;
grant execute on function purge_analysis_run_registry(text)
Expand Down
8 changes: 5 additions & 3 deletions migrations/0023_analysis_run_outbox.sql
Original file line number Diff line number Diff line change
Expand Up @@ -155,15 +155,17 @@ begin
end if;
if to_regclass('public.analysis_run_lineage_edge') is not null then
delete from analysis_run_lineage_edge;
delete from analysis_run_reconstruction;
end if;
if to_regclass('public.analysis_source_snapshot_member') is not null then
delete from analysis_source_snapshot_member;
if to_regclass('public.analysis_run_reconstruction') is not null then
delete from analysis_run_reconstruction;
end if;
delete from analysis_run_status_event;
delete from analysis_run_scope;
delete from analysis_run;
delete from analysis_source_count;
if to_regclass('public.analysis_source_snapshot_member') is not null then
delete from analysis_source_snapshot_member;
end if;
delete from analysis_source_snapshot;
exception
when others then
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "lineageweave"
version = "2.10.3"
version = "2.10.4"
description = "Reconstructs git-branch-style lineage DAGs from scattered short records using multi-channel score fusion and LLM adjudication."
readme = "README.md"
license = { text = "MIT" }
Expand Down
Loading