-
Notifications
You must be signed in to change notification settings - Fork 1
feat(estimation): authorize the ADR 0200 anchor method — weights activate #590
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 |
|---|---|---|
|
|
@@ -7,16 +7,21 @@ | |
|
|
||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
|
|
||
| import asyncpg | ||
| import redis.asyncio as redis | ||
| from uuid import UUID | ||
|
|
||
| from lineageweave.adjudication_client import AdjudicationClient | ||
| from lineageweave.tepp_client import TeppClient | ||
|
|
||
| from backend.app.analysis_run_ingestion import AnalysisRunCreateError | ||
| from backend.app.analysis_run_outbox import OUTBOX_STREAM_KEY | ||
| from backend.app.analysis_run_start import deliver_queued_analysis_run | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| async def consume_analysis_run_stream_once( | ||
| client: redis.Redis, | ||
|
|
@@ -40,26 +45,40 @@ async def consume_analysis_run_stream_once( | |
| except ValueError: | ||
| analysis_run_id = "" | ||
| if analysis_run_id: | ||
| async with pool.acquire() as conn: | ||
| async with conn.transaction(): | ||
| owner = await conn.fetchrow( | ||
| """ | ||
| select requested_by_account_id | ||
| from analysis_run | ||
| where analysis_run_id = $1::uuid | ||
| """, | ||
| analysis_run_id, | ||
| ) | ||
| if owner is not None: | ||
| await deliver_queued_analysis_run( | ||
| conn, | ||
| analysis_run_id=analysis_run_id, | ||
| account_id=str(owner["requested_by_account_id"]), | ||
| affiliated_entity_ids=[], | ||
| tepp_client=tepp_client, | ||
| adjudication_client=adjudication_client, | ||
| valkey_stream_entry_id=str(entry_id), | ||
| # One run's fail-closed refusal (404/409/503, e.g. channel | ||
| # weights not estimated yet, ADR 0145) must not end the | ||
| # worker task and halt every later run's delivery. The | ||
| # transaction rolls back, the durable outbox row stays | ||
| # available, and an explicit HTTP start retries the run | ||
| # once the operator resolves the named next action. | ||
| try: | ||
| async with pool.acquire() as conn: | ||
| async with conn.transaction(): | ||
| owner = await conn.fetchrow( | ||
| """ | ||
| select requested_by_account_id | ||
| from analysis_run | ||
| where analysis_run_id = $1::uuid | ||
| """, | ||
| analysis_run_id, | ||
| ) | ||
| if owner is not None: | ||
| await deliver_queued_analysis_run( | ||
| conn, | ||
| analysis_run_id=analysis_run_id, | ||
| account_id=str(owner["requested_by_account_id"]), | ||
| affiliated_entity_ids=[], | ||
| tepp_client=tepp_client, | ||
| adjudication_client=adjudication_client, | ||
| valkey_stream_entry_id=str(entry_id), | ||
| ) | ||
| except AnalysisRunCreateError as exc: | ||
| logger.warning( | ||
| "analysis-run %s delivery refused (%s): %s", | ||
| analysis_run_id, | ||
| exc.status_code, | ||
| exc.detail, | ||
| ) | ||
|
Comment on lines
+75
to
+81
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: Worker swallows all refusal statuses, not just 503 The new Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| last_id = str(entry_id) | ||
| return last_id | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,9 +22,15 @@ | |
| from lineageweave.lineage_persistence import lineage_edge_specs | ||
| from lineageweave.models import Edge, Record | ||
|
|
||
| # ADR 0145 rejected the unanchored estimator. A future accepted ADR must add | ||
| # its independently validated method code here before persisted weights can run. | ||
| _SUPPORTED_ANCHOR_METHOD_CODES: frozenset[str] = frozenset() | ||
| # Accepted ADR 0200 (points 2-3) authorizes exactly one anchor method: | ||
| # expected-information estimates honestly labeled as validated by the | ||
| # channels' internal response structure only, pending the TEPP | ||
| # criterion-validity gate. When that gate exists, a set that fails it is | ||
| # retired and this stays the only place an anchor method is ever added | ||
| # -- ADR-first, per ADR 0145's original condition. | ||
| _SUPPORTED_ANCHOR_METHOD_CODES: frozenset[str] = frozenset( | ||
| {"unanchored_internal_structure"} | ||
| ) | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
Comment on lines
+31
to
+33
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: Authorized anchor code matches estimation scripts The newly authorized Was this helpful? React with 👍 or 👎 to provide feedback.
Comment on lines
+25
to
+33
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. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 허용 상태 변경에 맞게 상태 메시지와 설명을 갱신해야 합니다.
🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| def _occurred_at(value: datetime) -> datetime: | ||
|
|
@@ -186,8 +192,33 @@ async def load_estimated_channel_weights( | |
| return persisted | ||
|
|
||
|
|
||
| class ChannelWeightsNotEstimated(RuntimeError): | ||
| """No activated estimated weight set exists for the active channels. | ||
|
|
||
| Product reconstruction treats fusion weights as measurement output | ||
| only (ADR 0200 point 1): estimated by fast-mlsirm, provenance-gated, | ||
| never hand-picked constants. No hand-picked default exists anywhere | ||
| -- the library demo estimates its weights from its declared design, | ||
| and unit tests inject synthetic weights explicitly. | ||
| """ | ||
|
|
||
| def __init__(self, active_channels: set[str]) -> None: | ||
| super().__init__( | ||
| "no activated fast-mlsirm channel weight estimate exists for " | ||
| f"active channels {sorted(active_channels)}; run " | ||
| "scripts/estimate_channel_weights.py first -- product " | ||
| "reconstruction never falls back to hand-picked weights" | ||
| ) | ||
| self.active_channels = active_channels | ||
|
|
||
|
|
||
| async def rebuild_lineage(conn: asyncpg.Connection) -> list[Edge]: | ||
| """Reconstruct lineage for every ``source_post`` and persist the edges.""" | ||
| """Reconstruct lineage for every ``source_post`` and persist the edges. | ||
|
|
||
| Raises :class:`ChannelWeightsNotEstimated` when no activated | ||
| estimate matches this path's active channels -- run | ||
| ``scripts/estimate_channel_weights.py`` first (ADR 0200 point 1). | ||
| """ | ||
| rows = await conn.fetch( | ||
| "select post_id, post_title, voc_type_code, created_at, corporate_entity_id, " | ||
| "process_unit_id, thread_group_key, secondary_grouping_key " | ||
|
|
@@ -196,9 +227,10 @@ async def rebuild_lineage(conn: asyncpg.Connection) -> list[Edge]: | |
| # No adjudication client is wired on this path, so the active channel | ||
| # set is the three deterministic channels (reconstruct drops llm when | ||
| # unavailable rather than faking it). | ||
| weights = await load_estimated_channel_weights( | ||
| conn, {"temporal", "secondary_key", "text"} | ||
| ) | ||
| active_channels = {"temporal", "secondary_key", "text"} | ||
| weights = await load_estimated_channel_weights(conn, active_channels) | ||
| if weights is None: | ||
| raise ChannelWeightsNotEstimated(active_channels) | ||
| edges = lineage_edge_specs(records_from_source_posts(rows), weights=weights) | ||
| await persist_lineage_edges(conn, edges) | ||
| return edges | ||
|
|
||
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.
📝 Info: Four-channel runs fail closed until an llm weight set exists
When a live adjudication client is configured,
_deliver_lineage_reconstructionrequires a four-channel weight set includingllm, butscripts/estimate_channel_weights.pyand the seed only ever persist the three-channel deterministic set. Any start/rebuild with the orchestrator enabled will 503 until achannel_set_with_llmestimate is produced — enabling the orchestrator disables reconstruction rather than degrading to three channels.Was this helpful? React with 👍 or 👎 to provide feedback.