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
6 changes: 5 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,11 @@ 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`; the default transport is unavailable, so that run is
Failed / `tepp_not_available` 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
Expand Down
4 changes: 4 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,4 @@
# 0.84.0 TEPP analysis-run seed

Seed writes `analysis_run_tepp` via `tepp_client`. Missing transport
is Failed / `tepp_not_available`, not a fake measurement.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ 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`. The default transport is unavailable, so the home
list shows "TEPP measurement · Failed · Demo Corp" with

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The list caption is kind · status · entity only (analysisRunCaption). After seed the home list shows TEPP measurement · Failed · Demo Corp and does not show tepp_not_available. Say the list shows Failed, and that detail history keeps the machine code.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The list caption is kind · status · entity only. After seed the home list shows TEPP measurement · Failed · Demo Corp and does not show tepp_not_available. Say the list shows Failed and the next action; detail history keeps the machine code.

`tepp_not_available` -- never a fabricated theta. TEPP stays a
wire client, not a local psychometric engine.

## [0.83.0] - 2026-08-16

### Fixed
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.83.0",
"version": "0.84.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,25 @@ describe("App, authenticated", () => {
},
],
},
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This list fixture has no matching GET /api/analysis-runs/run-demo-tepp stub. The lineage-only detail mock is at /api/analysis-runs/run-demo-lineage. Clicking the TEPP row in this test 404s.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This list fixture has no matching GET /api/analysis-runs/run-demo-tepp stub. Clicking the TEPP row 404s. Add the Failed detail (status history includes tepp_not_available) and click that row.

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 +1393,7 @@ 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");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Only list text is new. Click the Failed TEPP row and assert status history includes tepp_not_available. That is the buyer-visible difference between “channel dropped” and “measurement failed.”

expect(list).toHaveTextContent("3 documents");
expect(list).not.toHaveTextContent("postgresql://");
expect(list).not.toHaveTextContent("select ");
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
121 changes: 121 additions & 0 deletions scripts/seed_demo_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,11 @@ def seed(
account_ids["demo.analyst"],
corporate_entity_id,
)
_seed_demo_tepp_run(
cur,
account_ids["demo.analyst"],
corporate_entity_id,
)

conn.commit()
finally:
Expand Down Expand Up @@ -1298,6 +1303,122 @@ def _seed_demo_analysis_run(cur, requested_by_account_id, corporate_entity_id) -
)


def tepp_seed_outcome() -> tuple[str, str | None]:
"""Ask TEPP through the published client. A missing transport is Failed.

Never invents a psychometric score. ``tepp_not_available`` means the
channel was dropped, not a calibrated negative result.
"""
from lineageweave.tepp_client import AnalysisRunRequest, TeppClient, TeppNotAvailable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Move AnalysisRunRequest, TeppClient, and TeppNotAvailable to the module top with the other lineageweave imports. Same for hashlib in _seed_demo_tepp_run (and the existing lineage-run helper). Inline imports are disallowed unless there is a documented circular-import reason.

snapshot_id="demo-source-contract-v1" also does not match the TEPP snapshot this helper inserts (demo-tepp-contract-v1 / lineageweave-synthetic-tepp-snapshot-v1). If a transport is ever supplied, the request will name a different capture than the registry row. Use the same snapshot identity the insert uses.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Move this tepp_client import to the module top with psycopg2 / http_client. New code must not add inline imports.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Move AnalysisRunRequest, TeppClient, and TeppNotAvailable to the module top. New code must not add inline imports.

snapshot_id="demo-source-contract-v1" is the lineage contract string, not the snapshot this helper inserts (demo-tepp-contract-v1). Pass the shared Demo Corp digest (sha256(lineageweave-synthetic-demo-snapshot-v1)). That repair is on #118.


request = AnalysisRunRequest(
idempotency_key="demo-tepp-seed-2026-w02",
tenant_workspace_id="demo-workspace",
snapshot_id="demo-source-contract-v1",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This wire snapshot_id is the lineage contract version, but the inserted snapshot uses demo-tepp-contract-v1 and a different digest. Reuse the lineage snapshot (ADR 0013: one capture, many runs) or set snapshot_id to the persisted snapshot identity so a later real transport submits the capture that seed stored.

knowledge_cutoff="2026-01-12T12:00:00Z",
model_contract_version="tepp-analysis-run-v1",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This snapshot_id is the lineage seed's source_contract_version string, not the snapshot this function persists (sha256(lineageweave-synthetic-tepp-snapshot-v1) / demo-tepp-contract-v1). A later real TEPP transport would measure a different capture than the run row. Pass the persisted snapshot digest (or UUID, if that is TEPP's published identifier) into tepp_seed_outcome.

output_profile="calibrated_event_measurement",
)
try:
TeppClient().submit_analysis_run(request)
except TeppNotAvailable:
return "analysis_status_failed", "tepp_not_available"
return "analysis_status_succeeded", None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any injected transport that does not raise marks the run Succeeded and discards the envelope. The home list would then show a successful TEPP measurement with no stored payload.

Keep TeppNotAvailable → Failed / tepp_not_available. Until a later slice persists the real measurement, an unexpected accept must stay Failed (tepp_result_not_persisted).


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any injected transport that does not raise marks the run succeeded and discards submit_analysis_run's return. The home list would then show a successful TEPP measurement with no theta, no calibration, and no stored response — a fabricated completion.

Keep the default TeppNotAvailable → Failed / tepp_not_available path. Do not return succeeded unless a later slice persists the real TEPP payload. Until then, unexpected success should fail closed (still Failed, distinct machine code) rather than look like a calibrated result.

def _seed_demo_tepp_run(cur, requested_by_account_id, corporate_entity_id) -> None:
"""Insert one Demo-Corp TEPP run so the kind is visible without a live TEPP.

Uses :func:`tepp_seed_outcome`. Default transport is unavailable, so
the run ends Failed / ``tepp_not_available`` -- never a fake theta.
"""
import hashlib

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same rule: import hashlib at the top of the file. _seed_demo_analysis_run already inlines it; do not copy that pattern here.


digest = hashlib.sha256(b"lineageweave-synthetic-tepp-snapshot-v1").hexdigest()
cur.execute(
"select analysis_source_snapshot_id from analysis_source_snapshot "
"where snapshot_sha256 = %s",
(digest,),
)
snapshot_row = cur.fetchone()
if snapshot_row is None:
cur.execute(
"""
insert into analysis_source_snapshot
(snapshot_sha256, source_contract_version,
maximum_available_time, captured_at)
values (%s, 'demo-tepp-contract-v1',
'2026-01-12T00:00:00Z', '2026-01-12T00:05:00Z')
returning analysis_source_snapshot_id
""",
(digest,),
)
snapshot_id = cur.fetchone()[0]
else:
snapshot_id = snapshot_row[0]
cur.execute(
"""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

After the first seed, a run already points at this snapshot. enforce_analysis_source_count_freeze runs BEFORE INSERT, so ON CONFLICT DO NOTHING still raises analysis_source_count_frozen_after_run and rolls back the whole seed() transaction. Skip the count insert when the snapshot already has counts or is referenced by a run.

insert into analysis_source_count
(analysis_source_snapshot_id, count_type_code, count_value)
values (%s, 'analysis_count_document', 3)
on conflict do nothing

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is the same freeze hole as #110 / #107. BEFORE INSERT on analysis_source_count runs before ON CONFLICT. A second make seed after the TEPP run exists raises analysis_source_count_frozen_after_run.

Select existing counts and skip the insert when the snapshot already has rows — the same pattern as #114's lineage-run seed fix. Do not land this slice on #110 at c2c8407; rebase onto that repair first.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

enforce_analysis_source_count_freeze runs BEFORE INSERT, so ON CONFLICT DO NOTHING still raises analysis_source_count_frozen_after_run on a second make seed. Select existing counts and skip the insert when the snapshot already has rows.

""",
(snapshot_id,),
)
cur.execute(
"""
select analysis_run_id from analysis_run
where requested_by_account_id = %s
and idempotency_key = 'demo-tepp-seed-2026-w02'
""",
(requested_by_account_id,),
)
run_row = cur.fetchone()
if run_row is None:
cur.execute(
"""
insert into analysis_run
(analysis_source_snapshot_id, run_kind_code, idempotency_key,
requested_by_account_id, knowledge_cutoff,
configuration_schema_version, configuration_sha256,
code_revision_sha, requested_at)
values (%s, 'analysis_run_tepp', 'demo-tepp-seed-2026-w02',
%s, '2026-01-12T12:00:00Z', 'tepp-run-v1', %s, %s,
'2026-01-12T12:34:00Z')
returning analysis_run_id
""",
(snapshot_id, requested_by_account_id, "d" * 64, "e" * 40),
)
run_id = cur.fetchone()[0]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"d" * 64 / "e" * 40 are not the code or configuration that produced this run. #110 already surfaces revision and config prefixes on the detail pane, so make seed would show those dummy hex strings as if they were approved digests.

Use the same digest source as the lineage seed run, or omit prefixes until a real revision is known. Do not invent reproducibility evidence.

else:
run_id = run_row[0]
cur.execute(
"""
insert into analysis_run_scope
(analysis_run_id, scope_kind_code, corporate_entity_id)
values (%s, 'analysis_scope_corporate_entity', %s)
on conflict (analysis_run_id) do nothing
""",
(run_id, corporate_entity_id),
)
final_status, failure_code = tepp_seed_outcome()
events = [
(1, "analysis_status_pending", "2026-01-12T12:35:00Z", None),
(2, "analysis_status_running", "2026-01-12T12:36:00Z", None),
(3, final_status, "2026-01-12T12:37:00Z", failure_code),
]
for ordinal, status, occurred, fail in events:
cur.execute(
"""
insert into analysis_run_status_event
(analysis_run_id, status_ordinal, status_code, occurred_at, failure_code)
values (%s, %s, %s, %s, %s)
on conflict do nothing
""",
(run_id, ordinal, status, occurred, fail),
)


def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--postgres-dsn", default=DEFAULT_POSTGRES_DSN)
Expand Down
9 changes: 9 additions & 0 deletions tests/test_seed_tepp_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Seeded TEPP analysis runs go through tepp_client, never a local model."""

from scripts.seed_demo_data import tepp_seed_outcome


def test_tepp_seed_outcome_is_unavailable_not_a_fake_score() -> None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This only proves the process-default TeppClient() raises. It still passes if _seed_demo_tepp_run writes succeeded, stores a dummy theta, or stops calling tepp_client.

Add a seed/Postgres case (or a monkeypatched insert helper) that: (1) default transport persists Failed / tepp_not_available and no measurement payload; (2) a fake successful transport does not persist analysis_status_succeeded without a stored TEPP response. Accuracy here is "no fabricated psychometric result," not "the helper returned a string."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This passes if the function hard-codes analysis_status_failed / tepp_not_available. Spy TeppClient.submit_analysis_run (or inject a transport) and assert it was called with the seeded AnalysisRunRequest. A hardcoded skip is not proof the client was used.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This passes if tepp_seed_outcome hard-codes the failed tuple. Spy submit_analysis_run and assert the seeded AnalysisRunRequest. Also prove an accepting transport does not persist analysis_status_succeeded.

status, failure = tepp_seed_outcome()
assert status == "analysis_status_failed"
assert failure == "tepp_not_available"
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.