Skip to content
Merged
Show file tree
Hide file tree
Changes from 120 commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
09a9ca1
fix: preserve oidc deep links across callback storage
seonghobae Aug 20, 2026
e02cb11
fix: render buyer-facing image evidence
seonghobae Aug 20, 2026
b0edd9b
fix: label unavailable image evidence
seonghobae Aug 20, 2026
f7d11e3
docs: avoid oidc adr number collision
seonghobae Aug 20, 2026
116a8dc
Merge remote-tracking branch 'origin/fix/oidc-deep-link-locale' into …
seonghobae Aug 20, 2026
5deb067
docs: avoid buyer image adr number collision
seonghobae Aug 20, 2026
cd6301b
fix: bind summary actions to source-grounded projects
seonghobae Aug 20, 2026
3efdff3
test: cover project-bound summary action round trip
seonghobae Aug 20, 2026
1e5ac11
docs: record buyer gap checkpoint evidence
seonghobae Aug 20, 2026
313d38a
docs: align image evidence ADR heading
seonghobae Aug 20, 2026
e0dbc38
docs: align OIDC ADR heading
seonghobae Aug 20, 2026
4d1736e
fix: preserve legacy action evidence delimiters
seonghobae Aug 20, 2026
c01001d
Merge branch 'fix/project-bound-summary-actions' of https://github.co…
seonghobae Aug 20, 2026
e81aff8
docs: refresh buyer gap audit checkpoint
seonghobae Aug 20, 2026
d442287
docs: record current project action head
seonghobae Aug 20, 2026
0eb70c9
feat: bind summary events to source-grounded projects
seonghobae Aug 20, 2026
60b52f2
Merge remote-tracking branch 'origin/fix/project-bound-summary-action…
seonghobae Aug 20, 2026
a49b315
docs: record full checkpoint verification
seonghobae Aug 20, 2026
8220673
fix(summary): normalize project keys before persistence
seonghobae Aug 20, 2026
3c74f1f
Merge commit 'a49b315e6e0ad3c2369d56a77fa6fd50d2230a0a' into codex/fi…
seonghobae Aug 20, 2026
bd4e96b
docs: refresh exact-head buyer gap audit
seonghobae Aug 20, 2026
42e6230
test(summary): cover project key sentinel normalization
seonghobae Aug 20, 2026
a4d1de5
test(image): cover source placement and OCR rendering
seonghobae Aug 20, 2026
d8b7f56
fix: preserve stale summary buyer continuity
seonghobae Aug 20, 2026
2218ee7
fix: keep post content recovery alive
seonghobae Aug 20, 2026
b7ed772
test(summary): cover stale continuity and retry
seonghobae Aug 20, 2026
c914f76
feat: add explicit post content retry
seonghobae Aug 20, 2026
bff4c9d
test(queue): cover explicit retry rejection
seonghobae Aug 20, 2026
ba60842
docs(queue): complete retry command docstrings
seonghobae Aug 20, 2026
c301e2e
fix: reconcile backfill content with job ledger
seonghobae Aug 20, 2026
4016e2b
fix(ui): preserve indentation after mixed content
seonghobae Aug 20, 2026
20b9177
Merge remote-tracking branch 'origin/fix/stale-summary-buyer-continui…
seonghobae Aug 20, 2026
e219e48
docs: record post content recovery evidence
seonghobae Aug 20, 2026
8b0e1d8
docs(ui): clarify indentation matching boundary
seonghobae Aug 20, 2026
5873966
test(ui): cover missing source unit fallback
seonghobae Aug 20, 2026
4bc21e4
fix: serialize per-post vision region analysis
seonghobae Aug 20, 2026
69b96e9
Merge remote-tracking branch 'origin/fix/stale-summary-buyer-continui…
seonghobae Aug 20, 2026
d4807f0
fix(ui): preserve adjacent table boundaries
seonghobae Aug 20, 2026
d9b4b51
fix: preserve honest image evidence rendering
seonghobae Aug 20, 2026
cea0e7b
fix: separate source whitespace from explicit structure
seonghobae Aug 20, 2026
9f7574f
docs: record source indentation buyer gap
seonghobae Aug 20, 2026
f13bc3f
feat: enrich semantic hints from source catalogs
seonghobae Aug 20, 2026
adbb72d
fix: reject non-decomposed vision regions
seonghobae Aug 20, 2026
766b742
docs: refresh protected gate baseline
seonghobae Aug 20, 2026
4d3d7ae
docs: record corrected image region evidence
seonghobae Aug 20, 2026
85dbfb5
fix: trim source catalog lookup codes
seonghobae Aug 20, 2026
efd4aab
docs: record catalog hint boundary
seonghobae Aug 20, 2026
deca246
docs: record exact-head validation evidence
seonghobae Aug 20, 2026
82b7b39
docs: record live browser acceptance
seonghobae Aug 20, 2026
69b8909
docs: refresh live gate topology
seonghobae Aug 20, 2026
f922322
Merge remote-tracking branch 'origin/feat/analysis-run-name-evidence-…
seonghobae Aug 20, 2026
49d85d0
Merge remote-tracking branch 'origin/fix/oidc-deep-link-locale' into …
seonghobae Aug 20, 2026
40c9168
Merge remote-tracking branch 'origin/fix/buyer-image-evidence' into c…
seonghobae Aug 20, 2026
fc7969e
Merge remote-tracking branch 'origin/feat/analysis-run-name-evidence-…
seonghobae Aug 20, 2026
516603c
Merge remote-tracking branch 'origin/fix/oidc-deep-link-locale' into …
seonghobae Aug 20, 2026
3072e01
Merge remote-tracking branch 'origin/fix/buyer-image-evidence' into c…
seonghobae Aug 20, 2026
a903fff
Merge remote-tracking branch 'origin/fix/project-bound-summary-action…
seonghobae Aug 20, 2026
d36e106
Merge remote-tracking branch 'origin/fix/stale-summary-buyer-continui…
seonghobae Aug 20, 2026
ca3f56a
Merge remote-tracking branch 'origin/codex/fix-mixed-body-indentation…
seonghobae Aug 20, 2026
e84994e
Merge remote-tracking branch 'origin/codex/post-structure-case-fixes'…
seonghobae Aug 20, 2026
ab27898
fix: refresh content after summary ingestion
seonghobae Aug 20, 2026
e14dd95
test: cover lineage graph interactions
seonghobae Aug 20, 2026
2d7f899
fix: bound OIDC return state parsing
seonghobae Aug 20, 2026
3a32312
Merge remote-tracking branch 'origin/feat/analysis-run-name-evidence-…
seonghobae Aug 20, 2026
e5ee5db
Merge remote-tracking branch 'origin/fix/oidc-deep-link-locale' into …
seonghobae Aug 20, 2026
9e17c2a
fix: validate buyer image sources at render boundary
seonghobae Aug 20, 2026
1fa42f8
Merge remote-tracking branch 'origin/fix/buyer-image-evidence' into c…
seonghobae Aug 20, 2026
446abeb
Merge remote-tracking branch 'origin/fix/project-bound-summary-action…
seonghobae Aug 20, 2026
36a879b
Merge remote-tracking branch 'origin/fix/stale-summary-buyer-continui…
seonghobae Aug 20, 2026
bb9c78e
Merge remote-tracking branch 'origin/codex/fix-mixed-body-indentation…
seonghobae Aug 20, 2026
1e762d5
Merge remote-tracking branch 'origin/codex/post-structure-case-fixes'…
seonghobae Aug 20, 2026
9a45941
fix: expose unexpected content channel defects
seonghobae Aug 20, 2026
0816d59
Merge remote-tracking branch 'origin/codex/normalize-source-indent-se…
seonghobae Aug 20, 2026
9e59b70
fix: expose recoverable content batch failures
seonghobae Aug 20, 2026
18fdaa9
Merge remote-tracking branch 'origin/codex/normalize-source-indent-se…
seonghobae Aug 20, 2026
17fa14f
test: define directional accessible lineage graph contract
seonghobae Aug 21, 2026
3ab6ba7
test: avoid duplicate lineage marker identities
seonghobae Aug 21, 2026
c5bbb52
feat: make event lineage direction and evidence explicit
seonghobae Aug 21, 2026
6be794a
fix: keep lineage graph legible across themes and widths
seonghobae Aug 21, 2026
89daa52
fix: expose lineage evidence by default
seonghobae Aug 21, 2026
d8b7eef
Merge remote-tracking branch 'origin/feat/analysis-run-name-evidence-…
seonghobae Aug 21, 2026
4e40e26
Merge remote-tracking branch 'origin/feat/analysis-run-name-evidence-…
seonghobae Aug 21, 2026
fbecd77
Merge remote-tracking branch 'origin/fix/oidc-deep-link-locale' into …
seonghobae Aug 21, 2026
46ca533
Merge remote-tracking branch 'origin/fix/buyer-image-evidence' into c…
seonghobae Aug 21, 2026
4f029ea
test: require accessible lineage graph regions
seonghobae Aug 21, 2026
63269e4
feat: make lineage graph structure accessible
seonghobae Aug 21, 2026
b32e177
style: add lineage graph interaction states
seonghobae Aug 21, 2026
a285256
docs: add lineage graph Storybook states
seonghobae Aug 21, 2026
c5fa8c2
docs: record lineage DAG evidence improvements
seonghobae Aug 21, 2026
17ff911
Merge remote-tracking branch 'origin/fix/project-bound-summary-action…
seonghobae Aug 21, 2026
efaef8a
test: keep lineage node controls exposed to assistive technology
seonghobae Aug 21, 2026
077509e
fix: expose lineage node controls outside image semantics
seonghobae Aug 21, 2026
1fb1626
docs: record buyer lineage DAG design boundary
seonghobae Aug 21, 2026
e38df62
Merge remote-tracking branch 'origin/fix/stale-summary-buyer-continui…
seonghobae Aug 21, 2026
1faed2f
Merge remote-tracking branch 'origin/codex/fix-mixed-body-indentation…
seonghobae Aug 21, 2026
e3f00ea
Merge remote-tracking branch 'origin/codex/post-structure-case-fixes'…
seonghobae Aug 21, 2026
cabe3bb
test: cover isolated roots and fused-score labeling
seonghobae Aug 21, 2026
d668267
fix: omit empty lineage evidence tables
seonghobae Aug 21, 2026
f80998b
docs: add isolated lineage Storybook state
seonghobae Aug 21, 2026
3f3ec01
Merge remote-tracking branch 'origin/codex/normalize-source-indent-se…
seonghobae Aug 21, 2026
c19d05e
docs: record Event Lineage Figma frame IDs
seonghobae Aug 21, 2026
a157220
test: require lineage meaning and legend disclosure
seonghobae Aug 21, 2026
2d595d0
test: require five-locale lineage meaning copy
seonghobae Aug 21, 2026
94dbfeb
feat: add localized lineage meaning copy
seonghobae Aug 21, 2026
5f12e91
feat: disclose lineage legend and inference boundary
seonghobae Aug 21, 2026
5d63e1c
style: add lineage legend and inference notice
seonghobae Aug 21, 2026
e04b128
docs: record lineage meaning and legend disclosure
seonghobae Aug 21, 2026
34a3603
test: require actionable lineage empty state and Space activation
seonghobae Aug 21, 2026
088b1d5
test: require localized lineage empty-state next action
seonghobae Aug 21, 2026
507d520
feat: localize actionable lineage empty state
seonghobae Aug 21, 2026
0db9bd7
feat: make the lineage empty state actionable
seonghobae Aug 21, 2026
c883483
docs: record actionable lineage empty state
seonghobae Aug 21, 2026
86e2134
test: require mobile evidence-card labels
seonghobae Aug 21, 2026
0b0cacd
feat: label mobile lineage evidence cards
seonghobae Aug 21, 2026
f9df5ea
fix: guard lineage dag layout against cycles
seonghobae Aug 21, 2026
f4563df
docs: plan forward-only fast stabilization
seonghobae Aug 21, 2026
0d7fd47
test: reproduce lineage date specificity defect
seonghobae Aug 21, 2026
6f4712d
fix: preserve muted lineage event-date styling
seonghobae Aug 21, 2026
1fad94f
Merge protected main into Event Lineage stack
seonghobae Aug 21, 2026
6c9f86d
Merge commit 'c135c41ba65e02a5598647adcfc978002f275673' into HEAD
seonghobae Aug 21, 2026
336730f
fix: keep unauthenticated admin controls gated
seonghobae Aug 21, 2026
0e63ba0
fix: align Event Lineage shell with UI UX guide (#350)
seonghobae Aug 21, 2026
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
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ contextual-orchestrator owns model discovery and selection.
signals with contextual-orchestrator adjudication when evidence conflicts;
heuristics are not authoritative and must not be the only fallback for an
unresolved structure decision.
- Source-system codes may be enriched with catalog display names under ADR
0117. Pass those names to contextual-orchestrator as labeled lookup hints
only; never promote them to an entity binding, customer fact, project fact,
or imported-author affiliation without post evidence.
- Remove presentation-only visual line alignment inside a paragraph (for
example continuation lines manually aligned after `-`, `*`, `1.`, or `.`)
from derived semantic text, while retaining the source body and meaningful
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.d/2.12.6-buyer-image-source-safety.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# 2.12.6 — Validate buyer image sources

## Fixed

- Buyer image rendering now rejects script, SVG, external, and malformed
source URLs before they reach an image element.
7 changes: 7 additions & 0 deletions CHANGELOG.d/2.12.6-oidc-deep-link-safety.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# 2.12.6 — Bound OIDC deep-link state parsing

## Fixed

- OIDC callback state is parsed at most once and length-bounded before JSON
handling, preventing recursive encoded state from exhausting the browser
stack while preserving safe same-origin post deep links.
8 changes: 8 additions & 0 deletions CHANGELOG.d/2.12.7-lineage-dag-evidence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
### Changed

- Made the buyer Event Lineage DAG explicitly directional with parent-to-child arrowheads whose paths stop outside node circles.
- Preserved authored graph width behind a keyboard-focusable horizontal scroll region instead of shrinking deep lineages.
- Added visible event dates, a redundant visual/text legend, and an accessible exact-value evidence table for lineage relations, dates, and fused scores.
- Added five-locale buyer copy stating that reconstructed continuation edges do not prove causality or authoritative fact.
Comment on lines +1 to +6

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Changelog version 2.12.7 is below the latest 2.13.1 fragment

This adds 2.12.7-lineage-dag-evidence.md while newer fragments (up to 2.13.1-*) already exist in CHANGELOG.d/. The PR description notes it stacks on codex/normalize-source-indent-semantics, so the lower version may be intentional to slot into an earlier release line, but reviewers should confirm the version fragment is correct for the intended release rather than an accidental regression.

Open in Devin Review

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

- Replaced the internal "seed posts" empty-state language with an actionable five-locale instruction to add eligible source records and rebuild Event Lineage.
- Added synthetic Storybook states for branching, selected-root, isolated-root, and empty lineage surfaces.
7 changes: 7 additions & 0 deletions CHANGELOG.d/2.13.1-mixed-body-indentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Mixed table and paragraph indentation

## Fixed

- Match persisted post units to their source text instead of using ordinal
position, so a table or embedded image cannot shift the fallback indentation
of a later unresolved paragraph.
5 changes: 5 additions & 0 deletions CHANGELOG.d/2.13.1-partial-image-regions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Preserve partial visual regions

- Retain valid salient image regions for panel-level OCR and search.
- Also analyze the parent image when locator coverage is partial so text outside
the returned panels remains searchable.
7 changes: 7 additions & 0 deletions CHANGELOG.d/2.13.1-separate-source-tables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Preserve adjacent source tables

## Fixed

- Keep consecutive persisted rows in separate buyer-facing tables when the
source post contains more than one HTML table, preserving the authored table
boundary without changing source text or semantic row content.
7 changes: 7 additions & 0 deletions CHANGELOG.d/2.13.1-source-indent-semantics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## Fix source-only indentation depth

- Keep leading spaces and ` ` available as diagnostics without persisting
them as authoritative structure; only declared HTML/CSS/OOXML or list
nesting is explicit.
- Keep expected structure and embedding channel failures retryable while
propagating unexpected defects to the durable ingestion ledger.
48 changes: 48 additions & 0 deletions add_translations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import re

with open("frontend/src/i18n.ts", "r") as f:
content = f.read()

translations = {
"Admin": {
"ko": "관리자",
"zh": "管理员",
"ja": "管理者",
"vi": "Quản trị viên"
},
"Admin settings": {
"ko": "관리자 설정",
"zh": "管理员设置",
"ja": "管理者設定",
"vi": "Cài đặt quản trị viên"
},
"Tenant brand name": {
"ko": "테넌트 브랜드명",
"zh": "租户品牌名称",
"ja": "テナントブランド名",
"vi": "Tên thương hiệu khách thuê"
},
"Save settings": {
"ko": "설정 저장",
"zh": "保存设置",
"ja": "設定を保存",
"vi": "Lưu cài đặt"
},
"Settings saved!": {
"ko": "설정이 저장되었습니다!",
"zh": "设置已保存!",
"ja": "設定が保存されました!",
"vi": "Đã lưu cài đặt!"
}
}

for eng, trans in translations.items():
content = content.replace(f' Refresh: "새로 고침",', f' Refresh: "새로 고침",\n "{eng}": "{trans["ko"]}",')
content = content.replace(f' Refresh: "조회",', f' Refresh: "조회",\n "{eng}": "{trans["ko"]}",')

content = content.replace(f' Refresh: "刷新",', f' Refresh: "刷新",\n "{eng}": "{trans["zh"]}",')
content = content.replace(f' Refresh: "更新",', f' Refresh: "更新",\n "{eng}": "{trans["ja"]}",')
content = content.replace(f' Refresh: "Làm mới",', f' Refresh: "Làm mới",\n "{eng}": "{trans["vi"]}",')

with open("frontend/src/i18n.ts", "w") as f:
f.write(content)
47 changes: 47 additions & 0 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,36 @@ async def _post_filter_options(


@app.get("/healthz")

@app.get("/api/settings", response_model=dict)
async def read_tenant_settings(
account: CurrentAccount = Depends(get_current_account),
pool: asyncpg.Pool = Depends(get_pool),
):
async with pool.acquire() as conn:
row = await conn.fetchrow("SELECT brand_name FROM tenant_settings WHERE id = 1")
if not row:
return {"brandName": "LineageWeave"}
return {"brandName": row["brand_name"]}

@app.patch("/api/settings", response_model=dict)
async def update_tenant_settings(
payload: dict,
account: CurrentAccount = Depends(get_current_account),
pool: asyncpg.Pool = Depends(get_pool),
):
# Only admins can change settings
_require_post_admin(account)
brand_name = payload.get("brandName", "LineageWeave")
async with pool.acquire() as conn:
await conn.execute(
"INSERT INTO tenant_settings (id, brand_name) VALUES (1, $1) "
"ON CONFLICT (id) DO UPDATE SET brand_name = $1",
brand_name
)
return {"brandName": brand_name}


async def healthz() -> dict[str, str]:
"""Liveness probe: the process is up. Does not touch Postgres."""
return {"status": "ok"}
Expand Down Expand Up @@ -1621,12 +1651,15 @@ async def _load_post_semantic_hints(conn: asyncpg.Connection, post_id: str) -> s
post.source_author_name,
post.source_company_code,
post.source_company_name,
source_company.entity_name as source_company_catalog_name,
post.source_process_unit_code,
post.source_process_unit_name,
source_process_unit.process_unit_name as source_process_unit_catalog_name,
post.source_sales_pool_code,
post.source_sales_pool_name,
post.source_customer_code,
post.source_customer_name,
source_customer.entity_name as source_customer_catalog_name,
post.source_project_code,
post.source_project_name,
post.secondary_grouping_key as project_field,
Expand All @@ -1635,6 +1668,12 @@ async def _load_post_semantic_hints(conn: asyncpg.Connection, post_id: str) -> s
from source_post post
join user_account author on author.user_account_id = post.author_account_id
left join corporate_entity customer on customer.corporate_entity_id = post.corporate_entity_id
left join corporate_entity source_company
on source_company.corporate_entity_code = nullif(btrim(post.source_company_code), '')
left join process_unit source_process_unit
on source_process_unit.process_unit_code = nullif(btrim(post.source_process_unit_code), '')
left join corporate_entity source_customer
on source_customer.corporate_entity_code = nullif(btrim(post.source_customer_code), '')
left join account_affiliation account_aff
on account_aff.user_account_id = post.author_account_id
left join corporate_entity affiliated
Expand Down Expand Up @@ -1683,12 +1722,15 @@ async def _load_post_semantic_hints(conn: asyncpg.Connection, post_id: str) -> s
source_author_name=source_author_name,
source_company_code=first["source_company_code"],
source_company_name=first["source_company_name"],
source_company_catalog_name=first["source_company_catalog_name"],
source_business_unit_code=first["source_process_unit_code"],
source_process_unit_name=first["source_process_unit_name"],
source_process_unit_catalog_name=first["source_process_unit_catalog_name"],
source_sales_pool_code=first["source_sales_pool_code"],
source_sales_pool_name=first["source_sales_pool_name"],
source_customer_code=first["source_customer_code"],
source_customer_name=first["source_customer_name"],
source_customer_catalog_name=first["source_customer_catalog_name"],
source_project_code=first["source_project_code"],
source_project_name=first["source_project_name"],
source_context_present=source_context_present,
Expand Down Expand Up @@ -2389,9 +2431,12 @@ async def read_post_summary(
stored = await fetch_persisted_summary(conn, post_id)
if stored is not None:
return stored
stale = await fetch_persisted_summary(conn, post_id, allow_stale=True)
with use_llm_metadata(post_metadata):
client = _post_summary_client()
if not client.available:
if stale is not None:
return stale
raise HTTPException(
status.HTTP_503_SERVICE_UNAVAILABLE,
"Post summary is unavailable: set ORCHESTRATOR_BASE_URL / ORCHESTRATOR_API_KEY",
Expand All @@ -2408,6 +2453,8 @@ async def read_post_summary(
else:
summary = await asyncio.to_thread(client.summarize, post["post_title"], normalized_body)
except (HttpClientError, KeyError, OSError, TypeError, ValueError) as exc:
if stale is not None:
return stale
raise HTTPException(
status.HTTP_503_SERVICE_UNAVAILABLE,
"Post summary is unavailable: contextual-orchestrator returned no complete evidence object",
Expand Down
106 changes: 105 additions & 1 deletion backend/app/post_content_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
STALE_RUNNING_INTERVAL = timedelta(minutes=15)
_ACTIVE = {QUEUED, RUNNING}
POST_CONTENT_MAX_ATTEMPTS = 3
POST_CONTENT_RETRY_INTERVAL = "5 minutes"
POST_CONTENT_RETRY_INTERVAL = timedelta(minutes=5)


@dataclass(frozen=True)
Expand Down Expand Up @@ -296,6 +296,110 @@ async def ensure_post_content_job(
)


async def requeue_failed_post_content_job(
conn: asyncpg.Connection,
post_id: str,
body: str,
) -> PostContentJobRequest:
"""Explicitly requeue one terminal job without weakening automatic retry limits."""
digest = source_body_sha256(body)
row = await conn.fetchrow(
"""
select status_code
from post_content_ingestion_job
where post_id = $1
for update
""",
post_id,
)
if row is None:
raise ValueError(f"post-content job does not exist: {post_id}")
if str(row["status_code"]) != FAILED:
raise ValueError("only a failed post-content job can be explicitly requeued")
await conn.execute(
"""
update post_content_ingestion_job
set source_body_sha256 = $2,
status_code = $3,
attempt_count = 0,
queued_at = now(),
started_at = null,
completed_at = null,
updated_at = now(),
last_error_code = null,
last_error_detail = null
where post_id = $1
and status_code = $4
""",
post_id,
digest,
QUEUED,
FAILED,
)
await _record_status(
conn,
post_id,
QUEUED,
detail_text="operator requested an explicit post-content retry",
)
return PostContentJobRequest(post_id, digest, QUEUED, True)


async def record_post_content_backfill_success(
conn: asyncpg.Connection,
post_id: str,
body: str,
) -> PostContentJobRequest:
"""Synchronize a completed operator backfill with the durable job ledger."""
digest = source_body_sha256(body)
row = await conn.fetchrow(
"""
select status_code
from post_content_ingestion_job
where post_id = $1
for update
""",
post_id,
)
if row is not None and str(row["status_code"]) in {QUEUED, RUNNING}:
raise ValueError("cannot finalize a backfill while the job is active")
if row is None:
await conn.execute(
"""
insert into post_content_ingestion_job
(post_id, source_body_sha256, status_code, completed_at)
values ($1, $2, $3, now())
""",
post_id,
digest,
SUCCEEDED,
)
else:
await conn.execute(
"""
update post_content_ingestion_job
set source_body_sha256 = $2,
status_code = $3,
started_at = null,
completed_at = now(),
updated_at = now(),
last_error_code = null,
last_error_detail = null
where post_id = $1
""",
post_id,
digest,
SUCCEEDED,
)
await _record_status(
conn,
post_id,
SUCCEEDED,
detail_text="operator backfill persisted post-content evidence",
)
return PostContentJobRequest(post_id, digest, SUCCEEDED, False)


async def republish_queued_post_content_jobs(
client: redis.Redis,
pool: asyncpg.Pool,
Expand Down
Loading
Loading