feat: keep GNB Event Lineage focus on a linked DAG node (v2.17.0) - #264
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthrough관련 게시물 탐색 시 ChangesEvent Lineage 포커스 유지
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: 🟡 Moderate · up to Opening a linked DAG post from an analysis view can lose the cutoff context needed to display the correct historical data and warnings, which may make content appear current when it is not. Merge should wait until that context is preserved during DAG navigation. Sequence Diagram(s)sequenceDiagram
participant PostDetailPopup
participant selectPost
participant EventLineageFocus
PostDetailPopup->>selectPost: 관련 게시물 ID와 현재 진입 경로 플래그 전달
selectPost->>EventLineageFocus: 포커스 상태 반영
EventLineageFocus-->>PostDetailPopup: Event Lineage 포커스와 후속 작업 상태 표시
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 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 |
|
Cycle 2026-08-20T07:03 KST: GitHub writes recovered. Exact head |
|
v2.19.0 buyer increment is now stacked as #266 ( |
|
@coderabbitai autofix Commit the fix directly to the current PR branch. Keep this slice limited to the unresolved analysis-run cutoff-context finding. In addition to the production fix, extend the existing |
|
The agent ran but didn't make any changes. The issues may already be fixed or require manual intervention. |
|
@opencode-agent Fix the unresolved cutoff-context review finding on this existing PR branch only. Preserve analysis-run context across PostDetailPopup DAG navigation: retain the originating run's knowledge_cutoff plus its visible_posts metadata, and when a linked DAG target is selected, derive that target post's own live_after_cutoff value from visible_posts instead of copying the currently-open post's boolean or dropping the cutoff. Add a RED regression that fails on the current head and proves rewritten vs unchanged targets get different flags while the same knowledge_cutoff survives; then make the narrow GREEN fix. Preserve existing GNB focus flags and all current behavior. Run the focused frontend regression, App tests, lint, and production build. Do not touch #258/#260-#263/#265, do not merge, and do not resolve the review thread unless the current exact head actually contains and passes the fix. |
|
@opencode-agent Fix the unresolved cutoff-context bug directly on the current branch; do not create or commit a temporary workflow. Scope only Root cause: opening a post from an Analysis Run sets Required behavior:
|
|
Current exact head |
0664679 to
40bde8b
Compare
0aaf992 to
2687c5b
Compare
…lineage-v2160' into repair/pr264-current-base
…lineage-v2160' into repair/pr264-current-base
…lineage-v2160' into repair/pr264-current-base
|
@opencode-agent @devin-ai-integration Please review the exact current head |
|
@opencode-agent @devin-ai-integration Please review exact current head 8832216 against base 457e7e1. Fixed the remaining non-English timeline post-button accessibility label finding; focused/full frontend verification is green. |
…lineage-v2160' into repair/pr264-current # Conflicts: # backend/app/main.py # docker/postgres-init/migrate.sh # docs/product-technical-gap-baseline.md # frontend/src/App.tsx # frontend/src/postBodyDisplay.ts # lineageweave/chunking.py
|
Restacked onto current #263 head 5bd176c with normal merge commit 39f2126. Local verification on the exact pushed tree: backend 779 passed, 16 skipped; frontend 177 passed; frontend lint, build, and Storybook build passed; compileall/actionlint and diff checks passed. The restack also resolved the ontology/ADR collision without weakening tests: W3C ORG/SKOS interoperability metadata is retained and the duplicate ADR number is now 0125. Awaiting independent review and hosted Checks on the current head. |
|
@opencode-agent Rapidly stabilize this stack without reverting any Buyer surface. Read the current exact head of parent #263, ordinary-merge that parent into this branch, and resolve only real conflicts; never force-push or rewrite history. Preserve relationship-path explanation, ontology semantics, cutoff navigation, authorization boundaries, five-locale buyer copy, and every existing regression. Run the smallest focused suites first, then frontend/backend/ontology/documentation gates available to the agent. Publish the new exact head, compare receipt showing behind 0, current-head review status, and test evidence. Do not merge the PR or update descendants; once current-head gates and independent approval are terminal-success, keep auto-merge armed. |
…lineage-v2160' into codex/review-pr264-current # Conflicts: # docs/ontology/lineageweave-kg.ttl # docs/product-technical-gap-baseline.md
There was a problem hiding this comment.
📝 Info: Actor-type parsing now drops unknown/whitespace types instead of defaulting to person
parse_summary_response now maps only None/'person'/'organization'/'team' and continues on anything else (post_summary.py). This is the intended 'reject unknown actor type instead of inventing a person' change (new test test_explicit_unknown_actor_type_is_dropped), but note the stricter matching also drops values that the old code would have coerced to person, e.g. a stray empty string or a type with trailing whitespace ('person '). If the orchestrator ever emits such near-miss values, those R&R rows now silently disappear from the summary rather than being retained as person. Confirm the prompt constrains actor_type to the exact tokens.
(Refers to this code)
Was this helpful? React with 👍 or 👎 to provide feedback.
| through = int(session["context_summary_through_ordinal"]) | ||
| pending_count = int( | ||
| await conn.fetchval( | ||
| "select count(*) from global_ask_turn where global_ask_session_id = $1 and turn_ordinal > $2", | ||
| session_id, | ||
| through, | ||
| ) | ||
| ) | ||
| compress_count = min(compression_batch, max(0, pending_count - recent_limit)) | ||
| compress_rows = ( | ||
| await conn.fetch( | ||
| """ | ||
| select turn_ordinal, question_text, answer_text | ||
| from global_ask_turn | ||
| where global_ask_session_id = $1 | ||
| and turn_ordinal > $2 | ||
| order by turn_ordinal | ||
| limit $3 | ||
| """, | ||
| session_id, | ||
| through, | ||
| compress_count, | ||
| ) | ||
| if compress_count | ||
| else [] | ||
| ) | ||
| recent_rows = await conn.fetch( | ||
| """ | ||
| select turn_ordinal, question_text, answer_text | ||
| from global_ask_turn | ||
| where global_ask_session_id = $1 | ||
| and turn_ordinal > $2 | ||
| order by turn_ordinal desc | ||
| limit $3 | ||
| """, | ||
| session_id, | ||
| through, | ||
| recent_limit, | ||
| ) | ||
| return GlobalAskContext( | ||
| session_id=session_id, | ||
| summary=session["context_summary"], | ||
| summary_through_ordinal=through, | ||
| recent_turns=tuple( | ||
| (int(row["turn_ordinal"]), row["question_text"], row["answer_text"]) | ||
| for row in reversed(recent_rows) | ||
| ), | ||
| compress_turns=tuple( | ||
| (int(row["turn_ordinal"]), row["question_text"], row["answer_text"]) | ||
| for row in compress_rows | ||
| ), | ||
| ) |
There was a problem hiding this comment.
📝 Info: Global Ask context compression can drop a middle turn if turns ever exceed the steady-state window
load_global_ask_context (post_chat_ingestion.py) computes compress_count = min(compression_batch, max(0, pending_count - recent_limit)) and separately loads the oldest compress_count and newest recent_limit turns. In steady state (one turn added per request, one compressed) pending_count stays at recent_limit+1, so compress_count is 1 and every pending turn is either compressed or in recent_turns. However, if pending ever exceeds recent_limit + compression_batch (e.g. multiple turns persisted between compressions, or a tuning change), the turns between the compressed batch and the recent window are neither summarized nor included as recent context and are silently excluded from the model's continuity context until they eventually fall into a later compression batch. This is continuity-only context (never evidence), so impact is low, but worth noting if the batching constants change.
Was this helpful? React with 👍 or 👎 to provide feedback.
| await publish_operation_event( | ||
| valkey, | ||
| account.user_account_id, | ||
| "preferences_updated", | ||
| "Locale preference updated", | ||
| ) |
There was a problem hiding this comment.
📝 Info: Operation events raise after the DB commit
The new publish_operation_event calls (e.g. update_me_preferences, resolve_customer_master_hint, rebuild_lineage_graph, create_analysis_run, rebuild_period_report_endpoint, and the Global Ask paths in main.py) run after the PostgreSQL mutation has already committed and are not wrapped in try/except. A Valkey outage will surface as a 500 to the caller even though the durable write succeeded. This matches the existing publish_activity_event pattern and ADR 0125 explicitly states Valkey failure has the same runtime behavior as the activity stream, so it is intentional rather than a regression — noted for awareness since it broadens the set of endpoints whose success now depends on Valkey being reachable.
Was this helpful? React with 👍 or 👎 to provide feedback.
| """, | ||
| post_id, | ||
| ) | ||
| candidate_rows = await conn.fetch("select corporate_entity_id, entity_name from corporate_entity") | ||
| candidate_rows = ( | ||
| await conn.fetch( | ||
| "select corporate_entity_id, entity_name from corporate_entity " | ||
| "where corporate_entity_id = any($1::uuid[])", | ||
| list(authorized_corporate_entity_ids), | ||
| ) | ||
| if authorized_corporate_entity_ids | ||
| else [] | ||
| ) | ||
| candidates = [ | ||
| CorporateEntityCandidate(str(row["corporate_entity_id"]), row["entity_name"]) | ||
| for row in candidate_rows |
There was a problem hiding this comment.
📝 Info: Counterparty resolution candidate set narrowed to authorized entities
fetch_post_counterparties (entity_relationship_ingestion.py) and fetch_relationship_network (entity_relationship_ingestion.py) now build their CorporateEntityCandidate list only from corporate_entity rows whose id is in the authorized-entity set, replacing the previous unconditional select ... from corporate_entity. This is an intentional security narrowing (covered by the new test_counterparties_do_not_expose_unauthorized_catalog_entity), but it is also a behavioral change: a counterparty name that appears on a public post and matches a real catalog entity the caller is NOT affiliated with will now resolve to null instead of carrying the catalog id. That is the safer outcome (the related endpoints re-check ABAC anyway), but consumers relying on public-post names resolving to catalog ids will see fewer resolved links.
(Refers to this code)
Was this helpful? React with 👍 or 👎 to provide feedback.
| ) | ||
| candidate_ids = list( | ||
| dict.fromkeys([lineage_anchor_id, *lineage_neighbor_ids, *candidate_ids[1:]]) | ||
| )[:limit] | ||
| )[:candidate_budget] | ||
| else: | ||
| candidate_ids = [] | ||
| candidate_ids = candidate_ids[:candidate_budget] | ||
| lineage_neighbor_id_set = frozenset(lineage_neighbor_ids) | ||
|
|
||
| rows = await conn.fetch( | ||
| """ | ||
| select post_id, post_title, post_body, visibility_code, corporate_entity_id, | ||
| created_at, | ||
| source_system_code, source_record_key, source_author_code, source_author_name, | ||
| source_company_code, source_company_name, source_process_unit_code, | ||
| source_process_unit_name, source_sales_pool_code, source_sales_pool_name, |
There was a problem hiding this comment.
📝 Info: Global Ask overfetch bounds candidate ordering, not the DB fetch count
In gather_global_chat_sources, candidate_budget = min(_POST_CHAT_CANDIDATE_LIMIT, max(limit, limit*4)) (post_chat_ingestion.py) only widens the candidate_ids array passed as $2 for array_position ordering; the source query still uses limit $3 = limit and relies on the SQL where visibility_code='public' or corporate_entity_id = any($1) clause to drop hidden rows before the limit. This works because the DB-level visibility predicate mirrors the Python can_see_post check, so the overfetch improves relevance ordering without returning more than limit rows. If can_see_post ever becomes stricter than that SQL predicate, fewer than limit visible sources could be returned despite more visible candidates existing beyond the fetch window.
(Refers to this code)
Was this helpful? React with 👍 or 👎 to provide feedback.
| } | ||
| }) | ||
| .catch(() => { | ||
| if (disposed) return; | ||
| if (disposed || !isCurrent()) return; | ||
| setImageContent([]); | ||
| setStructureUnits([]); | ||
| }); | ||
| contentReloadRef.current = reloadContent; | ||
| reloadContent(); | ||
| void reloadContent(); | ||
| fetchPostBookmark(accessToken, postId) | ||
| .then((r) => setBookmarked(r.bookmarked)) | ||
| .then((r) => { | ||
| if (isCurrent()) setBookmarked(r.bookmarked); | ||
| }) | ||
| .catch(() => { | ||
| setBookmarked(null); | ||
| if (isCurrent()) setBookmarked(null); | ||
| }); | ||
| fetchPostEvaluation(accessToken, postId) | ||
| .then((r) => setEvaluation(r.responses)) | ||
| .catch(() => setEvaluation([])); | ||
| .then((r) => { | ||
| if (isCurrent()) setEvaluation(r.responses); | ||
| }) | ||
| .catch(() => { | ||
| if (isCurrent()) setEvaluation([]); | ||
| }); | ||
| fetchPostFiveW1H(accessToken, postId) | ||
| .then(setFiveW1H) | ||
| .catch(() => setFiveW1H(null)); | ||
| .then((value) => { | ||
| if (isCurrent()) setFiveW1H(value); | ||
| }) | ||
| .catch(() => { | ||
| if (isCurrent()) setFiveW1H(null); | ||
| }); | ||
| fetchPostKeymen(accessToken, postId) | ||
| .then((r) => { | ||
| if (!isCurrent()) return; | ||
| setKeymen(r.keymen); | ||
| setSourceAuthorContext(r.source_author_context ?? null); | ||
| }) | ||
| .catch(() => { | ||
| if (!isCurrent()) return; | ||
| setKeymen([]); | ||
| setSourceAuthorContext(null); | ||
| }); | ||
| fetchPostCounterparties(accessToken, postId) | ||
| .then((r) => setCounterparties(r.counterparties)) | ||
| .catch(() => setCounterparties([])); | ||
| fetchPostLineage(accessToken, postId).then(setLineage).catch(() => setLineage(null)); | ||
| .then((r) => { | ||
| if (isCurrent()) setCounterparties(r.counterparties); | ||
| }) | ||
| .catch(() => { | ||
| if (isCurrent()) setCounterparties([]); | ||
| }); | ||
| fetchPostLineage(accessToken, postId) | ||
| .then((value) => { | ||
| if (isCurrent()) setLineage(value); | ||
| }) | ||
| .catch(() => { | ||
| if (isCurrent()) setLineage(null); | ||
| }); | ||
| fetchPostAffiliateTree(accessToken, postId) | ||
| .then((r) => setAffiliateTrees(r.trees)) | ||
| .catch(() => setAffiliateTrees([])); | ||
| fetchPostVocEvidence(accessToken, postId).then(setVocEvidence).catch(() => setVocEvidence(null)); | ||
| .then((r) => { | ||
| if (isCurrent()) setAffiliateTrees(r.trees); | ||
| }) | ||
| .catch(() => { | ||
| if (isCurrent()) setAffiliateTrees([]); | ||
| }); | ||
| fetchPostVocEvidence(accessToken, postId) | ||
| .then((value) => { | ||
| if (isCurrent()) setVocEvidence(value); | ||
| }) | ||
| .catch(() => { | ||
| if (isCurrent()) setVocEvidence(null); | ||
| }); | ||
|
|
||
| return () => { | ||
| disposed = true; | ||
| if (contentPollTimer !== undefined) window.clearTimeout(contentPollTimer); | ||
| if (isCurrent()) detailRequestGeneration.current = generation + 1; | ||
| if (contentReloadRef.current === reloadContent) { | ||
| contentReloadRef.current = () => undefined; | ||
| } |
There was a problem hiding this comment.
📝 Info: PostDetailPopup request-generation guard invalidates stale in-flight fetches
The new detailRequestGeneration ref (App.tsx, effect at 1761-1891) stamps each popup load with a generation and every fetch callback early-returns unless its captured generation still matches. The cleanup increments the generation, so responses from a previously-selected post can no longer overwrite state after the popup navigates to a linked node (the scenario the new deferPostOneSummary test exercises). Note the generation advances by 2 per navigation (cleanup sets generation+1, the next effect sets current+1 again); this is harmless since only equality is checked, but it means generation values are non-contiguous.
(Refers to this code)
Was this helpful? React with 👍 or 👎 to provide feedback.
| await conn.execute( | ||
| """ | ||
| insert into organization_name_resolution | ||
| (raw_organization_name, resolved_organization_name, | ||
| (raw_organization_name, context_sha256, resolved_organization_name, | ||
| verification_status_code, verification_evidence_url) | ||
| values ($1, $2, $3, $4) | ||
| on conflict (raw_organization_name) do update set | ||
| values ($1, $2, $3, $4, $5) | ||
| on conflict (raw_organization_name, context_sha256) do update set | ||
| resolved_organization_name = excluded.resolved_organization_name, | ||
| verification_status_code = excluded.verification_status_code, | ||
| verification_evidence_url = excluded.verification_evidence_url, | ||
| resolved_at = now() | ||
| """, | ||
| resolution.raw_organization_name, | ||
| context_sha256, | ||
| resolution.resolved_organization_name, | ||
| resolution.verification_status_code, | ||
| resolution.verification_evidence_url, |
There was a problem hiding this comment.
📝 Info: context_sha256 default '' leaves pre-migration cache rows as permanent misses
Migration 0051 backfills existing organization_name_resolution rows with context_sha256 = '' and makes it part of the primary key, while resolve_organization_name always looks up and writes a real 64-hex digest (organization_name_resolution_ingestion.py). Existing rows keyed by '' can therefore never match a real-context lookup and become dead entries that will only be superseded when a new context digest row is inserted. This is harmless (cache miss, not incorrect data) and consistent with ADR 0008's intentional context-scoping, but it does mean prior resolutions are effectively discarded and re-queried once.
(Refers to this code)
Was this helpful? React with 👍 or 👎 to provide feedback.
| await publish_operation_event( | ||
| valkey, | ||
| account.user_account_id, | ||
| "preferences_updated", | ||
| "Locale preference updated", | ||
| ) |
There was a problem hiding this comment.
📝 Info: Account-scoped mutations now 500 on Valkey failure after the durable write commits
Several handlers (update_me_preferences, resolve_customer_master_hint, rebuild_lineage_graph, rebuild_period_report_endpoint, create_analysis_run, start_analysis_run, write_post_bookmark) now call publish_operation_event/publish_activity_event after the PostgreSQL write has committed. A Valkey error therefore surfaces as a 500 even though the durable mutation succeeded. ADR 0103 explicitly states this matches the existing activity-stream contract (the caller must recover/retry the DB write), so this is consistent with prior behavior rather than a new regression, but it does widen the set of endpoints where a Valkey outage produces a confusing post-commit 500.
Was this helpful? React with 👍 or 👎 to provide feedback.
| session_id = await ensure_global_ask_session( | ||
| conn, account.user_account_id, request.session_id | ||
| ) | ||
| if session_id is None: | ||
| raise HTTPException(status.HTTP_404_NOT_FOUND, "Global Ask session not found") | ||
| conversation = await load_global_ask_context(conn, session_id) |
There was a problem hiding this comment.
📝 Info: Failed Global Ask turns create orphaned empty sessions
ask_agent calls ensure_global_ask_session before generating the answer (main.py). The frontend only persists the returned session_id on success (App.tsx). So when an answer generation or compression fails (503), a new global_ask_session row was already created, but the client never learns its id, and the next attempt (still holding no/old id) creates yet another session. Repeated failures accumulate orphaned empty sessions. Minor for a demo, but a reviewer may want session creation deferred until an answer is actually produced, or a cleanup path.
Was this helpful? React with 👍 or 👎 to provide feedback.
* ci: run buyer-surface cutoff repair test-first * ci: cover direct and DAG cutoff navigation * ci: restore canonical tests workflow * ci: remove temporary cutoff repair workflow * ci: repair analysis-run DAG cutoff context * ci: repair analysis-run cutoff navigation * chore: remove one-shot repair workflow * chore: remove duplicate cutoff repair workflow * ci: execute PR 264 cutoff repair * ci: run PR 264 cutoff repair on branch push * ci: repair ontology-grounded semantic paths on PR 264 * ci: run cutoff repair through canonical tests workflow * ci: stage deterministic PR 264 cutoff repair * ci: execute deterministic cutoff repair * fix(ui): preserve analysis-run cutoff across DAG navigation * ci: guard PR 264 against the unrelated ontology writer * ci: restore canonical PR 264 verification * ci: add direct ontology semantic-path repair * ci: remove completed cutoff repair helper * ci: replace stale semantic repair workflow * ci: execute ontology semantic-path repair * test: stage ontology semantic-path repair * ci: run test-first ontology semantic-path repair * ci: move semantic-path repair out of PR 264 * ci: remove relocated semantic-path repair helper * ci: consolidate PR 264 Knowledge Graph semantic repair * ci: keep semantic repair exact-head safe * test: align semantic repair fixtures with production schema * test: require TEPP-backed project history evidence * test(ui): require the TEPP project timeline answer surface * feat: add the strict TEPP project-history client * feat: select authorized evidence for TEPP project histories * feat: open Customer master related posts onto Event Lineage (v2.15.0) Customer master names authorized customer entities as current and to open a related post to read Event Lineage. That open focuses the popup Event Lineage heading and names Keyman and evaluation next. Home-list opens do not. No TEPP theta or customer is invented. * feat(ui): add the accessible TEPP project history timeline * style(ui): present the TEPP timeline responsively * docs(storybook): demonstrate the minimum TEPP buyer timeline * fix: preserve Event Lineage focus after related loads * feat: open Ask Agent cited posts onto Event Lineage (v2.16.0) Ask Agent names authorized cited posts as current after an answer and to open one to read Event Lineage. That open focuses the popup Event Lineage heading and names Keyman and evaluation next. Home-list opens do not. No TEPP theta or cited post is invented. * feat: keep GNB Event Lineage focus on a linked DAG node (v2.17.0) Opening a linked Event Lineage node from a GNB-focused popup keeps Event Lineage focused and names Keyman and evaluation next. A home-list DAG walk does not. No TEPP theta or cited post is invented. * fix(ui): preserve analysis-run cutoff across DAG navigation * ci: materialize the bounded PR 281 integration patch * ci: verify and publish the PR 281 TEPP integration * test: define Global Ask public verification boundary * docs: define Global Ask public verification boundary * docs: record Global Ask public verification slice * ci: run project-history repair on exact branch pushes * ci: bound project-history repair runtime * ci: expose project-history repair to PR exact-head runs * ci: trigger project-history verification on PR edits * test(red): define evidence-bound project history projection * feat: build visible project-history evidence paths * test(red): require ABAC-safe project history repository * feat: load project histories through ABAC-safe evidence reads * test(red): require reversible project-history lookup indexes * feat: index and roll back exact project-history lookups * test: prove project history authorization in PostgreSQL * test(red): retain the authorized focus in bounded histories * fix: keep the current event in truncated project histories * test(red): define the authorized project-history HTTP contract * feat: expose a strict project-history API router * merge: restack onto feat/event-lineage-node-keeps-gnb-focus-v2170 Cascade-merges the current #264 tip into this branch, which was still on much older ADR/prop naming (this branch forked before the 0070-0075 -> 0092-0097 ADR renumbering and before the focusAskAfterRelated -> landOnAsk/focusAskOnLand rename landed upstream). Conflicts were the routine kind: doc prose taking origin's already-correct ADR numbers, and re-applying the established landOnAsk/focusAskOnLand naming over this branch's stale focusAskAfterRelated. isoWeek.ts/isoWeek.test.ts add/add conflict resolved by taking origin's version (validates the Gregorian calendar date before parsing). Also fixes a real bug found while resolving: this branch's own new docs/adr/0072-0075-*.md files were byte-identical duplicates of the already-renumbered 0094-0097 ADRs (content differed only in cross-reference numbers) -- deleted the four stale duplicates. Two pre-existing failures were investigated and left alone (confirmed via git stash against this branch's own pristine tip, not introduced by this merge): a "test(red)" frontend HTTP contract test for TeppProjectHistory and a "test(red)" PostgreSQL authorization test referencing a not-yet-migrated source_company_name column. Both are this branch's own in-progress TDD red state, not something to implement here. 142 frontend tests pass (excluding the known-red TeppProjectHistory suite), 564 python tests pass excluding the two known-red project- history tests, tsc -b passes for everything except the pending TeppProjectHistory component. * test(red): define the accessible project-history timeline * feat: render accessible evidence-bound project histories * test(red): require semantic Global Ask production integration * test(red): require opt-in Global Ask verification contract * test(red): require an explicit project-history disclosure * test(red): require explicit Global Ask verification request * test(red): require Buyer public-verification controls * test(red): preserve multilingual semantic query terms * fix: preserve multilingual Global Ask query terms * ci: apply the bounded Global Ask verification integration * test(red): preserve multilingual claim relevance * fix: preserve multilingual claim relevance * test(red): strip project evidence before public egress * test(red): remove raw evidence from public claims * test: preserve multilingual Global Ask relevance * ci: repair the Global Ask pnpm provisioning gate * ci: repair pinned pnpm provisioning directly * chore: remove self-modifying project history repair * ci: make pnpm repair indentation-independent * fix: use canonical popup focus contract * fix: integrate bounded Global Ask public verification * fix: keep verification ADR numbering unique * fix(project-history): use explicit finite score predicate * test(sql): require audited TEPP project-history statements * fix(sql): audit fixed-alias TEPP eligibility queries * fix(project-history): expose bounded timeline contract * test(orchestrator): require supported auto verification contract * ci: repair public claim orchestrator contract * ci: expose public-claim repair to PR checks * test(retrieval): require indexable semantic search * test(retrieval): reject concatenated semantic scans * perf(retrieval): use indexable semantic predicates * perf(db): index Global Ask semantic fields * perf(db): add semantic index rollback * build(db): apply semantic search migration * docs(doctoring): trace semantic search indexing * docs(changelog): record indexable semantic retrieval * ci: verify semantic indexes with public-claim repair * fix(orchestrator): use supported public-claim contract * docs(adr): align public verification with governed auto mode * ci: remove completed public-claim repair workflow * ci: repair pinned pnpm provisioning * docs: record Global Ask integration CI recovery * ci: pin Global Ask pnpm provisioning * fix: make project history protocol explicit * fix: localize public verification guidance * fix: parameterize global ask candidate filter * style: normalize post-chat ingestion imports * fix: align project history projection response contract * fix: align project history builder with API contract * fix: localize public verification controls * feat: search verified multilingual organization labels (#316) * feat: search verified multilingual organization labels * fix: replay organization label indexes * fix: make organization label indexes reversible --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
a2a0ad8
into
feat/ask-agent-open-focus-event-lineage-v2160
Buyer result
Linked Event Lineage navigation preserves the originating Buyer/GNB context,
keeps the selected post current, and names Keyman and evaluation as the next
read. Ask Agent recovers once from an expired saved session, and every Event
Lineage timeline heading, list, and post action has a localized accessible
label.
Exact stack state
8832216fcd2b0a1dcb486ea83269e25695ce378afeat/ask-agent-open-focus-event-lineage-v2160457e7e121adb79424f382a9868a84f6fb6e402f2protected-branch bypass was used.
Review repair
The latest valid finding was that timeline post-button aria labels used the
English source key without translations. Korean, Chinese, Japanese, and
Vietnamese translations are now present and covered by the shared i18n
contract test. Existing session recovery, target-specific cutoff, ABAC, and
unknown-actor safeguards remain unchanged.
Verification
166 passed(17 files).passed.
git diff --checkand CodeGraph sync passed.Merge gate
Review this exact head. Merge only after an independent formal approval, all
required Checks are terminal-success on this SHA, and no valid unresolved
review thread remains. If a Check fails, fix its root cause, validate locally,
push normally, and re-check the new exact head.