-
Notifications
You must be signed in to change notification settings - Fork 1
feat: consume TEPP terminal result contract #656
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| # 2.20.0 — TEPP terminal result consumer | ||
|
|
||
| - Reads TEPP's versioned status/result contract from a stored accepted receipt | ||
| without resubmitting the measurement request. | ||
| - Revalidates every request binding and persists only digest-bound terminal | ||
| measurement evidence; accepted/running states remain measurement-free. | ||
| - Rejects changed result digests and maps a validated provider terminal failure | ||
| to a typed local failure without inventing a score or theta. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,7 +36,12 @@ | |
| from lineageweave.http_client import HttpClientError, post_json | ||
| from lineageweave.lineage_persistence import lineage_edge_specs | ||
| from lineageweave.models import Edge | ||
| from lineageweave.tepp_client import AnalysisRunRequest, TeppClient, TeppNotAvailable | ||
| from lineageweave.tepp_client import ( | ||
| AnalysisRunRequest, | ||
| TeppClient, | ||
| TeppInvalidResponse, | ||
| TeppNotAvailable, | ||
| ) | ||
|
|
||
| _LINEAGE_KIND = "analysis_run_lineage" | ||
| _TEPP_KIND = "analysis_run_tepp" | ||
|
|
@@ -277,6 +282,32 @@ def classify_tepp_submission( | |
| return TeppSubmissionOutcome(_FAILED, "tepp_result_not_persisted", None, "") | ||
|
|
||
|
|
||
| def classify_tepp_status( | ||
| client: TeppClient, | ||
| request: AnalysisRunRequest, | ||
| remote_run_id: str, | ||
| ) -> TeppSubmissionOutcome: | ||
| """Read one bounded, request-bound TEPP status without resubmitting work.""" | ||
| try: | ||
| response = client.read_analysis_run_status(remote_run_id, request) | ||
| except TeppNotAvailable: | ||
| return TeppSubmissionOutcome(_RUNNING, "", None, "") | ||
| except TeppInvalidResponse: | ||
| return TeppSubmissionOutcome(_FAILED, "tepp_result_not_persisted", None, "") | ||
| state = response["run_state"] | ||
| if state in {"accepted", "running"}: | ||
| return TeppSubmissionOutcome(_RUNNING, "", response, "") | ||
| terminal = response["terminal_result"] | ||
| if state == "failed": | ||
| return TeppSubmissionOutcome( | ||
| _FAILED, | ||
| str(terminal["failure_code"]), | ||
| terminal, | ||
| "", | ||
| ) | ||
| return TeppSubmissionOutcome(_SUCCEEDED, "", terminal, _PERSIST_RESULT) | ||
|
|
||
|
|
||
| def _tepp_submission( | ||
| client: TeppClient, | ||
| request: AnalysisRunRequest, | ||
|
|
@@ -309,6 +340,19 @@ async def _persist_tepp_result( | |
| result_sha256 = hashlib.sha256(result_json.encode("utf-8")).hexdigest() | ||
| try: | ||
| async with conn.transaction(): | ||
| existing = await conn.fetchrow( | ||
| """ | ||
| select remote_run_id, result_sha256 | ||
| from analysis_run_tepp_result | ||
| where analysis_run_id = $1 | ||
| """, | ||
| analysis_run_id, | ||
| ) | ||
| if existing is not None: | ||
| return ( | ||
| str(existing["remote_run_id"]) == remote_run_id | ||
| and str(existing["result_sha256"]) == result_sha256 | ||
|
Comment on lines
+343
to
+354
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: Stored result digest recomputes the DTO, not TEPP's artifact digest
Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| ) | ||
| await conn.execute( | ||
| """ | ||
| insert into analysis_run_tepp_result | ||
|
|
@@ -973,10 +1017,13 @@ async def _deliver_tepp_measurement( | |
| knowledge_cutoff=locked["knowledge_cutoff"], | ||
| corporate_entity_id=str(locked["corporate_entity_id"]), | ||
| ) | ||
| outcome = classify_tepp_submission(tepp_client, request) | ||
| if not outcome.persist_kind and await fetch_tepp_accepted_receipt( | ||
| conn, analysis_run_id | ||
| ) is not None: | ||
| receipt = await fetch_tepp_accepted_receipt(conn, analysis_run_id) | ||
| outcome = ( | ||
| classify_tepp_status(tepp_client, request, str(receipt["remote_run_id"])) | ||
| if receipt is not None | ||
| else classify_tepp_submission(tepp_client, request) | ||
| ) | ||
| if receipt is not None and outcome.status_code == _RUNNING: | ||
| return False | ||
| status_code = outcome.status_code | ||
| failure_code = outcome.failure_code | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| # ADR 0178 — Read TEPP terminal results through the provider contract | ||
|
|
||
| **Decision status:** Accepted on this stacked PR; not protected-main truth until merge | ||
| **Date:** 2026-08-26 | ||
| **Depends on:** ADR 0022, ADR 0023, ADR 0162; TEPP PR #157 | ||
| **Refs:** LineageWeave issue #277; TEPP issues #156 and #249 | ||
|
|
||
| ## Context | ||
|
|
||
| ADR 0162 correctly separates TEPP's accepted receipt from measurement, but it | ||
| predates TEPP's versioned `AnalysisRunStatus` and | ||
| `AnalysisRunTerminalResult` v1 contracts. TEPP PR #157 merged those Rust wire | ||
| contracts on 2026-08-25. It deliberately did not publish a production HTTP | ||
| status service, so LineageWeave must not guess a URL or poll interval. | ||
|
|
||
| ## Decision | ||
|
|
||
| `TeppClient` exposes a pluggable status-read transport alongside its existing | ||
| submission transport. A stored accepted receipt causes the delivery retry to | ||
| read that remote run once instead of resubmitting the request. The read is | ||
| bounded by the supplied transport call and is crash-resumable because the | ||
| receipt and PostgreSQL outbox remain durable. | ||
|
|
||
| The consumer requires the exact v1 status shape and revalidates remote run, | ||
| idempotency key, tenant/workspace, snapshot, cutoff, model contract, output | ||
| profile, terminal state, result schema, lowercase SHA-256 digest, bounded | ||
| identity-free summary, completion time, and failure code. Accepted/running | ||
| contains no terminal result and keeps the local run Running. A succeeded | ||
| terminal result is persisted before Succeeded. A terminal provider failure | ||
| appends its validated snake-case failure code without a result. Any mismatch | ||
| fails closed. Replaying the same remote run with a changed canonical result | ||
| digest is rejected. | ||
|
|
||
| The configured HTTP client does not synthesize `GET /v1/analysis-runs/{id}` | ||
| while TEPP documents it only as a target endpoint. A production status | ||
| transport is enabled only when the owning TEPP service publishes that route. | ||
| No theta, score, estimator, poll cadence, or backoff coefficient is implemented | ||
| in LineageWeave. | ||
|
|
||
| ```mermaid | ||
| sequenceDiagram | ||
| participant Worker | ||
| participant Registry | ||
| participant TEPP | ||
| Worker->>Registry: read accepted receipt + immutable request | ||
| Worker->>TEPP: read status(remote run id) | ||
| alt accepted or running | ||
| Note over Registry: remain Running; outbox remains claimed | ||
| else succeeded and all bindings match | ||
| Worker->>Registry: persist terminal DTO + Succeeded atomically | ||
| else failed and all bindings match | ||
| Worker->>Registry: append typed Failed | ||
| else unavailable | ||
| Note over Registry: retain receipt; retry remains possible | ||
| else invalid or mismatched | ||
| Worker->>Registry: fail closed | ||
| end | ||
| ``` | ||
|
|
||
| ## Consequences | ||
|
|
||
| The contract consumer is testable now through an in-process or future HTTP | ||
| adapter without pretending TEPP has deployed a route. Automatic scheduled | ||
| polling remains unavailable until the provider owns an executable status | ||
| endpoint and an evidence-based retry policy. | ||
|
|
||
| ## References — APA 7th | ||
|
|
||
| Hohpe, G., & Woolf, B. (2003). *Enterprise integration patterns: Designing, | ||
| building, and deploying messaging solutions*. Addison-Wesley. | ||
|
|
||
| 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/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔍 Invalid status payload permanently fails a receipt-bearing run
A
TeppInvalidResponsefrom a status read maps to_FAILED(analysis_run_start.py:295-296);_deliver_tepp_measurementthen appends a terminal Failed and marks the outbox delivered, unlikeTeppNotAvailablewhich stays Running. One malformed status payload permanently fails a run that holds a durable accepted receipt, with no later retry. ADR 0178 documents fail-closed on invalid/mismatched, and no production status transport is wired yet, so impact today is nil.Was this helpful? React with 👍 or 👎 to provide feedback.