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: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ Cross-agent conventions for `LineageWeave`, readable by any coding agent
A demo BI prototype that reconstructs git-branch-style lineage between
scattered short records. See [ARCHITECTURE.md](ARCHITECTURE.md) for the
design and [`docs/lineage-bi-research-notes.md`](docs/lineage-bi-research-notes.md)
for the literature it is grounded in.
for the literature it is grounded in. After `make seed`, Analysis runs
lists lineage, TEPP, and the built period-report registry row on one
Demo Corp snapshot (ADR 0022). Mean θ stays on the period-report panel.

## Hard rule: no real data, ever

Expand Down
8 changes: 6 additions & 2 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,13 +491,17 @@ measurement service) so `tepp_not_available` is not mistaken for a
calibrated negative result. A failed lineage row tells the operator
to retry reconstruction, not to connect TEPP. A failed period-report
row tells the operator to rebuild the report. A pending TEPP row
does not claim a calibrated measurement. The
does not claim a calibrated measurement. A pending lineage row
says reconstruction has not started yet. The
payload is lookup labels plus non-negative aggregate counts -- never
source SQL, a DSN, a raw record, or a provider body. After `make seed`,
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`.
in Failed / `tepp_not_available`. Seed also records
"Period report · Succeeded · Demo Corp" on that same snapshot after
the calibrated report tables are written (ADR 0022). Open that row
to confirm the cutoff posts; mean θ stays on the period-report panel.

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

Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.d/0.86.0-related-nodes-team-org-walk.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Related-node walks include team and organization mention edges. Click an
R&R team to open sibling posts. Thread-group run lists honor knowledge_cutoff.
Failed period-report rows rebuild the report; a pending TEPP corpus
does not claim a calibrated measurement.
does not claim a calibrated measurement. A pending lineage row says
reconstruction has not started yet.
3 changes: 3 additions & 0 deletions CHANGELOG.d/0.86.4-seed-period-report-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Seed records the built Demo Corp period report as a Succeeded
analysis-run registry row on the shared snapshot (ADR 0022). Mean θ
stays on the period-report panel.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ 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.4] - 2026-08-16

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Target feat/role-responsibility-agent-ontology is now 69c035b / v0.87.0 (ADR 0020 purge + Storybook tokens). Landing this as 0.86.4 would downgrade the feature-branch version.

Rebase onto that tip, keep the 0.87.0 notes, and record this slice as 0.87.1. ADR 0022 stays; do not renumber it onto 0020 or 0021.


### Added

- `make seed` now records the already-built Demo Corp period report as
**Period report · Succeeded · Demo Corp** on the shared analysis-run
snapshot (ADR 0022). Open that row to confirm the cutoff posts. Mean
θ stays on the period-report panel — the registry row does not copy
a score.

### Fixed

- Opening a Pending lineage run repeats that reconstruction has not
started. Pending next-action copy is pinned to the registered run
kinds, so a Pending TEPP row does not say reconstruction.

## [0.86.2] - 2026-08-16

### Fixed
Expand Down
9 changes: 6 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ ADRs under `docs/adr/`. Do not fork those rules here.

## Analysis-run seed (v0.85.0)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heading still says v0.85.0 while the body now describes the ADR 0022 third registry row. After rebase, keep the v0.87.0 retention section from 69c035b and retitle this seed section to the slice that actually adds the Succeeded period-report row (0.87.1).


`make seed` writes a Demo Corp lineage run and a TEPP run on the same
snapshot (ADR 0013). The TEPP path goes through `tepp_client`. A missing
`make seed` writes a Demo Corp lineage run, a TEPP run, and a Succeeded
period-report run on the same snapshot (ADR 0013 / ADR 0022). The TEPP path goes through `tepp_client`. A missing
transport or an unused accepted envelope is Failed
(`tepp_not_available` / `tepp_result_not_persisted`). Do not invent a
theta or a local psychometric substitute. The home list caption stays
`kind · status · entity`; the machine failure code is detail-only
(ADR 0014). Open a Failed TEPP row, then connect a live TEPP
transport. A failed lineage row retries reconstruction -- it does not
mention TEPP. A failed period-report row rebuilds the report. A
pending TEPP row does not claim a calibrated measurement.
pending TEPP row does not claim a calibrated measurement. A pending
lineage row says reconstruction has not started yet.
Digest prefixes stay audible; hover a prefix to read the full digest.
Opening a cutoff title shows the live post -- compare it with the
cutoff before treating the body as reconstructed evidence (ADR 0016).
`POST /api/analysis-runs` records Pending on an authorized
cutoff capture (ADR 0017) and does not reconstruct lineage.
After `make seed`, open **Period report · Succeeded · Demo Corp**
to confirm the cutoff posts; mean θ stays on the period-report panel.
4 changes: 3 additions & 1 deletion docs/adr/0013-normalized-analysis-run-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,9 @@ Acceptance requires:
a live transport remains a later slice. A missing or unused TEPP
envelope must stay Failed (`tepp_not_available` /
`tepp_result_not_persisted`) and must not write a local psychometric
substitute.
substitute. Seed also records a Succeeded `analysis_run_report` on
that snapshot after the period-report tables are written (ADR 0022);
the registry row does not copy a theta.
5. Execute private actual-data analysis and store only signed aggregate and
reproducibility manifests outside public source control.
6. Run browser E2E through real OIDC, product navigation, and evidence drill-down.
Expand Down
10 changes: 6 additions & 4 deletions docs/adr/0014-authorized-analysis-run-read.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,19 @@ LineageWeave owns a fail-closed read projection of the #89 registry:

## Consequences

`make seed` writes one synthetic Demo Corp lineage run and one TEPP
run on the same snapshot so the existing React home page can show both
kinds without a second application. The TEPP run is Failed /
`make seed` writes one synthetic Demo Corp lineage run, one TEPP
run, and one Succeeded period-report run on the same snapshot so the
existing React home page can show all three kinds without a second
application (ADR 0022). The TEPP run is Failed /
`tepp_not_available` when the default transport is missing -- the list
keeps that machine code off the caption (this decision) and instead
tells the operator to open the TEPP run, then connect the measurement
service. A failed lineage row tells the operator to retry
reconstruction, not to connect TEPP. A failed period-report row
tells the operator to rebuild the report from a current snapshot.
A pending or running TEPP row must not claim a calibrated
measurement. The detail now shows the legal
measurement. A pending lineage row says reconstruction has not
started yet. The detail now shows the legal
lifecycle the registry already stored. `POST /api/analysis-runs` now
records a Pending run on an authorized cutoff capture (ADR 0017).
Reconstruction, a live TEPP transport, and a fuller Analysis Run
Expand Down
62 changes: 62 additions & 0 deletions docs/adr/0022-seed-period-report-analysis-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# ADR 0022 — Seed records the built period report on the shared snapshot

**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; ADR 0003
fast-mlsirm report integration; ADR 0014 authorized analysis-run read
**Refs:** After `make seed`, lineage and TEPP registry rows were visible
on home Analysis runs, but the calibrated period report lived only on
the separate report panel. ADR 0021 is the person-catalog bind on #153.

## Context

Seed already scores Demo Corp week-2/week-3 reports through
`fast-mlsirm` and persists them on the report tables. The analysis-run
registry already has `analysis_run_report`. Operators who opened
Analysis runs after `make seed` could retry a Failed TEPP transport or
inspect a Succeeded lineage tree, then had no registry row for the
report they could already see on the period-report panel.

A fake Failed report row would contradict the built report. Copying
mean θ onto `analysis_run` would invent a psychometric field the
registry is not allowed to store (ADR 0013).

## Decision

- `_seed_demo_period_report` still builds the calibrated report first.
- `_seed_demo_report_run` then inserts `analysis_run_report` on the
same Demo Corp snapshot, scoped to the same corporate entity.
- The lifecycle is Pending → Running → Succeeded because the report
tables already hold the scored period. The run row stores only
registry digests and counts — never a theta, item bank, or provider
body.
- Home next-action copy for a Succeeded report stays empty. Failed
report fixtures still say rebuild the period report.
- `POST /api/analysis-runs` stays lineage-or-TEPP as implemented
(ADR 0017). This slice does not add a Request period-report button
and does not call TEPP.

## Consequences

After `make seed`, Demo Analyst opens Analysis runs and sees
**Period report · Succeeded · Demo Corp** next to the lineage and TEPP
rows. Opening it shows the cutoff posts. Mean θ remains on the
period-report panel. Re-seed is idempotent on
`demo-report-seed-2026-w02`.

## References — APA 7th

American Educational Research Association, American Psychological
Association, & National Council on Measurement in Education. (2014).
*Standards for educational and psychological testing*. American
Educational Research Association.

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

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

World Wide Web Consortium. (2022). *Time ontology in OWL* (W3C
Recommendation). https://www.w3.org/TR/owl-time/
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 @@ -8,7 +8,7 @@
| Source | Product implication | Implemented evidence |
|---|---|---|
| W3C PROV-DM and PROV-O | Preserve identifiable entities, activities, agents, generation/use, and derivation without flattening provenance into display-only edges. | `analysis_source_snapshot`, `analysis_run`, authenticated requester, append-only status events, immutable digests; later product bindings continue to use the separate `provenance_*` layer from ADR 0011. |
| 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). Opening a listed title warns that the live body may have changed after that cutoff. |
| 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). Opening a listed title warns that the live body may have changed after that cutoff. Seed records the built period report as a later Succeeded run on that same snapshot (ADR 0022) without copying a theta onto the registry row. |
| W3C Accessible Name and Description Computation 1.1 | Do not let `aria-label` replace visible text the operator must hear. | Analysis-run digest prefixes live in a labeled group; the prefixes remain the accessible contents and the full digest is on `title` for hover verification. |
| 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. |
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": "0.86.2",
"version": "0.86.4",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
101 changes: 79 additions & 22 deletions frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ describe("App, authenticated", () => {
verificationEvidenceUrl?: string | null;
failedLineageRun?: boolean;
failedReportRun?: boolean;
succeededReportRun?: boolean;
succeededTeppRun?: boolean;
pendingTeppRun?: boolean;
postBody?: string;
Expand Down Expand Up @@ -175,6 +176,7 @@ describe("App, authenticated", () => {
);
}
if (url.endsWith("/api/analysis-runs/run-demo-report")) {
const reportSucceeded = Boolean(options?.succeededReportRun);
return Promise.resolve(
jsonResponse({
analysis_run_id: "run-demo-report",
Expand All @@ -183,8 +185,8 @@ describe("App, authenticated", () => {
scope_kind_code: "analysis_scope_corporate_entity",
scope_kind_label: "Corporate entity",
scope_entity_name: "Demo Corp",
status_code: "analysis_status_failed",
status_label: "Failed",
status_code: reportSucceeded ? "analysis_status_succeeded" : "analysis_status_failed",
status_label: reportSucceeded ? "Succeeded" : "Failed",
knowledge_cutoff: "2026-01-12T12:00:00Z",
requested_at: "2026-01-12T12:38:00Z",
source_counts: [
Expand All @@ -194,22 +196,45 @@ describe("App, authenticated", () => {
count_value: 3,
},
],
visible_posts: [],
status_history: [
{
status_ordinal: 1,
status_code: "analysis_status_pending",
status_label: "Pending",
occurred_at: "2026-01-12T12:39:00Z",
},
{
status_ordinal: 2,
status_code: "analysis_status_failed",
status_label: "Failed",
occurred_at: "2026-01-12T12:40:00Z",
failure_code: "period_report_rebuild_failed",
},
],
visible_posts: reportSucceeded
? [{ post_id: "post-1", post_title: "Public post" }]
: [],
status_history: reportSucceeded
? [
{
status_ordinal: 1,
status_code: "analysis_status_pending",
status_label: "Pending",
occurred_at: "2026-01-12T12:39:00Z",
},
{
status_ordinal: 2,
status_code: "analysis_status_running",
status_label: "Running",
occurred_at: "2026-01-12T12:40:00Z",
},
{
status_ordinal: 3,
status_code: "analysis_status_succeeded",
status_label: "Succeeded",
occurred_at: "2026-01-12T12:41:00Z",
},
]
: [
{
status_ordinal: 1,
status_code: "analysis_status_pending",
status_label: "Pending",
occurred_at: "2026-01-12T12:39:00Z",
},
{
status_ordinal: 2,
status_code: "analysis_status_failed",
status_label: "Failed",
occurred_at: "2026-01-12T12:40:00Z",
failure_code: "period_report_rebuild_failed",
},
],
}),
);
}
Expand Down Expand Up @@ -408,7 +433,7 @@ describe("App, authenticated", () => {
},
],
},
...(options?.failedReportRun
...(options?.failedReportRun || options?.succeededReportRun
? [
{
analysis_run_id: "run-demo-report",
Expand All @@ -417,8 +442,10 @@ describe("App, authenticated", () => {
scope_kind_code: "analysis_scope_corporate_entity",
scope_kind_label: "Corporate entity",
scope_entity_name: "Demo Corp",
status_code: "analysis_status_failed" as const,
status_label: "Failed",
status_code: options?.succeededReportRun
? ("analysis_status_succeeded" as const)
: ("analysis_status_failed" as const),
status_label: options?.succeededReportRun ? "Succeeded" : "Failed",
knowledge_cutoff: "2026-01-12T12:00:00Z",
requested_at: "2026-01-12T12:38:00Z",
source_counts: [
Expand Down Expand Up @@ -1737,6 +1764,31 @@ describe("App, authenticated", () => {
expect(teppButton).not.toHaveTextContent("reconstruction");
});

it("does not tell a succeeded period report to rebuild, reconstruct, or measure", async () => {
stubBackend({ succeededReportRun: true });
render(<App />);

const reportButton = await screen.findByRole("button", {
name: "Open analysis run: Period report · Succeeded · Demo Corp",
});
expect(reportButton).not.toHaveTextContent("rebuild the period report");
expect(reportButton).not.toHaveTextContent("Reconstruction has not started yet");
expect(reportButton).not.toHaveTextContent("measurement service");
expect(reportButton).not.toHaveTextContent("θ");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This locks rebuild / reconstruction / measurement / θ off the Succeeded report row. Also forbid the pending-report sentence (The report has not been built yet) on list and detail so a later copy regression cannot claim the seeded report is unbuilt.

await userEvent.click(reportButton);
expect(
await screen.findByRole("heading", { name: "Period report · Succeeded · Demo Corp" }),
).toBeInTheDocument();
expect(screen.queryByText(/rebuild the period report/)).not.toBeInTheDocument();
expect(screen.queryByText(/Reconstruction has not started yet/)).not.toBeInTheDocument();
expect(
screen.getByRole("button", {
name: "Open live post (may have changed after cutoff): Public post",
}),
).toBeInTheDocument();
});

it("does not tell a failed period report to connect the measurement service", async () => {
stubBackend({ failedReportRun: true });
render(<App />);
Expand Down Expand Up @@ -1772,6 +1824,7 @@ describe("App, authenticated", () => {
).toBeInTheDocument();
expect(screen.queryByText(/replace Failed/i)).not.toBeInTheDocument();
expect(screen.queryByText(/this TEPP run measured/i)).not.toBeInTheDocument();
expect(screen.queryByText(/Reconstruction has not started yet/)).not.toBeInTheDocument();
});

it("does not tell a succeeded TEPP run to replace Failed", async () => {
Expand Down Expand Up @@ -1799,7 +1852,11 @@ describe("App, authenticated", () => {
expect(
await screen.findByRole("heading", { name: "Lineage reconstruction · Pending · Demo Corp" }),
).toBeInTheDocument();
expect(screen.getByText(/has not started yet/)).toBeInTheDocument();
expect(
screen.getByText(
"Open this run to confirm which posts it will use. Reconstruction has not started yet.",
),
).toBeInTheDocument();
const postCall = fetchMock.mock.calls.find(
(call) => String(call[0]).endsWith("/api/analysis-runs") && call[1]?.method === "POST",
);
Expand Down
Loading
Loading