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: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ summary/chat, or invented commitment. A missing signal and a
confidently-negative signal are different things. Keyman extraction,
entity-relationship classification, post summary, in-popup chat, and
commitment derivation go through contextual-orchestrator the same way
adjudication does -- never a raw LLM API.
adjudication does -- never a raw LLM API. Demo TEPP seed goes through
`tepp_client` the same way: a missing transport or an unused accepted
envelope is Failed (`tepp_not_available` / `tepp_result_not_persisted`),
never a fabricated theta or a local psychometric substitute.

## Tests

Expand Down
15 changes: 12 additions & 3 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -465,16 +465,25 @@ thread-group scope is visible only when the account can already see a
post in that group; `all_visible` is requester-only. Hidden runs 404. Detail also lists ABAC-visible post titles in the
run's scope whose `created_at` is at or before `knowledge_cutoff`
(ADR 0016) so a buyer can open a post the run was allowed to know
without seeing later live rows or hidden bodies.
without seeing later live rows or hidden bodies. Detail also returns
revision and configuration digest prefixes.
`make seed` also records a TEPP measurement run through
`tepp_client` on that same snapshot; the default transport is
unavailable, so that run is Failed rather than a fabricated score.
The home list is clickable: `GET /api/analysis-runs/{id}` fills a
labeled detail (cutoff, requested date, counts, status history)
without exposing a DSN or raw record. Status history is detail-only
and uses lookup labels plus occurrence times; a failure event keeps
its machine `failure_code` rather than an invented caption. The
its machine `failure_code` rather than an invented caption. Failed
list rows add a next-action line (open the run, then connect the
measurement service) so `tepp_not_available` is not mistaken for a
calibrated negative result. 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.
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`.

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

Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.d/0.84.0-tepp-analysis-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# 0.84.0 TEPP analysis-run seed

Seed writes `analysis_run_tepp` via `tepp_client` on the shared Demo
Corp snapshot. The home list shows Failed and the next action; detail
history keeps `tepp_not_available`. Missing transport is not a fake
measurement.
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.84.0] - 2026-08-16

### Added

- `make seed` records a Demo Corp TEPP measurement run through
`tepp_client` on the same snapshot as the lineage run (ADR 0013).
The default transport is unavailable, so the home list shows
"TEPP measurement · Failed · Demo Corp" and tells the operator to
open the run, then connect the measurement service. Detail history
keeps `tepp_not_available` -- never a fabricated theta. TEPP stays
a wire client, not a local psychometric engine. `make seed` skips
snapshot-count inserts once counts exist so a re-run does not hit
the freeze trigger.

## [0.83.0] - 2026-08-16

### Fixed
Expand Down
14 changes: 14 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# CLAUDE.md

Tool-specific pointer. Policy lives in [AGENTS.md](AGENTS.md) and the
ADRs under `docs/adr/`. Do not fork those rules here.

## Analysis-run seed (v0.84.0)

`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
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 the Failed row, then connect a live TEPP transport.
7 changes: 6 additions & 1 deletion docs/adr/0013-normalized-analysis-run-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,12 @@ Acceptance requires:
read-only administrator surface.
3. Add a normalized PostgreSQL outbox and Valkey delivery worker.
4. Add TEPP and contextual-orchestrator adapters only after their versioned
contracts are present on reviewed main branches.
contracts are present on reviewed main branches. Seed now records a
Failed TEPP run through `tepp_client` on the shared Demo Corp snapshot;
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.
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
19 changes: 14 additions & 5 deletions docs/adr/0014-authorized-analysis-run-read.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,23 @@ LineageWeave owns a fail-closed read projection of the #89 registry:

## Consequences

`make seed` writes one synthetic Demo Corp lineage run so the existing
React home page can show Analysis runs without a second application.
The detail now shows the legal lifecycle the registry already stored.
Write/rebuild APIs, TEPP submission, and a fuller Analysis Run Console
remain later slices.
`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 /
`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 run, then connect the measurement
service. The detail now shows the legal lifecycle the registry already
stored. Write/rebuild APIs, a live TEPP transport, and a fuller
Analysis Run Console remain later slices.

## References

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

Lebo, T., Sahoo, S., & McGuinness, D. (Eds.). (2013). *PROV-O: The PROV
ontology* (W3C Recommendation). World Wide Web Consortium.
https://www.w3.org/TR/2013/REC-prov-o-20130430/
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.83.0",
"version": "0.84.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
81 changes: 81 additions & 0 deletions frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,51 @@ describe("App, authenticated", () => {
jsonResponse({ post_id: "post-1", has_commitment: true, ticket }),
);
}
if (url.endsWith("/api/analysis-runs/run-demo-tepp")) {
return Promise.resolve(
jsonResponse({
analysis_run_id: "run-demo-tepp",
run_kind_code: "analysis_run_tepp",
run_kind_label: "TEPP measurement",
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",
knowledge_cutoff: "2026-01-12T12:00:00Z",
requested_at: "2026-01-12T12:34:00Z",
source_counts: [
{
count_type_code: "analysis_count_document",
count_type_label: "Documents",
count_value: 3,
},
],
visible_posts: [{ post_id: "post-1", post_title: "Public post" }],
status_history: [
{
status_ordinal: 1,
status_code: "analysis_status_pending",
status_label: "Pending",
occurred_at: "2026-01-12T12:35:00Z",
},
{
status_ordinal: 2,
status_code: "analysis_status_running",
status_label: "Running",
occurred_at: "2026-01-12T12:36:00Z",
},
{
status_ordinal: 3,
status_code: "analysis_status_failed",
status_label: "Failed",
occurred_at: "2026-01-12T12:37:00Z",
failure_code: "tepp_not_available",
},
],
}),
);
}
if (url.endsWith("/api/analysis-runs/run-demo-lineage")) {
return Promise.resolve(
jsonResponse({
Expand Down Expand Up @@ -236,6 +281,25 @@ describe("App, authenticated", () => {
},
],
},
{
analysis_run_id: "run-demo-tepp",
run_kind_code: "analysis_run_tepp",
run_kind_label: "TEPP measurement",
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",
knowledge_cutoff: "2026-01-12T12:00:00Z",
requested_at: "2026-01-12T12:34:00Z",
source_counts: [
{
count_type_code: "analysis_count_document",
count_type_label: "Documents",
count_value: 3,
},
],
},
],
}),
);
Expand Down Expand Up @@ -1374,6 +1438,10 @@ describe("App, authenticated", () => {
expect(await screen.findByRole("heading", { name: "Analysis runs" })).toBeInTheDocument();
const list = screen.getByRole("list", { name: "Analysis runs" });
expect(list).toHaveTextContent("Lineage reconstruction · Succeeded · Demo Corp");
expect(list).toHaveTextContent("TEPP measurement · Failed · Demo Corp");
expect(list).toHaveTextContent(
"Open this run to see why it failed, then connect the measurement service and re-run.",
);
expect(list).toHaveTextContent("3 documents");
expect(list).not.toHaveTextContent("postgresql://");
expect(list).not.toHaveTextContent("select ");
Expand All @@ -1396,6 +1464,19 @@ describe("App, authenticated", () => {

await userEvent.click(screen.getByRole("button", { name: "Open run post: Public post" }));
await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument());

await userEvent.click(
screen.getByRole("button", {
name: "Open analysis run: TEPP measurement · Failed · Demo Corp",
}),
);
expect(
await screen.findByRole("heading", { name: "TEPP measurement · Failed · Demo Corp" }),
).toBeInTheDocument();
const teppHistory = screen.getByRole("list", { name: "Analysis run status history" });
expect(teppHistory).toHaveTextContent("Failed 2026-01-12 12:37 · tepp_not_available");
expect(screen.getByText(/cutoff corpus TEPP would measure/i)).toBeInTheDocument();
expect(teppHistory).not.toHaveTextContent("Succeeded");
});

it("shows the calibrated period-report mean theta on the home page", async () => {
Expand Down
79 changes: 65 additions & 14 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1353,6 +1353,48 @@ function analysisRunCaption(run: AnalysisRun): string {
.join(" · ");
}

/**
* Next action for a failed run on the home list.
*
* The machine `failure_code` stays on detail history (ADR 0014). The
* list tells the operator to open the run, then reconnect the service.
*/
function analysisRunNextAction(run: AnalysisRun): string | null {
if (run.status_code === "analysis_status_failed") {

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.

Same residual as #115: this next-action string is TEPP-only ("connect the measurement service") but the predicate is any analysis_status_failed row. Gate on analysis_run_tepp or use generic copy, and add a failed-lineage fixture.

return "Open this run to see why it failed, then connect the measurement service and re-run.";
Comment thread
cursor[bot] marked this conversation as resolved.
}
return null;
}

/**
* Empty-corpus copy that tells the operator what to do next.
*/
function analysisRunEmptyPostsHint(run: AnalysisRun): string {
if (run.run_kind_code === "analysis_run_tepp") {
return (
"No posts were available at this cutoff for TEPP to measure. " +
"Open a later run, or ask an administrator to capture a newer snapshot."
);
}
return (
"No posts were available at this cutoff. Open a later run, or ask an " +
"administrator to capture a newer snapshot."
);
}

/**
* Corpus copy for a TEPP run that already has cutoff posts.
*
* Those titles are the measurement bag, not a reconstruction result.
*/
function analysisRunCorpusHint(run: AnalysisRun): string | null {
if (run.run_kind_code !== "analysis_run_tepp") return null;
return (
"These posts are the cutoff corpus TEPP would measure. Connect a TEPP " +
"transport, then re-run, to replace Failed with a calibrated result."
);
}

function AnalysisRunsPanel({
accessToken,
onSelectPost,
Expand Down Expand Up @@ -1387,6 +1429,8 @@ function AnalysisRunsPanel({
if (error && runs === null) return <p className="error">{error}</p>;
if (runs === null) return <p>Loading analysis runs...</p>;

const corpusHint = selected ? analysisRunCorpusHint(selected) : null;

return (
<section className="popup-section lineage-home">
<div className="lineage-home-header">
Expand All @@ -1404,6 +1448,7 @@ function AnalysisRunsPanel({
(count) => count.count_type_code === "analysis_count_document",
);
const caption = analysisRunCaption(run);
const nextAction = analysisRunNextAction(run);
return (
<li key={run.analysis_run_id} className="ticket-list-item">
<button
Expand All @@ -1417,6 +1462,7 @@ function AnalysisRunsPanel({
{documentCount.count_value} {documentCount.count_type_label.toLowerCase()}
</span>
)}
{nextAction && <span className="post-meta">{nextAction}</span>}
</button>
</li>
);
Expand Down Expand Up @@ -1448,20 +1494,25 @@ function AnalysisRunsPanel({
))}
</ol>
)}
{selected.visible_posts && selected.visible_posts.length > 0 && (
<ul aria-label="Posts known at this run cutoff">
{selected.visible_posts.map((post) => (
<li key={post.post_id}>
<button
className="keyman-select"
aria-label={`Open run post: ${post.post_title}`}
onClick={() => onSelectPost(post.post_id)}
>
{post.post_title}
</button>
</li>
))}
</ul>
{selected.visible_posts && selected.visible_posts.length > 0 ? (
<>
{corpusHint && <p className="post-meta">{corpusHint}</p>}
<ul aria-label="Posts known at this run cutoff">
{selected.visible_posts.map((post) => (
<li key={post.post_id}>
<button
className="keyman-select"
aria-label={`Open run post: ${post.post_title}`}
onClick={() => onSelectPost(post.post_id)}
>
{post.post_title}
</button>
</li>
))}
</ul>
</>
) : (
<p className="popup-placeholder">{analysisRunEmptyPostsHint(selected)}</p>
)}
</div>
)}
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__ = "0.83.0"
__version__ = "0.84.0"
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 = "0.83.0"
version = "0.84.0"
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