docs(adr): define TEPP topic-lineage consumption boundary (TRSL-TM + CHRONOS/TDT) - #584
Conversation
…CHRONOS/TDT) Adds ADR 0132 specifying how LineageWeave will consume TEPP's Temporal Relational Shared-Latent Topic Measurement (TEPP ADR 0012) and CHRONOS/TDT event-intelligence contract (TEPP ADR 0016) to thread zcrht811_export_rows board posts by topic identity over time, without reimplementing topic modeling or event prediction locally. Extends the existing evidence-fusion lineage DAG (ADR 0064/0084) rather than replacing it, and stays fail-closed until TEPP publishes the versioned result contract (tracked as TEPP issue #156). Updates docs/product-technical-gap-baseline.md to reflect this scoped gap. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mk2aU7vk6Pn4pTnzcKT3Ws
…ed path Implements the runtime scaffolding for ADR 0132: a new analysis_run_topic_lineage kind (migration 0131) and result envelope table (migration 0132) that request TEPP's TRSL-TM topic identity and CHRONOS/TDT event-intelligence status through the same tepp_client boundary as the existing TEPP measurement kind (ADR 0022), reusing TEPP's generic AnalysisRunRequest wire shape with a different model_contract_version/output_profile instead of inventing a new type. - backend/app/analysis_run_start.py: topic_lineage_run_request, _deliver_topic_lineage_measurement, _persist_topic_lineage_result, start_kind_rejection and the outbox dispatch branch now handle the new kind, failing closed (tepp_not_available / tepp_result_not_persisted) exactly like TEPP. - backend/app/analysis_run_ingestion.py: POST /api/analysis-runs still 422s this kind (Create cannot invent a Pending topic-lineage row, same as TEPP). - scripts/seed_demo_data.py: make seed now also writes a Demo Corp topic-lineage run; verified end-to-end against the live dev Postgres (Pending -> Running -> Failed/tepp_not_available). - frontend/src/api.ts, App.tsx: AnalysisRunKindCode gains the new variant; every exhaustive switch (TypeScript `never` checks) and the start/retry UI copy is updated per kind. - Tests: mirrors the existing TEPP coverage in test_analysis_run_start.py, test_analysis_run_create.py, and test_migration_replay.py. Full backend suite: 852 passed, 17 skipped. Frontend: 200 passed, lint clean, build clean. Migrations verified idempotent on re-apply against the local Compose Postgres. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mk2aU7vk6Pn4pTnzcKT3Ws
- analysis_run_start.py: _tepp_submission only checked that a completed envelope's result was *a* dict, so a wrongly-shaped or empty envelope (e.g. accidentally serving the calibrated-measurement shape) would be persisted as a Succeeded topic-lineage run. Add topic_lineage_submit_outcome, which additionally requires the versioned topic-identity/CHRONOS-status contract ADR 0132 specifies and fails closed with tepp_topic_contract_unavailable otherwise; wire it into _deliver_topic_lineage_measurement. - post_content_queue.py, lineageDagI18n.ts: finish the ADR 0119 buyer -> reader terminology retirement this PR introduces in two docstrings that were missed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B5M79L945DMyMs3sg5yJ14
ADR 0132 decision 5 commits the Event Lineage DAG's topic-thread overlay to a "distinct visual channel, not color alone" per TEPP ADR 0016's evidence / inference / prediction status. Implements that primitive now, ahead of the DAG wiring, so it is reviewable and Storybook-covered while TEPP issue #156's completed-result contract remains open: - EvidenceStatusMark: distinguishes status by label text and glyph shape (solid dot / diamond / triangle) in addition to color, satisfying WCAG 1.4.1 with redundant, testable channels. Presentational only -- never infers or invents a status; every call site must source it from a real TEPP envelope once one exists. - evidenceStatusI18n.ts: en/ko/zh/ja/vi copy, mirrors lineageDagI18n.ts. - Three new badge-status design tokens (light + dark), extending the existing ADR 0099 badge-token family and its dark-mode-completeness test rather than inventing a parallel pattern. - Unit tests assert the three statuses are never text-indistinguishable and that a prediction's copy never reads as confirmed fact. Storybook stories cover each status plus a side-by-side comparison. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mk2aU7vk6Pn4pTnzcKT3Ws
|
Warning Review limit reachedNext included review available in 20 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (18)
📝 WalkthroughWalkthroughTEPP를 통해 ChangesTEPP topic-lineage 실행
EvidenceStatusMark 표현
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: 🟡 Moderate · up to The PR adds topic-lineage runs and result envelopes, but successful results are not currently exposed to the frontend, seeded topic-lineage data can fail because migration ordering is incomplete, and the migration may block traffic while validating existing data. These correctness, setup, and availability risks should be fixed or explicitly accepted before merge. Sequence Diagram(s)sequenceDiagram
participant 사용자
participant AnalysisRunUI
participant AnalysisRunAPI
participant TeppClient
participant TopicLineageResult
사용자->>AnalysisRunUI: topic-lineage 실행 시작
AnalysisRunUI->>AnalysisRunAPI: 실행 요청
AnalysisRunAPI->>TeppClient: topic-lineage AnalysisRunRequest 제출
TeppClient-->>AnalysisRunAPI: 완료 envelope 반환
AnalysisRunAPI->>TopicLineageResult: 유효한 envelope 저장
AnalysisRunAPI-->>AnalysisRunUI: 실행 상태 반환
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…ndary-recut # Conflicts: # CHANGELOG.md
…ndary-recut # Conflicts: # CHANGELOG.md # frontend/src/LineageDag.tsx
# Conflicts: # CLAUDE.md # docs/product-technical-gap-baseline.md
| const status = canvas.getByRole("status"); | ||
| await expect(status).toHaveTextContent("Evidence"); | ||
| await expect(status.getAttribute("aria-label")).toMatch(/directly observed/i); | ||
| }, | ||
| }; | ||
|
|
||
| export const Inference: Story = { | ||
| args: { status: "inference" }, | ||
| play: async ({ canvasElement }) => { | ||
| const canvas = within(canvasElement); | ||
| const status = canvas.getByRole("status"); | ||
| await expect(status).toHaveTextContent("Inference"); | ||
| await expect(status.getAttribute("aria-label")).toMatch(/derived from observed evidence/i); | ||
| }, | ||
| }; | ||
|
|
||
| export const Prediction: Story = { | ||
| args: { status: "prediction" }, | ||
| play: async ({ canvasElement }) => { | ||
| const canvas = within(canvasElement); | ||
| const status = canvas.getByRole("status"); | ||
| await expect(status).toHaveTextContent("Prediction"); | ||
| // A prediction must never read as settled fact -- it's the whole point | ||
| // of carrying this status through from TEPP ADR 0016 to the UI. | ||
| await expect(status.getAttribute("aria-label")).toMatch(/unconfirmed/i); | ||
| }, | ||
| }; | ||
|
|
||
| export const AllThreeSideBySide: Story = { | ||
| render: () => ( | ||
| <div style={{ display: "flex", gap: "0.5rem" }}> | ||
| <EvidenceStatusMark status="evidence" /> | ||
| <EvidenceStatusMark status="inference" /> | ||
| <EvidenceStatusMark status="prediction" /> | ||
| </div> | ||
| ), | ||
| play: async ({ canvasElement }) => { | ||
| const canvas = within(canvasElement); | ||
| const marks = canvas.getAllByRole("status"); |
There was a problem hiding this comment.
🟡 Storybook stories query a role the component never renders
The badge renders with role="img", but every play function queries getByRole("status") (and getAllByRole("status") in AllThreeSideBySide). No element carries the status role, so each query throws and all four interaction tests fail when the play functions run. The vitest test correctly uses getByRole("img").
Prompt for agents
The EvidenceStatusMark component (frontend/src/components/EvidenceStatusMark.tsx) renders its root span with role="img". The Storybook play functions in EvidenceStatusMark.stories.tsx instead look up the element with canvas.getByRole("status") (in the Evidence, Inference, Prediction stories) and canvas.getAllByRole("status") (in AllThreeSideBySide). Because no element has role="status", these lookups throw and the interaction tests fail. Change the queries to getByRole("img") / getAllByRole("img") to match the component, consistent with EvidenceStatusMark.test.tsx which already uses getByRole("img").
Was this helpful? React with 👍 or 👎 to provide feedback.
| if row["run_kind_code"] == _TOPIC_LINEAGE_RUN_KIND: | ||
| topic_result = await conn.fetchrow( | ||
| """ | ||
| select result_json, result_sha256 | ||
| from analysis_run_topic_lineage_result | ||
| where analysis_run_id = $1 | ||
| """, | ||
| analysis_run_id, | ||
| ) | ||
| if topic_result is not None: | ||
| envelope = topic_result["result_json"] | ||
| detail["topic_lineage_result"] = ( | ||
| json.loads(envelope) if isinstance(envelope, str) else envelope | ||
| ) | ||
| detail["topic_lineage_result_sha256"] = topic_result["result_sha256"] |
There was a problem hiding this comment.
🔍 Topic-lineage result read is not table-missing tolerant
The read of analysis_run_topic_lineage_result (backend/app/analysis_run_ingestion.py:410-424) is not wrapped in try/except asyncpg.UndefinedTableError, unlike fetch_reconstructed_edges which tolerates a missing migration-0021 table. A database with migration 0131 (the kind) but not 0132 (the result table) would 500 when opening a topic-lineage run detail. The seed script applies 0131 but not 0132 while creating such a run. AGENTS.md says app code must not compensate for a missing table, so this asymmetry may be intentional.
Was this helpful? React with 👍 or 👎 to provide feedback.
| hidden = client.get( | ||
| f"/api/analysis-runs/{seeded_db['hidden_run_id']}", | ||
| headers={"Authorization": f"Bearer {demo_analyst_token}"}, |
There was a problem hiding this comment.
📝 Info: New test absorbs prior test's trailing assertions
The new test_topic_lineage_detail_returns_authoritative_envelope is inserted before the trailing hidden-run 404 and unauthenticated 401 assertions of test_analysis_runs_are_labeled_aggregates_and_hide_other_scopes, so those checks now run inside the new test instead. Coverage still executes, but the original test loses those assertions and the new one asserts behavior unrelated to its name.
(Refers to this code)
Was this helpful? React with 👍 or 👎 to provide feedback.
| alter table analysis_run | ||
| add constraint analysis_run_kind_check_topic_lineage | ||
| check (run_kind_code in ( | ||
| 'analysis_run_lineage', | ||
| 'analysis_run_report', | ||
| 'analysis_run_tepp', | ||
| 'analysis_run_topic_lineage' | ||
| )) not valid; | ||
| alter table analysis_run drop constraint if exists analysis_run_kind_check; | ||
| alter table analysis_run rename constraint analysis_run_kind_check_topic_lineage | ||
| to analysis_run_kind_check; | ||
|
|
||
| do $$ | ||
| begin | ||
| if to_regclass('public.analysis_run_outbox') is not null then | ||
| alter table analysis_run_outbox | ||
| add constraint analysis_run_outbox_kind_check_topic_lineage | ||
| check (work_kind_code in ( | ||
| 'analysis_run_lineage', | ||
| 'analysis_run_tepp', | ||
| 'analysis_run_topic_lineage' | ||
| )) not valid; | ||
| alter table analysis_run_outbox | ||
| drop constraint if exists analysis_run_outbox_kind_check; | ||
| alter table analysis_run_outbox | ||
| rename constraint analysis_run_outbox_kind_check_topic_lineage | ||
| to analysis_run_outbox_kind_check; | ||
| end if; | ||
| end | ||
| $$; |
There was a problem hiding this comment.
📝 Info: Migration 0131 kind-check rewrite is replay-safe
0131 adds analysis_run_kind_check_topic_lineage NOT VALID, drops the old analysis_run_kind_check, then renames the new one in; the outbox block mirrors this. On replay the temp name is free again, so add/drop/rename still succeeds. The lookup insert uses category analysis_run_kind, matching 0018 and passing its mismatch guard. Validation is deferred to 0204, ordered later. Idempotent and correct.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Replaces #417, whose branch (
worktree-structured-twirling-dusk) had drifted 802 commits behindmain— a "worktree base drift" case, not a genuinely 199-file feature. The vast majority of that diff had already landed onmainthrough other merged PRs; only a small slice of commits at the top of that branch's log were real, unmerged work. This PR re-cuts just that real work cleanly off currentmain(4 commits, 21 files, ~600 lines).analysis_run_topic_lineagekind (migration 0131) and result envelope table (migration 0132), wired throughtepp_clientexactly like the existing TEPP measurement path (ADR 0022) — same fail-closedtepp_not_available/tepp_result_not_persistedbehavior until TEPP ships the versioned result contract (TEPP issue fix(ui): never dump raw post bodies for unmatched images (v0.86.3) #156).make seedwrites a Demo Corp topic-lineage run (Pending → Running → Failed/tepp_not_available).AnalysisRunKindCodegains the new variant; every exhaustive TypeScriptnever-check switch and the start/retry UI copy is updated per kind.EvidenceStatusMark: a reusable evidence/inference/prediction status badge (ADR 0132 decision 5, TEPP ADR 0016), distinguishing status by label text and glyph shape (not color alone, WCAG 1.4.1). Ships ahead of the DAG topic-thread wiring it's designed for — presentational only, never infers a status.docs/product-technical-gap-baseline.md,CLAUDE.md,CHANGELOG.md.What changed vs. #417's branch
I audited
git log origin/main..worktree-structured-twirling-dusk(802 commits) file-by-file rather than trusting commit messages. Findings:analysis_run_topic_lineagekind + migrations + seed + frontend wiring, the envelope-contract enforcement fix, and theEvidenceStatusMarkprimitive. These are the only commits whose content is genuinely absent from currentmain.mainby independent work: tworead_post_summarystale-fallback commits (mainalready has an equivalent, differently-structured stale-summary fallback); the old Playwright harness scaffolding commit (mainalready has a more mature, independently-built e2e harness with different env-var/script naming — reusing it would have revertedmainto an older, worse version).next_actionsuccess-path fix. Both are genuinely still missing frommain, but neither touches topic-lineage/TEPP boundary work, so bundling them here would mix unrelated concerns. Worth separate follow-up PRs.global_ask_history.py. That file (and the entire "Global Ask conversation history" persistence feature —global_ask_turn/global_ask_sessiontables, migration 0105) does not exist anywhere onmain; it was built on the stale branch but never merged. It's unrelated to topic-lineage and far too large to fold in here, so it's left untouched.topic-lineage-fail-closed.spec.ts) since it depended on the old, now-superseded Playwright scaffolding and a live docker-compose + Keycloak stack; the fail-closed contract already has solid backend (test_analysis_run_start.py) and frontend (App.test.tsx) coverage. Can be re-added againstmain's current e2e harness in a follow-up.frontend/src/lineageDagI18n.tsnever existed onmain— the equivalent copy lives inline infrontend/src/LineageDag.tsx), so the "retire buyer wording" fix was reapplied there instead of reintroducing a disconnected i18n module.Merge conflicts (CHANGELOG.md, CLAUDE.md,
docs/product-technical-gap-baseline.md,docker/postgres-init/migrate.sh,tests/test_migration_replay.py) were resolved by hand against each commit's isolated diff rather than blindly taking either side — in particular,docker/postgres-init/migrate.shand its test keepmain's current generic four-digit-filename replay boundary (ADR 0166), which already covers migrations 0131/0132 without needing a per-migration allowlist entry.Migration numbers 0131/0132 were confirmed free on current
main(ls migrations/ | grep -E "^013[12]_"→ no match) — no renumbering needed.Test plan
python3 -c "import ast; ast.parse(...)"on every touched.pyfile — cleanuv sync --all-extras && uv run pytest tests/ backend/tests/ -q— 925 passed, 16 skipped, 0 failedpnpm install --frozen-lockfile— cleannpx tsc -b --force— silent/clean (exhaustiveAnalysisRunKindCodeswitches cover the new variant)pnpm vitest run— 233 passed (22 files), 0 failednpx oxlint— cleannpx vite build— clean.py/.ts/.tsxfiles)🤖 Generated with Claude Code
Summary by CodeRabbit
새 기능
UI 개선
문서