Repository navigation
feat: disclose verified organization labels on Global Ask (v2.21.0) #318
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 2 commits
63ebb11
b50e51f
79b2bd3
f025eb4
87611ff
a64bdf0
8e67050
42d24fd
8ad157b
3ae5bd0
2329b69
69b6052
04e1664
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,9 @@ | ||
| # 2.21.0 — Disclose corroborated organization labels on Global Ask | ||
|
|
||
| - Global Ask now shows search-corroborated raw→canonical organization | ||
| labels on cited-post evidence (for example `DC → Demo Corp`) after | ||
| ABAC-visible posts are selected. | ||
| - Pending and uncorroborated aliases stay excluded from nomination and | ||
| disclosure. | ||
| - When a corroborated label matched, Ask names opening a cited post to | ||
| read Event Lineage as the next action (ADR 0107 / ADR 0008). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,102 @@ | |
| _TOKEN = re.compile(r"[^\W_]+(?:-[^\W_]+)*", re.UNICODE) | ||
| _EVIDENCE_POST_IDS = re.compile(r"\[evidence_post_id=([^]]+)\]") | ||
|
|
||
| VERIFIED_ORGANIZATION_LABEL_PREFIX = "verified organization label:" | ||
| VERIFIED_ORGANIZATION_LABEL_NEXT_ACTION = ( | ||
| "Corroborated organization labels are current. Open a cited post to read Event Lineage." | ||
| ) | ||
|
|
||
|
|
||
| def verified_organization_label_fact( | ||
| raw_organization_name: str, | ||
| resolved_organization_name: str, | ||
| ) -> str: | ||
| """Return one buyer-visible SKOS altLabel → prefLabel pair.""" | ||
|
|
||
| return ( | ||
| f"{VERIFIED_ORGANIZATION_LABEL_PREFIX} {raw_organization_name} → " | ||
| f"{resolved_organization_name}" | ||
| ) | ||
|
|
||
|
|
||
| async def verified_organization_label_facts( | ||
| conn: asyncpg.Connection, | ||
| question: str | None, | ||
| post_ids: list[str], | ||
| *, | ||
| maximum_terms: int = 8, | ||
| ) -> dict[str, tuple[str, ...]]: | ||
| """Disclose corroborated raw→canonical labels for already-visible posts. | ||
|
|
||
| Pending and uncorroborated aliases never appear. Nomination remains | ||
| identifier-only; this query runs after ABAC-visible post ids are known. | ||
| """ | ||
|
|
||
| if not post_ids: | ||
| return {} | ||
| terms = global_ask_query_terms(question, maximum_terms=maximum_terms) | ||
| if not terms: | ||
| return {} | ||
| rows = await conn.fetch( | ||
| """ | ||
| with query_terms as ( | ||
| select unnest($1::text[]) as term | ||
| ), nominated_post as ( | ||
| select unnest($2::uuid[]) as post_id | ||
| ), verified_organization as ( | ||
| select distinct | ||
| entity.corporate_entity_id, | ||
| resolution.raw_organization_name, | ||
| resolution.resolved_organization_name | ||
| 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' | ||
| ), matched_organization_label as ( | ||
| select distinct | ||
| mention.post_id, | ||
| organization.raw_organization_name, | ||
| organization.resolved_organization_name | ||
| from post_organization_mention mention | ||
| join verified_organization organization | ||
| on organization.corporate_entity_id = mention.corporate_entity_id | ||
| join nominated_post nominated | ||
| on nominated.post_id = mention.post_id | ||
| union | ||
| select distinct | ||
| mention.post_id, | ||
| organization.raw_organization_name, | ||
| organization.resolved_organization_name | ||
| 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 nominated_post nominated | ||
| on nominated.post_id = mention.post_id | ||
| ) | ||
| select post_id::text as post_id, | ||
| raw_organization_name, | ||
| resolved_organization_name | ||
| from matched_organization_label | ||
| order by post_id, raw_organization_name, resolved_organization_name | ||
| """, | ||
| list(terms), | ||
| list(post_ids), | ||
| ) | ||
| facts: dict[str, list[str]] = {} | ||
| for row in rows: | ||
| facts.setdefault(str(row["post_id"]), []).append( | ||
| verified_organization_label_fact( | ||
| row["raw_organization_name"], | ||
| row["resolved_organization_name"], | ||
| ) | ||
| ) | ||
| return {post_id: tuple(dict.fromkeys(values)) for post_id, values in facts.items()} | ||
|
Comment on lines
+64
to
+140
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: Disclosure query recomputes terms independently of nomination
Was this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
||
|
|
||
| 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.""" | ||
|
|
@@ -233,8 +329,12 @@ def _public_semantic_fact(fact: str) -> str: | |
|
|
||
|
|
||
| __all__ = [ | ||
| "VERIFIED_ORGANIZATION_LABEL_NEXT_ACTION", | ||
| "VERIFIED_ORGANIZATION_LABEL_PREFIX", | ||
| "global_ask_query_terms", | ||
| "graph_fact_evidence_post_ids", | ||
| "public_external_claim_facts", | ||
| "semantic_candidate_post_ids", | ||
| "verified_organization_label_fact", | ||
| "verified_organization_label_facts", | ||
| ] | ||
|
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: next_action override coexists with the verify_external skip status
(Refers to this code) Was this helpful? React with 👍 or 👎 to provide feedback. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,6 +47,7 @@ | |
| global_ask_query_terms, | ||
| public_external_claim_facts, | ||
| semantic_candidate_post_ids, | ||
| verified_organization_label_facts, | ||
| ) | ||
| from lineageweave.claim_verification import GlobalAskSourceDocument | ||
| from lineageweave.ontology import ontology_annotations | ||
|
|
@@ -706,6 +707,7 @@ async def gather_global_chat_sources( | |
| visible_ids = [str(row["post_id"]) for row in visible_rows] | ||
| anchor_is_visible = lineage_anchor_id in visible_ids | ||
| semantic_facts = await _semantic_facts_for_posts(conn, visible_ids) | ||
| label_facts = await verified_organization_label_facts(conn, question, visible_ids) | ||
| graph_facts = (await _graph_facts_for_posts(conn, visible_ids))[:16] | ||
| public_post_ids = frozenset( | ||
| str(row["post_id"]) | ||
|
|
@@ -740,7 +742,9 @@ async def gather_global_chat_sources( | |
| graph_facts=graph_facts if index == 0 else (), | ||
| evidence_facts=_source_hint_facts(row) | ||
| + semantic_facts.get(post_id, ()) | ||
| + label_facts.get(post_id, ()) | ||
|
Comment on lines
743
to
+746
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: Label facts kept out of external claims but fed to the LLM prompt
Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| + lineage_fact, | ||
|
|
||
| occurred_at=_timestamp_text(row), | ||
| timeline_kind=( | ||
| "lineage_neighbor" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| # ADR 0107 — Disclose corroborated organization labels on Global Ask | ||
|
|
||
| - Status: Accepted | ||
| - Date: 2026-08-20 | ||
| - Owners: LineageWeave Buyer surface / Organization identity | ||
| - Depends on: [0008](0008-organization-abbreviation-resolution.md), [0106](0106-global-ask-public-claim-verification.md) | ||
|
|
||
| ## Context | ||
|
|
||
| Global Ask may nominate authorized posts from search-corroborated | ||
| raw/canonical organization-name pairs (ADR 0008). Nomination returns | ||
| post identifiers only. Without a buyer-visible evidence fact, a query | ||
| for a synthetic alias such as `DC` can surface a Demo Corp post while | ||
| leaving the matched SKOS altLabel → prefLabel pair hidden. The buyer | ||
| then cannot tell why that post was nominated or what to open next. | ||
|
|
||
| Pending and uncorroborated aliases must stay invisible. A guessed | ||
| translation or an unverified LLM expansion is not evidence. | ||
|
|
||
| ## Decision | ||
|
|
||
| After the ordinary source-visibility/ABAC predicate has selected | ||
| visible posts, Global Ask SHALL load corroborated | ||
| `organization_name_resolution` rows whose raw or resolved label matches | ||
| the bounded query terms and whose canonical name joins a | ||
| `corporate_entity` already mentioned on, or affiliated to a person on, | ||
| those visible posts. | ||
|
|
||
| Each match is disclosed as cited-post evidence of kind | ||
| `verified_organization_label` with the raw label and canonical label | ||
| kept separate (`DC → Demo Corp`). The fact is internal SKOS evidence. It | ||
| is not a public-search claim, is not placed in `external_claim_facts`, | ||
| and does not authority-promote a Knowledge Graph edge. | ||
|
|
||
| Pending (`verify_pending`) and uncorroborated (`verify_uncorroborated`) | ||
| rows remain excluded from both nomination and disclosure. The query | ||
| uses one indexable `ILIKE` predicate per label column. | ||
|
|
||
| When at least one cited post carries a verified organization label, the | ||
| response next action SHALL name opening that cited post to read Event | ||
| Lineage. Public-verification next actions remain for answers that have | ||
| no such label. | ||
|
|
||
| ## Buyer next action | ||
|
|
||
| - corroborated label match → open a cited post to read Event Lineage | ||
| - no corroborated label match → keep the ADR 0106 public-verification | ||
| next action | ||
|
|
||
| ## Consequences | ||
|
|
||
| - The buyer can inspect the exact raw→canonical pair that nominated the | ||
| post without inventing a translation at query time. | ||
| - Disclosure cannot precede ABAC: only already-visible post identifiers | ||
| are joined. | ||
| - Synthetic fixtures only; Demo Corp / DC and Aurora Grid Power / AGP | ||
| are the documented examples. | ||
|
|
||
| ## References | ||
|
|
||
| De Cao, N., Wu, L., Popat, K., Artetxe, M., Goyal, N., Plekhanov, M., | ||
| Zettlemoyer, L., & Riedel, S. (2022). Multilingual autoregressive | ||
| entity linking. *Transactions of the Association for Computational | ||
| Linguistics, 10*, 274–290. https://doi.org/10.1162/tacl_a_00460 | ||
|
|
||
| Miles, A., & Bechhofer, S. (Eds.). (2009). *SKOS simple knowledge | ||
| organization system reference*. World Wide Web Consortium. | ||
| https://www.w3.org/TR/skos-reference/ |
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: Label disclosure is context-agnostic once a resolution is entity-linked
link_verified_organization_entity(organization_name_resolution_ingestion.py) links a resolution keyed by raw-name + context digest to a stable entity id, butverified_organization_label_factsthen discloses that alias for ANY ABAC-visible post mentioning the linkedcorporate_entity(or affiliating a mentioned person to it) whose raw/resolved label matches a query term — it does not re-check the post's own context digest. This is intentional per ADR 0107/0122 (disclosure joins by catalog identity, not per-post context), so a homonym-safe entity id gates it; noting it because it means a corroboration established in one post's context surfaces the alias across other posts about the same entity.Was this helpful? React with 👍 or 👎 to provide feedback.