Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
140 commits
Select commit Hold shift + click to select a range
943ab63
ci: run buyer-surface cutoff repair test-first
seonghobae Aug 19, 2026
8e5be3e
ci: cover direct and DAG cutoff navigation
seonghobae Aug 19, 2026
3f4c41e
ci: restore canonical tests workflow
seonghobae Aug 19, 2026
4b80c87
ci: remove temporary cutoff repair workflow
seonghobae Aug 19, 2026
c96fa28
ci: repair analysis-run DAG cutoff context
seonghobae Aug 19, 2026
c23b81a
ci: repair analysis-run cutoff navigation
seonghobae Aug 19, 2026
b78b8c5
chore: remove one-shot repair workflow
seonghobae Aug 19, 2026
be0a544
chore: remove duplicate cutoff repair workflow
seonghobae Aug 19, 2026
aad290b
ci: execute PR 264 cutoff repair
seonghobae Aug 19, 2026
8cf3491
ci: run PR 264 cutoff repair on branch push
seonghobae Aug 19, 2026
4e4dae5
ci: repair ontology-grounded semantic paths on PR 264
seonghobae Aug 19, 2026
1b73e66
ci: run cutoff repair through canonical tests workflow
seonghobae Aug 19, 2026
796af57
ci: stage deterministic PR 264 cutoff repair
seonghobae Aug 19, 2026
a409895
ci: execute deterministic cutoff repair
seonghobae Aug 19, 2026
821432a
fix(ui): preserve analysis-run cutoff across DAG navigation
github-actions[bot] Aug 19, 2026
c616a4a
ci: guard PR 264 against the unrelated ontology writer
seonghobae Aug 19, 2026
f74377f
ci: restore canonical PR 264 verification
seonghobae Aug 19, 2026
3e37f02
ci: add direct ontology semantic-path repair
seonghobae Aug 19, 2026
f010d9b
ci: remove completed cutoff repair helper
seonghobae Aug 19, 2026
6cf7f3b
ci: replace stale semantic repair workflow
seonghobae Aug 19, 2026
f68b8d7
ci: execute ontology semantic-path repair
seonghobae Aug 19, 2026
af9d25a
test: stage ontology semantic-path repair
seonghobae Aug 19, 2026
06a5b37
ci: run test-first ontology semantic-path repair
seonghobae Aug 19, 2026
8431998
ci: move semantic-path repair out of PR 264
seonghobae Aug 19, 2026
4e601f6
ci: remove relocated semantic-path repair helper
seonghobae Aug 19, 2026
757757b
ci: consolidate PR 264 Knowledge Graph semantic repair
seonghobae Aug 19, 2026
2234fc7
ci: keep semantic repair exact-head safe
seonghobae Aug 19, 2026
0664679
test: align semantic repair fixtures with production schema
seonghobae Aug 19, 2026
35143d7
test: require TEPP-backed project history evidence
seonghobae Aug 20, 2026
7f726a3
test(ui): require the TEPP project timeline answer surface
seonghobae Aug 20, 2026
d7f4f14
feat: add the strict TEPP project-history client
seonghobae Aug 20, 2026
ff69cfa
feat: select authorized evidence for TEPP project histories
seonghobae Aug 20, 2026
101dcf6
feat: open Customer master related posts onto Event Lineage (v2.15.0)
seonghobae Aug 19, 2026
f6fccb1
feat(ui): add the accessible TEPP project history timeline
seonghobae Aug 20, 2026
df0ee78
style(ui): present the TEPP timeline responsively
seonghobae Aug 20, 2026
ce41de1
docs(storybook): demonstrate the minimum TEPP buyer timeline
seonghobae Aug 20, 2026
59b8153
fix: preserve Event Lineage focus after related loads
seonghobae Aug 20, 2026
43070dd
feat: open Ask Agent cited posts onto Event Lineage (v2.16.0)
seonghobae Aug 19, 2026
674b33e
feat: keep GNB Event Lineage focus on a linked DAG node (v2.17.0)
seonghobae Aug 19, 2026
8be6cca
fix(ui): preserve analysis-run cutoff across DAG navigation
seonghobae Aug 20, 2026
f9d750b
ci: materialize the bounded PR 281 integration patch
seonghobae Aug 20, 2026
2c69d97
ci: verify and publish the PR 281 TEPP integration
seonghobae Aug 20, 2026
e358eb9
test: define Global Ask public verification boundary
seonghobae Aug 20, 2026
554a48a
docs: define Global Ask public verification boundary
seonghobae Aug 20, 2026
7d495a7
docs: record Global Ask public verification slice
seonghobae Aug 20, 2026
d1e7a19
chore: restack project-history TDD on exact buyer parent
seonghobae Aug 20, 2026
3ea68e7
ci: run project-history repair on exact branch pushes
seonghobae Aug 20, 2026
88b1ac6
ci: bound project-history repair runtime
seonghobae Aug 20, 2026
fe8c2f1
ci: expose project-history repair to PR exact-head runs
seonghobae Aug 20, 2026
a2a677d
ci: trigger project-history verification on PR edits
seonghobae Aug 20, 2026
ca91dfe
test(red): define evidence-bound project history projection
seonghobae Aug 20, 2026
f174743
feat: build visible project-history evidence paths
seonghobae Aug 20, 2026
ed01445
test(red): require ABAC-safe project history repository
seonghobae Aug 20, 2026
854081f
feat: load project histories through ABAC-safe evidence reads
seonghobae Aug 20, 2026
c57f1cd
test(red): require reversible project-history lookup indexes
seonghobae Aug 20, 2026
891eadd
feat: index and roll back exact project-history lookups
seonghobae Aug 20, 2026
cbd1c08
test: prove project history authorization in PostgreSQL
seonghobae Aug 20, 2026
4761443
test(red): retain the authorized focus in bounded histories
seonghobae Aug 20, 2026
5af1330
fix: keep the current event in truncated project histories
seonghobae Aug 20, 2026
5bc174d
test(red): define the authorized project-history HTTP contract
seonghobae Aug 20, 2026
ec94a8b
merge: restack onto feat/event-lineage-node-keeps-gnb-focus-v2170
seonghobae Aug 20, 2026
244388a
feat: expose a strict project-history API router
seonghobae Aug 20, 2026
f66e5c8
merge: restack onto feat/event-lineage-node-keeps-gnb-focus-v2170
seonghobae Aug 20, 2026
194c631
test(red): define the accessible project-history timeline
seonghobae Aug 20, 2026
5b4070c
feat: render accessible evidence-bound project histories
seonghobae Aug 20, 2026
78ed1e8
test(red): require semantic Global Ask production integration
seonghobae Aug 20, 2026
6a9cc8f
test(red): require opt-in Global Ask verification contract
seonghobae Aug 20, 2026
60c741e
test(red): require an explicit project-history disclosure
seonghobae Aug 20, 2026
6a2f66d
merge: restack RED Global Ask integration on current #264
seonghobae Aug 20, 2026
5e17442
test(red): require explicit Global Ask verification request
seonghobae Aug 20, 2026
f9024fc
test(red): require Buyer public-verification controls
seonghobae Aug 20, 2026
59ed63f
test(red): preserve multilingual semantic query terms
seonghobae Aug 20, 2026
72b0147
fix: preserve multilingual Global Ask query terms
seonghobae Aug 20, 2026
8e39e53
ci: apply the bounded Global Ask verification integration
seonghobae Aug 20, 2026
6eb0e34
test(red): preserve multilingual claim relevance
seonghobae Aug 20, 2026
12740a1
fix: preserve multilingual claim relevance
seonghobae Aug 20, 2026
890aae8
test(red): strip project evidence before public egress
seonghobae Aug 20, 2026
bb2f1dd
test(red): remove raw evidence from public claims
seonghobae Aug 20, 2026
2205cde
test: preserve multilingual Global Ask relevance
seonghobae Aug 20, 2026
0c406a4
ci: repair the Global Ask pnpm provisioning gate
seonghobae Aug 20, 2026
26dbc4a
ci: repair pinned pnpm provisioning directly
seonghobae Aug 20, 2026
8ea0f73
merge: restack onto feat/event-lineage-node-keeps-gnb-focus-v2170
seonghobae Aug 20, 2026
51ea771
chore: remove self-modifying project history repair
seonghobae Aug 20, 2026
d082118
merge: restack onto feat/event-lineage-node-keeps-gnb-focus-v2170
seonghobae Aug 20, 2026
2b28927
Merge remote-tracking branch 'origin/feat/tepp-project-history-buyer-…
seonghobae Aug 20, 2026
34bd1f8
ci: make pnpm repair indentation-independent
seonghobae Aug 20, 2026
cc2435e
Merge commit '2b289275757fc4629a7f59c86e316fd3fb84ea1c' of https://gi…
seonghobae Aug 20, 2026
ba3101b
Merge commit '86c5f88826b9554a642f0ab8a9f51288da19820d' of https://gi…
seonghobae Aug 20, 2026
669dfdf
fix: use canonical popup focus contract
seonghobae Aug 20, 2026
c283c02
merge: restack Global Ask verification onto current LineageWeave
seonghobae Aug 20, 2026
48923d1
fix: integrate bounded Global Ask public verification
seonghobae Aug 20, 2026
658e98b
merge: remove generated repair workflow from Global Ask stack
seonghobae Aug 20, 2026
e9f1185
merge: restack project history onto Global Ask verification
seonghobae Aug 20, 2026
9a23421
Merge remote-tracking branch 'origin/feat/gnb-event-lineage-focus-key…
seonghobae Aug 20, 2026
fa016aa
Merge remote-tracking branch 'origin/feat/global-ask-public-claim-ver…
seonghobae Aug 20, 2026
40f2bb9
Merge remote-tracking branch 'origin/feat/gnb-event-lineage-focus-key…
seonghobae Aug 20, 2026
1f44b3c
Merge remote-tracking branch 'origin/feat/gnb-event-lineage-focus-key…
seonghobae Aug 20, 2026
79061df
Merge current GNB lineage base into Global Ask verification
seonghobae Aug 20, 2026
da66fb2
merge: propagate current protected Keyman-focus base
seonghobae Aug 20, 2026
49161af
fix: keep verification ADR numbering unique
seonghobae Aug 20, 2026
680ed11
merge: propagate current Keyman-focus branch head
seonghobae Aug 20, 2026
635420d
merge: propagate current semantic-source Keyman base
seonghobae Aug 20, 2026
5baf7dc
fix(project-history): use explicit finite score predicate
seonghobae Aug 20, 2026
c94e01a
test(sql): require audited TEPP project-history statements
seonghobae Aug 20, 2026
55679fa
fix(sql): audit fixed-alias TEPP eligibility queries
seonghobae Aug 20, 2026
8d54ece
fix(project-history): expose bounded timeline contract
seonghobae Aug 20, 2026
7c7856a
test(orchestrator): require supported auto verification contract
seonghobae Aug 20, 2026
6775851
ci: repair public claim orchestrator contract
seonghobae Aug 20, 2026
37fac22
ci: expose public-claim repair to PR checks
seonghobae Aug 20, 2026
df828bc
test(retrieval): require indexable semantic search
seonghobae Aug 20, 2026
2e45f65
test(retrieval): reject concatenated semantic scans
seonghobae Aug 20, 2026
ff8802a
perf(retrieval): use indexable semantic predicates
seonghobae Aug 20, 2026
9f69bbe
perf(db): index Global Ask semantic fields
seonghobae Aug 20, 2026
27a8724
perf(db): add semantic index rollback
seonghobae Aug 20, 2026
94e8d48
build(db): apply semantic search migration
seonghobae Aug 20, 2026
79d752a
docs(doctoring): trace semantic search indexing
seonghobae Aug 20, 2026
6500c6d
docs(changelog): record indexable semantic retrieval
seonghobae Aug 20, 2026
bfd46ef
ci: verify semantic indexes with public-claim repair
seonghobae Aug 20, 2026
d9a2652
fix(orchestrator): use supported public-claim contract
seonghobae Aug 20, 2026
dccf234
docs(adr): align public verification with governed auto mode
seonghobae Aug 20, 2026
a7a267a
ci: remove completed public-claim repair workflow
seonghobae Aug 20, 2026
34bd2bb
ci: repair pinned pnpm provisioning
seonghobae Aug 20, 2026
39135fa
Merge remote-tracking branch 'origin/feat/gnb-event-lineage-focus-key…
seonghobae Aug 20, 2026
20e54e2
docs: record Global Ask integration CI recovery
seonghobae Aug 20, 2026
4100ab0
Merge remote-tracking branch 'origin/feat/global-ask-public-claim-ver…
seonghobae Aug 20, 2026
84b42bb
ci: pin Global Ask pnpm provisioning
github-actions[bot] Aug 20, 2026
b1f0e8d
fix: make project history protocol explicit
seonghobae Aug 20, 2026
c8ffd9c
fix: localize public verification guidance
seonghobae Aug 20, 2026
75c9d12
fix: parameterize global ask candidate filter
seonghobae Aug 21, 2026
04c5a2f
Merge remote-tracking branch 'origin/feat/gnb-event-lineage-focus-key…
seonghobae Aug 21, 2026
41ad3b7
style: normalize post-chat ingestion imports
seonghobae Aug 21, 2026
094d863
fix: align project history projection response contract
seonghobae Aug 21, 2026
3b3ce14
fix: align project history builder with API contract
seonghobae Aug 21, 2026
167bb56
merge remote #276 updates preserving project history contract
seonghobae Aug 21, 2026
4b38412
Merge remote-tracking branch 'origin/feat/gnb-event-lineage-focus-key…
seonghobae Aug 21, 2026
139cd24
merge concurrent #276 updates preserving contract fix
seonghobae Aug 21, 2026
af7c0d5
Merge remote-tracking branch 'origin/feat/gnb-event-lineage-focus-key…
seonghobae Aug 21, 2026
a3602fa
Merge remote-tracking branch 'origin/feat/gnb-event-lineage-focus-key…
seonghobae Aug 21, 2026
7d96fe6
fix: localize public verification controls
seonghobae Aug 21, 2026
39f67a8
feat: search verified multilingual organization labels (#316)
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
12 changes: 6 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,12 @@ Opening a Calendar commitment uses the same focus path (ADR 0094). Do not
invent a week, a theta, a cutoff body, or a CalDAV event.
Opening a Customer master related post uses the same focus path (ADR 0095).
Do not invent a week, a theta, a cutoff body, a CalDAV event, or a customer.
Opening an Ask Agent cited post uses the same focus path (ADR 0096).
Do not invent a cited post.
A linked Event Lineage node opened from that focused popup keeps the
originating flags (ADR 0097). That open then focuses Keyman as the named
next read (ADR 0100). Do not invent a week, a theta, a cutoff body,
a CalDAV event, a customer, or a cited post.
Opening an Ask Agent cited post uses the same focus path (ADR 0096). Do not
invent a cited post.
A linked Event Lineage node opened from that focused popup keeps the
originating flags (ADR 0097). Do not invent a cited post.
That open then focuses Keyman as the named next read (ADR 0100). Do not invent
a week, a theta, a cutoff body, a CalDAV event, a customer, or a cited post.


## Tests
Expand Down
11 changes: 5 additions & 6 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,14 +281,13 @@ HTML. `src/api.ts` calls the FastAPI backend directly with the token
Keycloak issued; `src/App.tsx` renders a git-branch SVG of
`GET /api/lineage` (click a node to open that post; `post_admin` can
rebuild), the post list with a named Weekly VOC ISO-8601 week filter
(ADR 0092; opening that filtered post focuses Event Lineage, ADR 0093).
Calendar commitments use the same Event Lineage focus path (ADR 0094).
(ADR 0092; opening that filtered post focuses Event Lineage, ADR 0093),
Calendar commitments use the same Event Lineage focus path (ADR 0094),
Customer master related posts use the same Event Lineage focus path
(ADR 0095). Ask Agent cited posts use the same Event Lineage focus path
(ADR 0096). A linked Event Lineage node opened from a focused popup
keeps those flags (ADR 0097) and then focuses Keyman as the named next
read (ADR 0100).
The full detail popup includes Korean
(ADR 0096). A linked Event Lineage node opened from a focused popup keeps
those flags (ADR 0097) and then focuses Keyman as the named next read
(ADR 0100). The full detail popup includes Korean
summary/key-events/R&R, VOC evidence excerpts, an Event Lineage panel
(direct vs. indirect links; a link opens that post), the Keyman
affiliate tree (resolved ancestors plus unresolved org roots), Keyman +
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.d/2.20.0-global-ask-public-verification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# 2.20.0 — Global Ask public semantic verification

- Global Ask may nominate source posts from persisted semantic, ontology, and Knowledge Graph evidence instead of requiring the buyer's term to appear in raw post text.
- Multilingual contains-search uses one indexable predicate per semantic field and migration 0054 adds matching `pg_trgm` GIN indexes; concatenated expression scans are regression-tested out.
- An explicit public-verification boundary uses SearXNG retrieval and contextual-orchestrator verification while keeping external URLs separate from internal post citations.
- Private source evidence, Keyman/person facts, TEPP measurement artifacts, and fast-mlsirm measurement data are ineligible for public-search egress.
- Public corroboration remains review evidence and never authority-promotes an inferred graph or ontology assertion.
7 changes: 7 additions & 0 deletions CHANGELOG.d/2.20.0-project-history-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# 2.20.0 Project-history response contract

Project-history storage projections now emit the same strict evidence-bound
shape that the HTTP response validates. Project identity display names retain
their authoritative code/key, event source IDs and time basis are explicit,
and the endpoint no longer fails with a validation error for an authorized
timeline.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ All notable changes to this project are documented here. Format follows
and evaluation as the next read. After an authorized answer, Ask Agent
names cited posts as current before that open. Home-list opens do not add
that focus or copy. No TEPP theta is invented. No cited post is invented
(ADR 0096 / ADR 0039 / ADR 0016).
(ADR 0096 / ADR 0039 / ADR 0016).

## [2.15.0] - 2026-08-19

Expand Down
240 changes: 240 additions & 0 deletions backend/app/global_ask_retrieval.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
"""Semantic and Knowledge-Graph candidate nomination for Global Ask.

Candidate nomination is deliberately non-authoritative. This module returns
post identifiers only. The caller must re-run the normal source-post visibility
predicate before exposing body text or evidence.
"""

from __future__ import annotations

import re
from typing import Any

import asyncpg

from lineageweave.claim_verification import ontology_lookup_codes_for_question

_STOP_WORDS = frozenset(
{
"which",
"what",
"where",
"when",
"who",
"why",
"how",
"the",
"this",
"that",
"posts",
"post",
"글",
"게시글",
"질문",
"관련",
"확인되는",
"핵심",
"사실",
"무엇",
"무엇인가요",
"인가요",
}
)
_TOKEN = re.compile(r"[^\W_]+(?:-[^\W_]+)*", re.UNICODE)
_EVIDENCE_POST_IDS = re.compile(r"\[evidence_post_id=([^]]+)\]")


def global_ask_query_terms(question: str | None, *, maximum_terms: int = 8) -> tuple[str, ...]:
"""Return bounded, de-duplicated lexical terms from a Global Ask query."""

if maximum_terms <= 0:
return ()
return tuple(
dict.fromkeys(
token.casefold()
for token in _TOKEN.findall(question or "")
if len(token) >= 2 and token.casefold() not in _STOP_WORDS
)
)[:maximum_terms]


async def semantic_candidate_post_ids(
conn: asyncpg.Connection,
question: str | None,
*,
maximum_candidates: int = 128,
) -> list[str]:
"""Nominate posts from persisted semantic and Knowledge-Graph evidence.

Project mentions, responsibility/affiliation evidence, Keyman names, and
organization/team catalogs use one ``ILIKE`` predicate per indexed column.
Search-corroborated raw/canonical organization-name pairs additionally
nominate direct organization mentions and affiliated people's posts; pending
or uncorroborated aliases do not. This preserves multilingual substring lookup
without wrapping indexed fields in an expression that forces a sequential
scan. Ontology lookup codes are applied only to graph lookup-code columns.
The function never returns source text and nomination never grants access.
"""

if maximum_candidates <= 0:
return []
terms = global_ask_query_terms(question)
ontology_codes = ontology_lookup_codes_for_question(question or "")
if not terms and not ontology_codes:
return []
rows = await conn.fetch(
"""
with query_terms as (
select unnest($1::text[]) as term
), verified_organization as (
select distinct entity.corporate_entity_id
from organization_name_resolution resolution
join corporate_entity entity
on entity.entity_name = resolution.resolved_organization_name
join query_terms term
on resolution.raw_organization_name ilike '%' || term.term || '%'
or resolution.resolved_organization_name ilike '%' || term.term || '%'
where resolution.verification_status_code = 'verify_corroborated'
), candidate_post as (
select mention.post_id, post.created_at
from post_project_mention mention
join source_post post on post.post_id = mention.post_id
where exists (
select 1 from query_terms term
where mention.project_name ilike '%' || term.term || '%'
or mention.evidence_text ilike '%' || term.term || '%'
or mention.ontology_iri ilike '%' || term.term || '%'
)
union all
select role.post_id, post.created_at
from post_summary_role role
join source_post post on post.post_id = role.post_id
where exists (
select 1 from query_terms term
where role.actor_name ilike '%' || term.term || '%'
or role.responsibility ilike '%' || term.term || '%'
or role.affiliated_organization_name ilike '%' || term.term || '%'
)
union all
select mention.post_id, post.created_at
from post_person_mention mention
join cataloged_person person on person.person_id = mention.person_id
join source_post post on post.post_id = mention.post_id
where exists (
select 1 from query_terms term
where person.person_name ilike '%' || term.term || '%'
or person.last_known_job_title ilike '%' || term.term || '%'
or mention.mention_context ilike '%' || term.term || '%'
)
union all
select mention.post_id, post.created_at
from post_organization_mention mention
join corporate_entity entity
on entity.corporate_entity_id = mention.corporate_entity_id
join source_post post on post.post_id = mention.post_id
where exists (
select 1 from query_terms term
where entity.entity_name ilike '%' || term.term || '%'
)
union all
select mention.post_id, post.created_at
from post_organization_mention mention
join verified_organization organization
on organization.corporate_entity_id = mention.corporate_entity_id
join source_post post on post.post_id = mention.post_id
union all
select mention.post_id, post.created_at
from post_person_mention mention
join person_affiliation affiliation
on affiliation.person_id = mention.person_id
join verified_organization organization
on organization.corporate_entity_id = affiliation.affiliated_corporate_entity_id
join source_post post on post.post_id = mention.post_id
union all
select mention.post_id, post.created_at
from post_team_mention mention
join cataloged_team team on team.team_id = mention.team_id
join source_post post on post.post_id = mention.post_id
where exists (
select 1 from query_terms term
where team.team_name ilike '%' || term.term || '%'
or team.affiliated_organization_name ilike '%' || term.term || '%'
)
union all
select evidence.evidence_post_id as post_id, post.created_at
from knowledge_graph_edge edge
join knowledge_graph_edge_evidence evidence
on evidence.knowledge_graph_edge_id = edge.knowledge_graph_edge_id
join source_post post on post.post_id = evidence.evidence_post_id
where edge.edge_type_code = any($2::text[])
or edge.source_node_type_code = any($2::text[])
or edge.target_node_type_code = any($2::text[])
)
select post_id::text as post_id
from candidate_post
group by post_id
order by max(created_at) desc, post_id desc
limit $3
""",
list(terms),
list(ontology_codes),
maximum_candidates,
)
return list(dict.fromkeys(str(row["post_id"]) for row in rows))


def graph_fact_evidence_post_ids(fact: str) -> frozenset[str]:
"""Extract all persisted evidence-post identifiers named by one graph fact."""

match = _EVIDENCE_POST_IDS.search(fact)
if match is None:
return frozenset()
return frozenset(
value.strip() for value in match.group(1).split(",") if value.strip()
)


def public_external_claim_facts(
row: Any,
semantic_facts: tuple[str, ...],
graph_facts: tuple[str, ...],
public_post_ids: frozenset[str],
) -> tuple[str, ...]:
"""Return externally searchable facts only for a public source post.

Source hints, people/Keyman facts, TEPP results, and fast-mlsirm reports are
absent by construction. A graph fact is eligible only if every persisted
evidence post named by that edge is public in the authorized result set.
"""

if row.get("visibility_code") != "public":
return ()
public_graph_facts = tuple(
fact
for fact in graph_facts
if (evidence_ids := graph_fact_evidence_post_ids(fact))
and evidence_ids.issubset(public_post_ids)
and "node_person" not in fact
)
public_semantic_facts = tuple(
_public_semantic_fact(fact)
for fact in semantic_facts
if fact.startswith("project:")
and "node_person" not in fact
and not fact.startswith(("actor:", "Keyman mention:"))
)
return tuple(dict.fromkeys(public_semantic_facts + public_graph_facts))


def _public_semantic_fact(fact: str) -> str:
"""Keep only the assertion, never the internal project evidence excerpt."""

return fact.split(" | evidence:", 1)[0].strip()


__all__ = [
"global_ask_query_terms",
"graph_fact_evidence_post_ids",
"public_external_claim_facts",
"semantic_candidate_post_ids",
]
Loading