Skip to content

docs(adr): define TEPP topic-lineage consumption boundary (TRSL-TM + CHRONOS/TDT) - #417

Closed
seonghobae wants to merge 802 commits into
mainfrom
worktree-structured-twirling-dusk
Closed

docs(adr): define TEPP topic-lineage consumption boundary (TRSL-TM + CHRONOS/TDT)#417
seonghobae wants to merge 802 commits into
mainfrom
worktree-structured-twirling-dusk

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds ADR 0132, the consumption-boundary decision for TEPP's Temporal Relational Shared-Latent Topic Measurement (TEPP ADR 0012) and CHRONOS/TDT event-intelligence contract (TEPP ADR 0016), enabling a git-branch-style topic-lineage thread across zcrht811_export_rows board posts.
  • Extends, does not replace, the existing evidence-fusion lineage DAG (ADR 0064/0084); LineageWeave still never reimplements topic modeling or event prediction locally.
  • Implements the runtime scaffolding: a new analysis_run_topic_lineage kind (migration 0131) and result envelope table (migration 0132), wired through tepp_client exactly like the existing TEPP measurement path (ADR 0022) — same fail-closed tepp_not_available / tepp_result_not_persisted behavior until TEPP ships the versioned result contract (tracked as TEPP issue fix(ui): never dump raw post bodies for unmatched images (v0.86.3) #156).
  • 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 AnalysisRunKindCode gains the new variant; every exhaustive TypeScript never-check switch and the start/retry UI copy is updated per kind.
  • Updates docs/product-technical-gap-baseline.md, CLAUDE.md, and CHANGELOG.md to reflect this increment.

Remaining for this feature: DAG topic-thread overlay UI, Storybook states per CHRONOS status, and Playwright e2e coverage — tracked in ADR 0132 and the gap baseline. Those depend partly on TEPP publishing its topic-identity/CHRONOS envelope shape.

Test plan

  • uv run pytest -q — 852 passed, 17 skipped (full backend suite, no regressions)
  • pnpm test — 200 passed (full frontend suite, no regressions)
  • pnpm run build — TypeScript build clean (exhaustiveness checks pass)
  • pnpm run lint — clean
  • Migrations 0131/0132 applied to the local Compose Postgres, verified idempotent on re-apply
  • _seed_demo_topic_lineage_run exercised directly against the live dev DB; confirmed Pending → Running → Failed/tepp_not_available lifecycle and outbox rows
  • Follow-up PR: DAG topic-thread overlay + Storybook stories + Playwright e2e

🤖 Generated with Claude Code

https://claude.ai/code/session_01Mk2aU7vk6Pn4pTnzcKT3Ws

seonghobae and others added 30 commits August 20, 2026 21:28
…odex/fix-project-action-parser

# Conflicts:
#	docs/product-technical-gap-baseline.md
…s' into HEAD

# Conflicts:
#	frontend/src/PostBody.test.tsx
… into codex/normalize-source-indent-semantics

# Conflicts:
#	docs/product-technical-gap-baseline.md
devin-ai-integration[bot]

This comment was marked as resolved.

@github-actions
github-actions Bot disabled auto-merge August 23, 2026 08:15
@seonghobae

Copy link
Copy Markdown
Contributor Author

Reviewed the remaining exact-head info thread. _SEMANTIC_NODE_CLASS_IRIS matches migration 0114's constrained semantic-node type set; a future schema extension must update the application mapping in the same ADR/migration change rather than silently degrading an unknown ontology type. @opencode-agent please independently review exact head 91b7e1cd914442e61e322620f194d1ba9da48567.

@seonghobae
seonghobae enabled auto-merge (squash) August 23, 2026 08:26

@opencode-agent opencode-agent Bot left a comment

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.

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head 91b7e1cd914442e61e322620f194d1ba9da48567.

  • Head SHA: 91b7e1cd914442e61e322620f194d1ba9da48567

  • Workflow run: 32628753453

  • Workflow attempt: 1

Coverage evidence

Coverage Decision

  • Result: FAIL
  • Test evidence: not proven passing
  • Docstring evidence: not proven passing when configured
  • Failure count: 4

Changed-File Evidence Map

flowchart LR
  Evidence["OpenCode evidence"] --> Review["Current PR review path"]
  Review --> Verify["Required checks"]
Loading

@opencode-agent

Copy link
Copy Markdown
Contributor

OpenCode Review Overview

  • Head SHA: 91b7e1cd914442e61e322620f194d1ba9da48567
  • Workflow run: 32628753453
  • Workflow attempt: 1
  • Gate result: REQUEST_CHANGES (approval step)

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head 91b7e1cd914442e61e322620f194d1ba9da48567.

  • Head SHA: 91b7e1cd914442e61e322620f194d1ba9da48567

  • Workflow run: 32628753453

  • Workflow attempt: 1

Coverage evidence

Coverage Decision

  • Result: FAIL
  • Test evidence: not proven passing
  • Docstring evidence: not proven passing when configured
  • Failure count: 4

Changed-File Evidence Map

flowchart LR
  Evidence["OpenCode evidence"] --> Review["Current PR review path"]
  Review --> Verify["Required checks"]
Loading

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
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Stand up the Playwright harness (frontend/playwright.config.ts, npm run
test:e2e; vitest now excludes e2e/) and ship ADR 0132 decision 5's
fail-closed spec ahead of the TEPP wiring: log in through the compose
stack's Keycloak realm as demo.admin, expand Advanced review tools, and
assert the seeded Failed topic-lineage row shows the ADR 0014
'kind · status · entity' caption, the connect-a-topic-lineage-transport
next action, the machine failure code on detail history only, and zero
evidence/inference/prediction marks — a status mark without a TEPP
envelope would be an invented status (TEPP ADR 0016).

On a deployment holding real imported source data the spec skips itself
with the reason named: ADR 0001/0042 deliberately hide the synthetic
Demo Corp runs there (verified live: /api/analysis-runs returns [] for
demo.admin on this machine's real-corpus DB). It runs fully against the
seed-only stack make seed builds. The golden-path e2e half still waits
on TEPP issue #156 publishing the topic-identity envelope.

Validation: oxlint clean; tsc -b + vite build clean; vitest 205/205
(e2e excluded); playwright test runs the login + navigation walk live
and self-skips at the documented boundary on this real-data stack.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mk2aU7vk6Pn4pTnzcKT3Ws
devin-ai-integration[bot]

This comment was marked as resolved.

The Unreleased Fixed entry said Global Ask "remains stateless" per
ADR 0090, but this same PR ships persisted conversation history
(migration 0105, backend/app/global_ask_history.py, ADR 0126, which
supersedes that ADR 0090 consequence) -- the release notes stated the
opposite of what the code does. Add the missing Added entry for the
actual feature and narrow the Fixed entry to the true, unrelated fact
it was describing (Customer Master fixtures don't need that migration
for their own setup).

Addresses Devin Review finding on CHANGELOG.md:70-72 (PR #417).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XKohNQEbNH1mpNEkvsuhKv
devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae
seonghobae enabled auto-merge August 24, 2026 01:33
@opencode-agent
opencode-agent Bot disabled auto-merge August 24, 2026 02:31
parse_summary_response returned the model's korean_summary verbatim
while the plain-text path ran _formalize_korean_summary, so the same
post could render informal -함/-음 endings depending on which transport
the provider used. Apply the formalizer in the JSON path as well and
extend it with generic 함->합니다 / 음->습니다 rules (specific 없음/있음/
않음 rules still run first). Regression test covers both sentences
through parse_summary_response.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

Open in Devin Review

Comment on lines +102 to +124
def chat_completion_content(body: object) -> str:
"""Extract text from a provider chat-completion envelope safely.

Provider error bodies and malformed success bodies must never be echoed by
a consumer through ``KeyError`` or a repr of the response. The caller
receives only a stable validation error and can translate it at its own
product boundary.
"""
if not isinstance(body, dict):
raise TypeError("provider response was not an object")
choices = body.get("choices")
if not isinstance(choices, list) or not choices:
raise ValueError("provider response did not contain a choice")
first_choice = choices[0]
if not isinstance(first_choice, dict):
raise TypeError("provider response choice was not an object")
message = first_choice.get("message")
if not isinstance(message, dict):
raise TypeError("provider response message was not an object")
content = message.get("content")
if not isinstance(content, str) or not content.strip():
raise TypeError("provider response did not contain text content")
return content

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Empty provider content now raises instead of scoring zero

chat_completion_content in http_client.py raises TypeError on empty/whitespace content. Consumers such as adjudication_client.py previously read the field directly and returned 0.0 on an empty string via a regex miss. Now an empty completion fails closed at the provider boundary. All callers wrap this in try/except, so impact is limited, but it is a semantic change worth confirming against providers that can return empty content.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +199 to +239
def _topic_lineage_envelope_is_valid(envelope: dict[str, Any]) -> bool:
"""Require TEPP's versioned topic-identity/CHRONOS-status contract (ADR 0132).

``_tepp_submission`` only checks that ``result`` is *a* dict -- a
``completed`` envelope carrying the calibrated-measurement shape (or any
other unrelated payload) would pass it too, since both requests share the
same wire contract and differ only in ``model_contract_version`` /
``output_profile``. This additionally requires TRSL-TM topic identity and
CHRONOS/TDT status, keyed by envelope version.
"""
result = envelope.get("result")
if not isinstance(result, dict):
return False
if not isinstance(result.get("envelope_version"), int):
return False
topic_identity = result.get("topic_identity")
if not isinstance(topic_identity, (list, dict)) or not topic_identity:
return False
chronos_status = result.get("chronos_status")
if not isinstance(chronos_status, (list, dict, str)) or not chronos_status:
return False
return True


def topic_lineage_submit_outcome(
client: TeppClient,
request: AnalysisRunRequest,
) -> tuple[str, str, dict[str, Any] | None]:
"""Submit through ``tepp_client`` and require the topic-lineage contract.

Mirrors :func:`tepp_submit_outcome`, but a syntactically ``completed``
envelope that omits the versioned topic-identity/CHRONOS-status contract
is also Failed (``tepp_topic_contract_unavailable``, ADR 0132 Decision
item 3), not silently persisted as a topic-lineage result.
"""
status_code, failure_code, envelope = _tepp_submission(client, request)
if status_code == _SUCCEEDED and not (
envelope is not None and _topic_lineage_envelope_is_valid(envelope)
):
return _FAILED, "tepp_topic_contract_unavailable", None
return status_code, failure_code, envelope

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Valid topic envelope without a remote run id fails at persist

_topic_lineage_envelope_is_valid in analysis_run_start.py requires envelope_version/topic_identity/chronos_status but not analysis_run_id/run_id. Such an envelope is reported Succeeded by topic_lineage_submit_outcome, then _persist_topic_lineage_result rejects it for a missing remote id and the run becomes Failed/tepp_result_not_persisted. Behavior is fail-closed and safe; noted so the validation split is understood.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Superseded by #584#417's branch had drifted 800+ commits behind main (base-drift, not real diff size); re-cut the same topic-lineage/TEPP-boundary feature cleanly off current main. See #584.

@seonghobae seonghobae closed this Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants