Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
7cde1d2
fix(voice): distinguish carrying and evidence exports
Aug 28, 2026
11b0b82
fix(voice): preserve legacy CSV column position
Aug 28, 2026
5bc7cb3
Merge remote-tracking branch 'origin/main' into fix/voice-export-auth…
Aug 28, 2026
e519028
fix(voice): keep primary evidence non-derivative
Aug 28, 2026
150298e
docs(gaps): record residual-map ownership conflict
Aug 28, 2026
ddcc473
test(voice): assert empty primary derivation evidence
Aug 28, 2026
cfe7a42
docs(gaps): refresh exact-head queue conflict
Aug 28, 2026
6c37425
docs(gaps): refresh stacked plot evidence
Aug 28, 2026
ca4e5f4
docs(gaps): refresh exact-head stack audit
Aug 28, 2026
f5e915f
fix(voice): export assignment evidence authority
Aug 28, 2026
5766b48
docs(gaps): refresh exact-head delivery inventory
Aug 28, 2026
210b21f
docs(gaps): refresh protected queue snapshot
Aug 28, 2026
c73307a
docs(gaps): refresh exact-head queue evidence
Aug 29, 2026
4f96651
docs(gaps): bind residual map owner delivery
Aug 29, 2026
466bfc9
docs(gaps): record PRD authority conflict
Aug 29, 2026
27a3828
merge: synchronize voice export with protected main
Aug 30, 2026
56f2103
fix(voice): keep primary evidence out of derivation UI
Aug 30, 2026
280ba69
docs(gaps): refresh exact-head Voice delivery evidence
Aug 30, 2026
5fb95e4
docs(gaps): distinguish implementation evidence revision
Aug 30, 2026
43dfcca
fix(voice): prove authenticated export authority
Aug 30, 2026
8a1a404
docs(gaps): record authenticated Voice acceptance
Aug 30, 2026
2bf5354
docs(gaps): bind Voice implementation revision
Aug 30, 2026
d56e860
fix(voice): separate carrying post from derivation
Aug 30, 2026
70a533a
docs(gaps): record Voice SHACL root repair
Aug 30, 2026
4fa6330
fix(ontology): bind voice assignment carrying post
Aug 30, 2026
6496d54
docs(gaps): record voice inverse-link repair
Aug 30, 2026
047b730
fix(ontology): reject extra voice assignment owners
Aug 30, 2026
7688e07
docs(gaps): refresh exact-head Voice validation evidence
Aug 30, 2026
dd674eb
codex: address PR review feedback (#780)
Aug 30, 2026
a8f3bc2
docs(gaps): record authenticated Voice acceptance
Aug 30, 2026
a26af45
fix(runtime): restore post evidence status loader
Aug 30, 2026
d8bda6d
docs(gaps): record exact-head rendered acceptance
Aug 30, 2026
16883d9
docs(gaps): refresh exact-head Voice evidence
Aug 30, 2026
c1dd34b
docs(gaps): refresh exact-head Voice queue evidence
Aug 30, 2026
3bff4fe
docs(gaps): distinguish Voice implementation revision
Aug 30, 2026
7dc8f4b
test(voice): preserve multi-Voice JSON-LD relations
Aug 30, 2026
4b45377
fix(voice): keep carrying post visible on mobile
Aug 30, 2026
48392a8
docs(gaps): refresh Voice exact-head evidence
Aug 30, 2026
116593f
docs(gaps): refresh exact-head Voice delivery evidence
Aug 30, 2026
d7df80e
docs(gaps): bind owner workflow repair head
Aug 30, 2026
8a2fab9
docs(gaps): refresh exact-head Voice delivery evidence
Aug 31, 2026
3da03c8
docs(gaps): bind synchronized policy repair head
Aug 31, 2026
e71895c
docs(gaps): refresh Voice delivery evidence
Aug 31, 2026
4a85e8d
docs(gaps): refresh exact-head Voice delivery evidence
Aug 31, 2026
79dbedf
docs(gaps): bind current central policy head
Aug 31, 2026
8ad7f13
docs(gaps): refresh exact-head Voice evidence
Aug 31, 2026
0327602
docs(gaps): refresh exact-head Voice delivery evidence
Aug 31, 2026
781c004
docs(gaps): refresh exact-head Voice delivery evidence
Aug 31, 2026
72c9636
docs(gaps): distinguish evidence parent from PR head
Aug 31, 2026
81b425f
docs(gaps): refresh exact-head Voice evidence
Aug 31, 2026
29c2191
docs(gaps): refresh exact-head Voice loop evidence
Aug 31, 2026
c5490fc
docs(gaps): refresh protected PR loop evidence
Aug 31, 2026
8403a64
docs(gaps): bind overlay to evidence parent
Aug 31, 2026
734bbcc
docs: refresh exact-head delivery evidence
Aug 31, 2026
948d6e1
docs(gaps): refresh exact-head Voice delivery evidence
Aug 31, 2026
10ca7c1
docs(gaps): refresh exact-head Voice delivery evidence
Aug 31, 2026
c9db49d
docs(gaps): refresh exact-head Voice delivery evidence
Sep 1, 2026
44fb786
docs(gaps): refresh exact-head Voice delivery evidence
Sep 1, 2026
39ee1ee
docs(gaps): record current Voice delivery audit
Sep 1, 2026
1d07c96
docs(gaps): refresh exact-head Voice evidence
Sep 1, 2026
0c3044b
test(api): apply shipped migrations in live fixture
Sep 1, 2026
1c3b48f
chore(voice): restack export authority on live main
seonghobae Sep 1, 2026
9928fd6
docs(gaps): refresh exact-head Voice delivery evidence
Sep 1, 2026
df6aac9
docs(gaps): refresh exact-head Voice evidence
Sep 2, 2026
68c4285
Merge protected main queue fix into Voice export lane
seonghobae Sep 3, 2026
0bf2440
chore(voice): converge export lane with protected main
seonghobae Sep 3, 2026
1d8fa26
chore(voice): converge protected Draft-admission contract
seonghobae Sep 4, 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
1 change: 1 addition & 0 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
)
from backend.app.occupational_construct_ingestion import (
load_occupational_construct_assertions,
load_occupational_construct_evidence_status,
)
from backend.app.occupational_construct_search import (
OccupationalConstructSearchError,
Expand Down
3 changes: 1 addition & 2 deletions backend/app/ontology_neighborhood_ingestion.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
NODE_POST,
NODE_PROJECT,
NODE_TEAM,
EDGE_MENTION_PROJECT,
EDGE_SUPPORTS_OCCUPATIONAL_CONSTRUCT,
)
from lineageweave.ontology import iri_for_lookup_code
Expand Down Expand Up @@ -441,7 +440,7 @@ async def _load_facts(
and ($7::timestamptz is null or edge.created_at <= $7::timestamptz)
group by edge.source_node_type_code, edge.source_node_id,
edge.target_node_type_code, edge.target_node_id,
edge.edge_type_code
edge.edge_type_code, edge.created_at
union all
select 'node_post'::text as source_node_type_code,
mention.post_id::text as source_node_id,
Expand Down
104 changes: 104 additions & 0 deletions backend/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
_VALKEY_URL = os.environ.get("LINEAGEWEAVE_TEST_VALKEY_URL", "redis://localhost:16379/0")
_REALM = "lineageweave-demo"
_MIGRATION_PATH = Path(__file__).resolve().parents[2] / "migrations" / "0001_initial_schema.sql"
_PROV_O_MIGRATION = (
Path(__file__).resolve().parents[2] / "migrations" / "0017_prov_o_standard_relations.sql"
)
_REGISTRY_MIGRATION = Path(__file__).resolve().parents[2] / "migrations" / "0018_analysis_run_registry.sql"
_RETENTION_MIGRATION = Path(__file__).resolve().parents[2] / "migrations" / "0020_analysis_run_retention_purge.sql"
_RECONSTRUCTION_MIGRATION = (
Expand Down Expand Up @@ -187,6 +190,36 @@
/ "migrations"
/ "0233_report_leftover_map_unexplained_share.sql"
)
_VOICE_TAXONOMY_MIGRATION = (
Path(__file__).resolve().parents[2]
/ "migrations"
/ "0235_voice_of_x_post_taxonomy.sql"
)
_ONTOLOGY_TRUTH_STATUS_MIGRATION = (
Path(__file__).resolve().parents[2]
/ "migrations"
/ "0175_ontology_truth_status.sql"
)
_SOURCE_POST_VOICE_MIGRATION = (
Path(__file__).resolve().parents[2]
/ "migrations"
/ "0237_source_post_voice_combination.sql"
)
_SOURCE_POST_VOICE_HISTORY_MIGRATION = (
Path(__file__).resolve().parents[2]
/ "migrations"
/ "0243_source_post_voice_history.sql"
)
_OCCUPATIONAL_CONSTRUCT_ASSERTION_MIGRATION = (
Path(__file__).resolve().parents[2]
/ "migrations"
/ "0238_occupational_construct_assertion.sql"
)
_OCCUPATIONAL_CONSTRUCT_EXTRACTION_MIGRATION = (
Path(__file__).resolve().parents[2]
/ "migrations"
/ "0240_occupational_construct_extraction_run.sql"
)
_LEFTOVER_MAP_EXPLAINED_SHARE_MIGRATION = (
Path(__file__).resolve().parents[2]
/ "migrations"
Expand Down Expand Up @@ -217,6 +250,16 @@
/ "migrations"
/ "0218_global_ask_public_verification.sql"
)
_ANALYSIS_RUN_TEPP_RECEIPT_MIGRATION = (
Path(__file__).resolve().parents[2]
/ "migrations"
/ "0217_analysis_run_tepp_receipt.sql"
)
_SOURCE_CONVERSATION_TURN_EVIDENCE_MIGRATION = (
Path(__file__).resolve().parents[2]
/ "migrations"
/ "0233_source_conversation_turn_evidence.sql"
)
_GLOBAL_ASK_KNOWLEDGE_CUTOFF_MIGRATION = (
Path(__file__).resolve().parents[2]
/ "migrations"
Expand Down Expand Up @@ -332,6 +375,7 @@ def seeded_db(demo_analyst_token):
try:
with conn.cursor() as cur:
cur.execute(_MIGRATION_PATH.read_text())
cur.execute(_PROV_O_MIGRATION.read_text())
cur.execute(_REGISTRY_MIGRATION.read_text())
cur.execute(_RETENTION_MIGRATION.read_text())
cur.execute(_RECONSTRUCTION_MIGRATION.read_text())
Expand Down Expand Up @@ -415,6 +459,7 @@ def seeded_db(demo_analyst_token):
)
conn.autocommit = False
cur.execute(_GLOBAL_ASK_KNOWLEDGE_CUTOFF_MIGRATION.read_text())
cur.execute(_ANALYSIS_RUN_TEPP_RECEIPT_MIGRATION.read_text())
cur.execute(_GLOBAL_ASK_PUBLIC_VERIFICATION_MIGRATION.read_text())
cur.execute(_EVENT_OCCURRED_AT_MIGRATION.read_text())
cur.execute(_LEFTOVER_MAP_AXIS_MIGRATION.read_text())
Expand All @@ -423,6 +468,13 @@ def seeded_db(demo_analyst_token):
cur.execute(_LEFTOVER_MAP_CROSS_SHARE_MIGRATION.read_text())
cur.execute(_LEFTOVER_MAP_RECONSTRUCTION_MIGRATION.read_text())
cur.execute(_LEFTOVER_MAP_UNEXPLAINED_SHARE_MIGRATION.read_text())
cur.execute(_ONTOLOGY_TRUTH_STATUS_MIGRATION.read_text())
cur.execute(_VOICE_TAXONOMY_MIGRATION.read_text())
cur.execute(_SOURCE_POST_VOICE_MIGRATION.read_text())
cur.execute(_SOURCE_CONVERSATION_TURN_EVIDENCE_MIGRATION.read_text())
cur.execute(_OCCUPATIONAL_CONSTRUCT_ASSERTION_MIGRATION.read_text())
cur.execute(_OCCUPATIONAL_CONSTRUCT_EXTRACTION_MIGRATION.read_text())
cur.execute(_SOURCE_POST_VOICE_HISTORY_MIGRATION.read_text())
cur.execute(_LEFTOVER_MAP_EXPLAINED_SHARE_MIGRATION.read_text())
cur.execute(_LEFTOVER_MAP_COORDINATES_MIGRATION.read_text())
cur.execute(
Expand Down Expand Up @@ -1923,6 +1975,58 @@ def test_post_detail_uses_lookup_labels_not_raw_codes(client, demo_analyst_token
assert body["visibility_label"] == "Public"


def test_authenticated_ontology_neighborhood_reads_primary_voice_from_postgresql(
client, demo_analyst_token, seeded_db
) -> None:
"""The authenticated API preserves carrying evidence without inventing derivation."""
post_id = seeded_db["own_private_post_id"]
response = client.get(
"/api/ontology/neighborhood",
params={"focus_node_type": "node_post", "focus_node_id": post_id},
headers={"Authorization": f"Bearer {demo_analyst_token}"},
)

assert response.status_code == 200, response.text
payload = response.json()
primary = next(
assignment
for assignment in payload["voice_assignments"]
if assignment["is_primary"] and assignment["post_id"] == post_id
)
assert primary["post_id"] == post_id
assert primary["voice_type_code"] == "voc"
assert primary["truth_status_code"] == "truth_observed"
assert primary["evidence_post_id"] is None

row = next(
row
for row in payload["exact_value_rows"]
if row["property_code"] == "hasVoiceAssignment"
and row["source_node_id"] == post_id
)
assert row["source_node_id"] == post_id
assert row["evidence_post_id"] == post_id

assignment_iri = (
"https://contextualwisdomlab.github.io/LineageWeave/ontology#"
f"voice-assignment/{post_id}/voc"
)
projected = next(item for item in payload["jsonld"]["@graph"] if item.get("@id") == assignment_iri)
assert "prov:wasDerivedFrom" not in projected
assert (
"https://contextualwisdomlab.github.io/LineageWeave/ontology#voiceAssignmentEvidence"
not in projected
)
assert projected[
"https://contextualwisdomlab.github.io/LineageWeave/ontology#voiceAssignmentCarryingPost"
] == {
"@id": (
"https://contextualwisdomlab.github.io/LineageWeave/ontology#"
f"node/node_post/{post_id}"
)
}


def test_post_detail_exposes_explicit_and_semantic_project_evidence(
client, demo_analyst_token, seeded_db
) -> None:
Expand Down
4 changes: 2 additions & 2 deletions docs/adr/0252-temporal-primary-voice-history.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

## Status

Accepted (2026-08-27). Extends ADR 0251 and closes issue #748.
Accepted (2026-08-27). Extends ADR 0256 and closes issue #748.

## Context

ADR 0251 records when a Voice assignment starts, but migration 0237 deletes
ADR 0256 records when a Voice assignment starts, but migration 0237 deletes
the former imported primary when `source_post.voc_type_code` changes. The live
value is honest, yet an authorized knowledge-cutoff read after that update can
no longer recover the primary that was effective at the cutoff. The existing
Expand Down
9 changes: 6 additions & 3 deletions docs/adr/0256-evidence-bearing-voice-combinations.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ compound lookup codes.
keyword rule, confidence threshold, or weight converts those dimensions into
a voice.
- The public ontology represents each row as a qualified `VoiceAssignment`
linked from its post. Each assignment names one atomic SKOS voice concept;
additional assignments retain evidence through `prov:wasDerivedFrom`.
linked from its post. Each assignment names one atomic SKOS voice concept
and its carrying Post through `voiceAssignmentCarryingPost`, which does not
imply derivation. Additional assignments alone retain their distinct
evidence Post through `voiceAssignmentEvidence` and `prov:wasDerivedFrom`.
- Authorized post list/detail responses expose ordered voice assignments with
labels, truth state, and evidence availability but never internal assertion
identifiers. Filters match any associated voice, and repeated post cards show
Expand All @@ -72,7 +74,8 @@ compound lookup codes.
accounts without `post_admin` do not expose this write control.
- The authorized ontology neighborhood projects each association as a
qualified assignment in JSON-LD and the exact-value CSV. SHACL requires its
atomic voice concept, primary flag, and source-post evidence. The exact-value
atomic voice concept, primary flag, and carrying Post. Additional assignments
also require derivation evidence. The exact-value
table opens the carrying Post and, separately, the already-authorized
derivation-evidence Post. It does not invent a graph edge or expose an
internal assertion identifier. A single bounded query loads
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ decision from them.
| [`product-requirements.md`](../product-requirements.md) | Product requirements projection across the ADR set; ADRs remain normative, including [0252](0252-temporal-primary-voice-history.md) |
| [`product-technical-gap-baseline.md`](../product-technical-gap-baseline.md) | Product/technical traceability projection across the ADR set; ADRs remain normative |
| [`lineage-bi-research-notes.md`](../lineage-bi-research-notes.md) | [0084](0084-lineage-research-grounding.md), [0062](0062-semantic-unit-embedding.md), [0064](0064-lineage-evidence-and-tree-assembly.md), [0024](0024-rankweave-fusion-fail-closed.md), [0165](0165-quantity-script-display.md), [0167](0167-rankweave-ranking-channel-evidence.md), [0169](0169-ask-batched-lineage-graph.md), [0172](0172-event-lineage-channel-evidence.md), [0202](0202-ask-event-time-filter.md), [0223](0223-explicit-semantic-content-unit-kinds.md), [0238](0238-source-conversation-turn-import-contract.md) |
| [`voice-combination-technical-requirements.md`](../voice-combination-technical-requirements.md) | [0246](0246-expanded-voice-of-x-post-taxonomy.md), [0251](0256-evidence-bearing-voice-combinations.md), [0252](0252-temporal-primary-voice-history.md) |
| [`voice-combination-technical-requirements.md`](../voice-combination-technical-requirements.md) | [0246](0246-expanded-voice-of-x-post-taxonomy.md), [0256](0256-evidence-bearing-voice-combinations.md), [0252](0252-temporal-primary-voice-history.md) |
| [`lineage-bi-research-notes.md`](../lineage-bi-research-notes.md) | [0084](0084-lineage-research-grounding.md), [0062](0062-semantic-unit-embedding.md), [0064](0064-lineage-evidence-and-tree-assembly.md), [0024](0024-rankweave-fusion-fail-closed.md), [0165](0165-quantity-script-display.md), [0167](0167-rankweave-ranking-channel-evidence.md), [0169](0169-ask-batched-lineage-graph.md), [0172](0172-event-lineage-channel-evidence.md), [0202](0202-ask-event-time-filter.md), [0223](0223-explicit-semantic-content-unit-kinds.md) |
| [`PROV_O_IMPLEMENTATION.md`](../PROV_O_IMPLEMENTATION.md) | [0065](0065-prov-o-provenance-boundary.md) |
| [`PROV_O_IMPLEMENTATION_MATRIX.md`](../PROV_O_IMPLEMENTATION_MATRIX.md) | [0065](0065-prov-o-provenance-boundary.md) |
Expand Down
42 changes: 39 additions & 3 deletions docs/ontology/lineageweave-kg-shapes.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,48 @@
sh:datatype xsd:boolean ;
] ;
sh:property [
sh:path :voiceAssignmentEvidence ;
sh:name "voice assignment evidence" ;
sh:description "Every assignment retains exactly one authorized supporting source post." ;
sh:path :voiceAssignmentCarryingPost ;
sh:name "voice assignment carrying post" ;
sh:description "Every assignment names exactly one authorized Post that carries it without implying derivation." ;
sh:minCount 1 ;
sh:maxCount 1 ;
sh:class :Post ;
] ;
Comment thread
seonghobae marked this conversation as resolved.
sh:property [
sh:path :voiceAssignmentEvidence ;
sh:name "voice assignment derivation evidence" ;
sh:description "An additional assignment retains exactly one authorized evidence Post; imported primary assignments have none." ;
sh:maxCount 1 ;
sh:class :Post ;
] ;
sh:sparql [
a sh:SPARQLConstraint ;
sh:message "Additional Voice assignments require derivation evidence; primary assignments must not claim it." ;
sh:select """
SELECT $this WHERE {
$this :primaryVoiceAssignment ?primary .
OPTIONAL { $this :voiceAssignmentEvidence ?evidence . }
FILTER ((?primary = false && !BOUND(?evidence)) ||
(?primary = true && BOUND(?evidence)))
}
""" ;
] ;
sh:sparql [
a sh:SPARQLConstraint ;
sh:message "The carrying Post must link to this Voice assignment." ;
sh:select """
SELECT DISTINCT $this WHERE {
{
$this :voiceAssignmentCarryingPost ?post .
FILTER NOT EXISTS { ?post :hasVoiceAssignment $this . }
}
UNION
{
?post :hasVoiceAssignment $this .
FILTER NOT EXISTS { $this :voiceAssignmentCarryingPost ?post . }
}
Comment thread
seonghobae marked this conversation as resolved.
}
""" ;
] .

:OccupationalConstructAssertionShape a sh:NodeShape ;
Expand Down
8 changes: 7 additions & 1 deletion docs/ontology/lineageweave-kg.ttl
Original file line number Diff line number Diff line change
Expand Up @@ -386,12 +386,18 @@
rdfs:range xsd:boolean ;
rdfs:label "primary voice assignment"@en .

:voiceAssignmentCarryingPost a owl:ObjectProperty ;
rdfs:domain :VoiceAssignment ;
rdfs:range :Post ;
rdfs:label "voice assignment carrying post"@en ;
rdfs:comment "The authorized post that carries this qualified Voice assignment; this relation does not assert derivation."@en .

:voiceAssignmentEvidence a owl:ObjectProperty ;
rdfs:subPropertyOf prov:wasDerivedFrom ;
rdfs:domain :VoiceAssignment ;
rdfs:range :Post ;
rdfs:label "voice assignment evidence"@en ;
rdfs:comment "The authorized source post that supports this qualified voice assignment."@en .
rdfs:comment "The authorized evidence post from which an additional qualified voice assignment was derived."@en .

#################################################################
# SKOS -- corporate_entity_level (Group -> Company -> Plant)
Expand Down
Loading
Loading