diff --git a/backend/app/main.py b/backend/app/main.py index 122165990..5e9391a9a 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -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, diff --git a/backend/app/ontology_neighborhood_ingestion.py b/backend/app/ontology_neighborhood_ingestion.py index 51f56ff2c..080928ffc 100644 --- a/backend/app/ontology_neighborhood_ingestion.py +++ b/backend/app/ontology_neighborhood_ingestion.py @@ -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 @@ -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, diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index 520277031..db41c4456 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -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 = ( @@ -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" @@ -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" @@ -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()) @@ -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()) @@ -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( @@ -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: diff --git a/docs/adr/0252-temporal-primary-voice-history.md b/docs/adr/0252-temporal-primary-voice-history.md index 2cf3f5685..d0cbbc607 100644 --- a/docs/adr/0252-temporal-primary-voice-history.md +++ b/docs/adr/0252-temporal-primary-voice-history.md @@ -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 diff --git a/docs/adr/0256-evidence-bearing-voice-combinations.md b/docs/adr/0256-evidence-bearing-voice-combinations.md index 79279130d..54f9cc826 100644 --- a/docs/adr/0256-evidence-bearing-voice-combinations.md +++ b/docs/adr/0256-evidence-bearing-voice-combinations.md @@ -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 @@ -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 diff --git a/docs/adr/README.md b/docs/adr/README.md index 8979111c1..f914bbfe1 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -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) | diff --git a/docs/ontology/lineageweave-kg-shapes.ttl b/docs/ontology/lineageweave-kg-shapes.ttl index 2a5b6fbab..1485dbb0a 100644 --- a/docs/ontology/lineageweave-kg-shapes.ttl +++ b/docs/ontology/lineageweave-kg-shapes.ttl @@ -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 ; + ] ; + 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 . } + } + } + """ ; ] . :OccupationalConstructAssertionShape a sh:NodeShape ; diff --git a/docs/ontology/lineageweave-kg.ttl b/docs/ontology/lineageweave-kg.ttl index 067462572..dab5d789e 100644 --- a/docs/ontology/lineageweave-kg.ttl +++ b/docs/ontology/lineageweave-kg.ttl @@ -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) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index b5d31877b..239e03da1 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,5 +1,787 @@ # Product & Technical Gap Baseline +> Exact-head development-loop snapshot: 2026-09-02 KST. Protected `main` is +> `3f61c8242b9c02dec307a7396e83e28f7cdd9f3d`; the fresh inventory contains +> 107 open PRs and 15 open non-PR issues. PR #780's remotely observed evidence +> parent is `9928fd65e75d8d5e53d9a25f33df8d05acddf4c1`, based directly on that +> `main`, non-draft, mechanically mergeable, and policy-blocked. The active +> organization ruleset still requires one independent approval, resolved +> review threads, and the central required workflows. No qualifying +> independent approval exists. Exact-parent product and frontend checks pass; +> dependency review and Noema fail closed in their central workflows, while +> Strix is cancelled. Those hosted states are not converted into product +> acceptance, and no protected merge or merge SHA is claimed. +> +> Fresh local verification on that evidence parent passes 53 Voice-authority, +> ontology-neighborhood, and SHACL tests plus all 534 frontend tests, lint, +> type checking, and the production build. The synthetic desktop and mobile +> Storybook screenshots were visually audited: the exact-value view keeps the +> carrying Post in Source and shows an evidence action only for genuine +> derivation evidence; the narrow view retains the horizontally scrollable +> exact-value table. Paged JSON-LD regressions continue to union properties and +> multi-Voice relations for one subject. ADR 0246's twelve atomic Voices stay +> extensible assignments rather than fixed combination codes, and every +> additional Voice retains its authorized evidence Post, PROV-O derivation, +> truth status, and cutoff. Authenticated deployed PostgreSQL/API evidence was +> not rerun in this snapshot, so that acceptance remains unavailable rather +> than inherited from an older revision. +> +> Exact-head development-loop snapshot: 2026-09-01 21:36 KST. Protected +> `main` is `3f61c8242b9c02dec307a7396e83e28f7cdd9f3d`; 98 PRs and 10 +> non-PR issues were open in the fresh inventory collected before GitHub's API +> rate limit closed the review-thread query. PR #780 is based directly on that +> `main` at exact head `1c3b48fa465bb602a5fdacec57d32ef23c5997a9`, is non-draft, +> and has normal squash auto-merge armed. Its review decision remains +> `REVIEW_REQUIRED`; review-thread state is unavailable in this snapshot and is +> not inferred from older evidence. Fourteen exact-head checks are queued, the +> Strix job is cancelled, and the remaining reported jobs are successful or +> skipped. Pending work is not acceptance evidence and does not block safe +> review of another PR. No protected merge or merge SHA is claimed. +> +> Fresh local verification on this exact product head passes 184 focused +> Voice-authority, ontology, SHACL, and API tests with five live-stack skips, +> plus all 534 frontend tests, lint, type checking, and the production build. +> The candidate remains the largest buyer-visible gap implementation: exact- +> value UI and CSV distinguish the carrying Post from derivation evidence; +> authorized additional Voices retain their evidence Post, truth status, +> cutoff, and server-created PROV-O derivation; hidden evidence is omitted +> without substituting the carrying Post; and paged JSON-LD unions properties +> and multi-Voice relations for the same subject. ADR 0246's twelve atomic +> Voice codes remain extensible assignments rather than enumerated +> combinations. Authenticated deployed PostgreSQL/API and rendered-UI +> acceptance remain unavailable at this revision. + +> Exact-head development-loop snapshot: 2026-09-01 13:23 KST. Protected +> `main` remains `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; 98 PRs and 10 +> non-PR issues are open. PR #780's exact evidence parent is +> `39ee1ee39b3667cc1adc8337f5c8a183f22aaeb1`, based directly on that +> `main`. Normal squash auto-merge remains armed. Fresh GraphQL evidence shows +> all 24 review threads resolved, but no independent `APPROVED` review exists. +> Two exact-parent checks succeed, seven are skipped, and thirteen are queued; +> queued work is not acceptance evidence and did not block safe local review. +> The active ruleset still requires an independent approval, resolved threads, +> central required workflows, and non-fast-forward protection. No protected +> merge, merge SHA, or deployed acceptance is claimed. +> +> Fresh focused verification passes 54 Voice-authority, ontology, SHACL, and +> authenticated synthetic PostgreSQL/API tests. The wider selected API file +> also exposed three schema-drift failures outside this Voice slice: its fresh +> test databases lack `analysis_run_tepp_receipt` and +> `semantic_unit.source_evidence_reference`. Those failures remain explicit +> unavailable integration evidence and are not hidden by narrowing the accepted +> regression claim. Direct inspection of the committed desktop and mobile +> renders confirms separate carrying-Post and derivation-evidence actions; the +> narrow table preserves them through horizontal scrolling. The candidate keeps +> ADR 0246's twelve atomic Voices extensible, preserves authorized additional- +> Voice evidence, server-created PROV-O derivation, truth status, and cutoff, +> omits hidden evidence instead of substituting the carrying Post, and unions +> same-subject multi-Voice relations in paged JSON-LD. Voice composition remains +> governed by ADR 0256, not this repository's unrelated ADR 0251. Protected-main +> and authenticated deployed UI acceptance remain unavailable. + +> Exact-head development-loop snapshot: 2026-09-01 12:08 KST. Protected +> `main` is `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; 98 PRs and 10 +> non-PR issues are open. PR #780's exact evidence parent is +> `44fb7863eb60cfd4c2ae4b85674346f6cc9be5b6`, based directly on that +> `main`. Normal squash auto-merge is armed. No independent `APPROVED` review +> exists, and the active ruleset requires one approval, fresh review after a +> push, resolved threads, central required workflows, and non-fast-forward +> protection. Seven exact-parent checks are skipped and thirteen are queued; +> queued work is not acceptance evidence and did not block safe local review. +> GitHub GraphQL review-thread evidence is rate-limited in this snapshot, so +> thread resolution is explicitly unverified rather than inferred from REST +> comments. No protected merge, merge SHA, or deployed acceptance is claimed. +> +> The largest buyer-visible gap remains implemented only on PR #780: the +> exact-value UI and CSV distinguish the carrying Post from genuine derivation +> evidence; additional Voices retain authorized Post evidence, server-created +> PROV-O derivation, truth status, and cutoff; hidden evidence is omitted rather +> than replaced; and paged JSON-LD unions same-subject multi-Voice properties. +> The twelve ADR 0246 atomic classifications remain extensible assignments, +> never enumerated combinations. In this repository ADR 0251 governs the I/O +> psychology semantic layer; Voice composition is governed by ADR 0256 and +> temporal imported-primary history by ADR 0252. Issue #807 keeps the duplicated +> occupational PRD/ADR identities explicitly unresolved. Package metadata is +> 2.28.0 while `lineageweave.__version__` remains 2.20.0; the v2.29.0 analysis +> candidate in PR #897 must not be promoted ahead of an owner-approved release +> identity reconciliation. These conflicts remain unavailable, not repaired by +> a local guess. + +> Exact-head development-loop overlay: 2026-09-01 11:03 KST. Protected +> `main` remains `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; ninety-eight PRs +> and ten non-PR issues are open. PR #780's exact product and rendered-evidence +> parent for this documentation update is +> `10ca7c1a02d58bbb12436889d4256fedfb2da7dd`, based directly on that +> `main`; the current exact head is +> `c9db49da74e71b94d721ee40a6ee7becec676aa1` and differs from that parent +> only in this baseline. Normal squash auto-merge remains armed and no +> independent `APPROVED` review exists. GitHub's review-thread GraphQL query is +> rate-limited in this snapshot, so the earlier resolved-thread evidence is not +> promoted to a fresh claim; the REST review and inline-comment feeds expose no +> newer actionable finding. Thirteen exact-head checks are queued and seven are +> skipped. No protected merge or merge SHA is claimed. +> +> The owner-bound documentation-image workflow repair advanced without a force +> push in `ContextualWisdomLab/.github#1466` to exact head +> `3efa7ded68826301a6fbf56237c436740b0b4c1b` on central `main`. Its latest +> published review is `COMMENTED`, no independent approval or auto-merge is +> present, fourteen checks are queued, fourteen are skipped, and two are +> cancelled. Until that parent reaches protected central `main`, PR #780's PNG +> bootstrap failure remains unavailable workflow evidence. The separate +> governed review timeout and GitHub dependency-comparison HTTP 403 also remain +> unavailable rather than product acceptance. One queued Strix run for PR #640 +> was cancelled only after the PR's live head was verified as +> `5c430d4d91b22bd99a6d74b288c9356a62a50a5d` and the run was verified against +> stale head `f40e4ed8020a7db4099730d558da942a0f331614`; current `main` and open-PR +> exact-head runs were left intact. +> +> The largest buyer-visible candidate remains the Voice exact-value/export +> repair: carrying Post and derivation evidence stay distinct in UI and CSV; +> additional Voices retain authorized Post evidence, server-created PROV-O +> derivation, truth status, and cutoff; hidden evidence is not replaced; and +> paged JSON-LD unions same-subject multi-Voice relations. ADR 0246's twelve +> atomic classifications remain extensible rows rather than enumerated +> combinations. Fresh local verification passes 54 focused Voice, ontology, +> SHACL, and API selections plus 29 affected frontend tests, frontend lint, and +> the production build. Direct inspection of the committed desktop and mobile +> renders keeps the carrying-Post action separate from the additional Voice's +> derivation-evidence action, including the narrow table's horizontal scroll. +> The earlier authenticated synthetic PostgreSQL/API evidence is +> revision-scoped; deployed acceptance remains unavailable. + +> Exact-head development-loop overlay: 2026-09-01 07:31 KST. Protected +> `main` remains `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; ninety-seven PRs +> and ten non-PR issues are open. PR #780's exact implementation and rendered- +> evidence parent for this documentation-only update is +> `948d6e1524d4f203296f5c409dc60f5bd289d9ca`, based directly on that +> `main`. All 24 review threads are resolved and normal squash auto-merge is +> armed, but no independent `APPROVED` review exists. Sixteen current-parent +> checks succeed, ten are skipped, and three fail. The failures remain explicit +> unavailable evidence: the central Pingora policy decodes a committed PNG as +> UTF-8, the governed Noema request times out, and GitHub denies the exact +> dependency comparison with HTTP 403. None is relabeled as product acceptance. +> +> Fresh local verification passes 37 focused Voice/ontology/SHACL/API tests and +> all 534 frontend tests. Direct desktop/mobile render inspection confirms that +> the carrying-Post action remains distinct from derivation evidence in the +> exact-value surface, including the horizontally scrolled narrow table. The +> candidate preserves ADR 0246's twelve extensible atomic Voice rows and ADR +> 0256's evidence-bearing composition: additional Voices retain authorized Post +> evidence, server-created PROV-O derivation, truth status, and cutoff; hidden +> evidence is never replaced with the carrying Post; and paged JSON-LD unions +> same-subject multi-Voice relations. Earlier authenticated PostgreSQL/API +> evidence remains revision-scoped. Protected-main delivery, merge SHA, and +> deployed acceptance remain unavailable. +> +> The owner-bound PNG repair remains candidate-only in +> `ContextualWisdomLab/.github#1466` at +> `c17841917764960208e89b204c50e527d644db70`; its current OpenCode verdict is +> changes-requested from failed or cancelled peer checks, Noema/OpenCode reruns +> are queued, and no independent approval exists. Canonical remote names remain +> `ContextualWisdomLab/LineageWeave`, `RankWeave`, `ThreadWeave`, lowercase +> `disksage`, and `TEPP`. No self-approval, admin bypass, force push, invented +> weight, heuristic classification, or hidden-evidence substitution was used. + +> Exact-head development-loop overlay: 2026-09-01 05:14 KST. Protected +> `main` is `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; ninety-eight PRs and +> ten non-PR issues are open. PR #780's exact product and rendered-evidence +> parent for this documentation update is +> `734bbcc312041ec50b117112caa2fda71320190d`, based directly on that +> `main`. All review threads are resolved and normal squash auto-merge is +> freshly armed, but no independent `APPROVED` review exists. Fourteen parent-head +> checks succeed, ten are skipped, and three fail. The failed evidence remains +> unavailable rather than being promoted to acceptance: GitHub denies the +> exact dependency comparison with HTTP 403, the governed Noema request times +> out, and the central Pingora gate treats a committed documentation PNG as a +> UTF-8 runtime candidate. +> +> The PNG failure is reproduced independently on the next direct-`main` PR, +> #774 at `5949e243673e7068743340a97ce33168a9d01969`; its product, frontend, +> CodeQL, Noema, Semgrep, OSV, Trivy, and ontology-independent checks succeed, +> all review threads are resolved, and normal auto-merge remains armed. Its +> Strix run exhausted the governed virtual provider route without a finding, +> and dependency review received the same HTTP 403. These external failures +> are not repaired by weakening LineageWeave tests or workflows. +> +> The central owner-bound repair is still candidate-only. In +> `ContextualWisdomLab/.github#1420`, another Agent's active ownership and +> unresolved bounded-PNG validation review are preserved; the branch is behind +> central `main` and has no independent approval or auto-merge. The newer +> `ContextualWisdomLab/.github#1466` is also open at +> `c17841917764960208e89b204c50e527d644db70`; its 67 focused owner-repository +> regression tests pass, all review threads are resolved, and normal squash +> auto-merge is armed, while current-head OpenCode/Noema verdicts and independent +> approval remain unavailable. Neither candidate is protected- +> main evidence, so affected LineageWeave required workflows are not rerun as +> though the root fix had landed. +> +> The largest implemented buyer-visible candidate remains #780: exact-value +> UI/CSV distinguishes the carrying Post from derivation evidence, additional +> Voices preserve authorized Post evidence, server-created PROV-O derivation, +> truth status, and cutoff, and paged JSON-LD unions multi-Voice properties for +> one subject. ADR 0246's twelve atomic classifications remain extensible rows, +> not enumerated combinations; hidden evidence is never replaced with the +> carrying Post. The committed desktop/mobile renders and authenticated +> PostgreSQL/API evidence remain revision-scoped. Fresh local verification on +> the product parent passes 37 focused Voice/ontology/SHACL/API tests and 29 +> affected frontend tests plus frontend lint. The broader frontend run passed +> 532 of 534 tests but two unrelated App tests exceeded their existing five-second +> local timeout; the exact-head hosted frontend gate remains successful, so the +> local timeout is not promoted to acceptance or hidden. Protected-main delivery, +> merge SHA, and deployed acceptance remain unavailable. Fresh remote checks +> preserve canonical `ContextualWisdomLab/LineageWeave`, `RankWeave`, +> `ThreadWeave`, lowercase `disksage`, and `TEPP`. No self-approval, admin +> bypass, force push, invented weight, or heuristic classification was used. + +> Exact-head development-loop overlay: 2026-09-01 01:41 KST. Protected +> `main` remains `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; ninety-five PRs and +> ten non-PR issues are open. PR #780's exact pre-overlay evidence parent is +> `29c21916de99fbe26b67ef4fe45aa72f26fc0020`, based directly on that +> `main`; the documentation-only overlay does not inherit its hosted results. +> All review threads were resolved and normal squash auto-merge was +> armed, but no independent `APPROVED` review exists. Fourteen exact-head +> checks succeed, eleven are skipped, three fail, and Strix remains in +> progress. The failures remain explicit unavailable evidence: the central +> bootstrap does not yet admit the committed PNG evidence, the governed +> review call timed out, and GitHub returned HTTP 403 for the exact-base/head +> dependency comparison. None is relabeled as product acceptance. +> +> The current candidate is still the largest completed buyer-visible gap in +> this loop: exact-value UI/CSV distinguishes the carrying Post from +> derivation evidence, additional Voices retain authorized Post evidence, +> server-created PROV-O derivation, truth status, and cutoff, and paged +> JSON-LD unions multi-Voice properties for one subject. Regression coverage +> and the committed 1440-by-1400 desktop and 390-by-1688 mobile Storybook +> renders remain revision-scoped evidence; protected-main delivery, a merge +> SHA, and deployed authenticated PostgreSQL/UI acceptance remain unavailable. +> ADR 0246 owns the twelve extensible atomic classifications and ADR 0256 owns +> evidence-bearing composition; the supporting PRD's conflicting ADR 0251 +> identity and duplicate occupational requirement identifiers remain +> candidate-only under #847, not silently reinterpreted here. +> +> Pending checks did not stop safe queue work. Exact-head review found no +> unresolved threads on direct-`main` PRs #771, #772, #774, #802, and #847; +> normal squash auto-merge is now armed on each, while required independent +> approval and failed or pending current-head checks remain unsatisfied. PR +> #640 moved concurrently to a new head during this audit, so its existing +> auto-merge was preserved and its new evidence was not inherited from the +> earlier snapshot. Fresh remote-name verification preserves canonical +> `ContextualWisdomLab/LineageWeave`, `RankWeave`, `ThreadWeave`, lowercase +> `disksage`, and `TEPP`. No self-approval, admin bypass, force push, invented +> weight, or heuristic classification was used. + +> Exact-head development-loop overlay: 2026-09-01 00:34 KST. Protected +> `main` remains `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; ninety-five PRs and +> ten non-PR issues are open. PR #780's exact implementation and rendered- +> evidence parent for this documentation-only update is +> `81b425f1253e15f615e3395c2c5ff9cce5456a75`, based on `main`. All review +> threads are resolved and normal squash auto-merge is armed, but the required +> independent `APPROVED` review is unavailable. Fourteen checks succeed, nine +> are skipped, and four exact-head reruns are queued or running; pending checks +> are not promoted to acceptance and did not stop review of the next direct +> `main` PR. PR #847 at `a87c6ec96c546c214461727efedeb8e7549d0fdd` +> also has all review threads resolved and normal squash auto-merge re-armed; +> it likewise remains blocked on independent approval and fresh failed-check +> reruns. +> +> Fresh local verification on #780's parent passes 38 focused Voice, +> ontology, SHACL, and authenticated-API selections plus 29 affected frontend +> tests. Frontend lint, type checking, and production build pass. Direct +> inspection of the committed 1440-by-1400 desktop and 390-by-1688 mobile +> Storybook renders confirms that carrying-Post navigation remains distinct +> from derivation evidence, including the horizontally scrolled narrow table. +> The candidate preserves ADR 0246's twelve extensible atomic Voice rows, +> authorized evidence, server-created PROV-O derivation, truth status, cutoff, +> and same-subject multi-Voice JSON-LD unions without enumerating combinations +> or substituting hidden evidence. Protected-main delivery, merge SHA, and a +> deployed authenticated runtime claim remain unavailable. Fresh remote +> verification preserves canonical `ContextualWisdomLab/LineageWeave`, +> `RankWeave`, `ThreadWeave`, lowercase `disksage`, and `TEPP`. No +> self-approval, admin bypass, force push, invented weight, or heuristic +> classification was used. + +> Exact-head development-loop overlay: 2026-08-31 23:16 KST. Protected +> `main` remains `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; ninety-five PRs and +> ten non-PR issues are open. PR #780's exact product and rendered-evidence +> parent for this documentation-only update is +> `72c9636a4a03deec9b6115e2ec41503ff52bd11e`, based on `main`; the later +> documentation revision does not inherit that parent's hosted evidence. All +> 24 review threads are resolved, but no independent `APPROVED` review exists. +> Fourteen checks succeed, three fail, fourteen are skipped, and none is +> pending. The failures remain fail-closed owner/platform evidence gaps: the +> required review bootstrap rejects the committed PNG before the central +> binary-evidence repair lands, Noema times out after the governed +> contextual-orchestrator sidecar is ready, and GitHub denies exact-base/head +> dependency-graph comparison with HTTP 403. They are not relabeled as product +> success. +> +> Fresh exact-head local verification passes 53 focused Voice, ontology, and +> SHACL tests, one authenticated PostgreSQL/API Voice projection test, and all +> 534 frontend tests. Frontend lint, type checking, and the production build +> also succeed. A broader local API-file run exposed three unrelated stale +> shared-stack schema failures, so those paths are not counted as acceptance. +> Direct +> inspection of the committed 1440-by-1400 desktop and 390-by-1688 mobile +> Storybook renders confirms that the exact-value view keeps its carrying Post +> action distinct from derivation evidence, including the horizontally +> scrolled narrow table. The candidate keeps ADR 0246's twelve atomic Voice +> classifications extensible; it does not enumerate combinations. Additional +> assignments retain an authorized evidence Post, PROV-O derivation, truth +> status, and cutoff, while an imported primary Voice acquires no invented +> derivation. Paged JSON-LD unions same-subject multi-Voice relations. Earlier +> authenticated PostgreSQL/runtime evidence remains revision-scoped; +> protected-main delivery and merge SHA remain unavailable. +> +> The central root repair is now `ContextualWisdomLab/.github#1466` at +> `c17841917764960208e89b204c50e527d644db70`: its focused policy contract +> validates complete bounded PNG evidence and rejects malformed or appended +> payloads. Normal squash auto-merge is armed, but Noema and OpenCode reruns are +> queued and no independent approval exists; it remains candidate-only. PR +> #847 remains a candidate-only PRD identity repair at +> `a87c6ec96c546c214461727efedeb8e7549d0fdd`; every review thread is resolved +> and auto-merge is armed, but dependency review and the current-head review +> verdict fail closed and no independent approval exists. PR #640's current +> head `d56efa3a7358cc8e6f8498894782e079b9ec0114` likewise has all 139 review +> threads resolved and normal auto-merge armed, but remains unapproved and is +> not protected-main evidence. Fresh remote verification preserves canonical +> `ContextualWisdomLab/LineageWeave`, `RankWeave`, `ThreadWeave`, lowercase +> `disksage`, `TEPP`, `fast-mlsirm`, and `contextual-orchestrator`. No +> self-approval, admin bypass, force push, heuristic classification, invented +> weight, or hidden-evidence substitution was used. + +> Exact-head development-loop overlay: 2026-08-31 20:49 KST. Protected +> `main` remains `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; ninety-five PRs and +> ten non-PR issues are open. PR #780's exact implementation and rendered- +> evidence parent for this documentation-only update is +> `8ad7f13a82e75cfc5adf485f455f719dc208201e`, based on `main`. All 24 +> review threads are resolved, no independent `APPROVED` review exists, and +> normal squash auto-merge was freshly re-armed without bypass. Every check is +> terminal on that parent. Product tests, frontend lint/tests/build, ontology +> publication, CodeQL, Semgrep, OSV, Trivy, Scorecard, the queue scan, Strix, +> CodeRabbit, and Devin succeed. Required-workflow bootstrap, Noema, and +> dependency review fail closed; they are not relabeled as product success. +> +> Fresh local verification passes 37 focused Voice-authority, ontology, +> SHACL, and authenticated-API tests plus 29 affected frontend tests, frontend +> lint, type checking, and the production build. Direct inspection of the +> committed 1440-by-1400 desktop and 390-by-1688 mobile Storybook renders +> confirms that the exact-value table keeps the carrying Post action distinct +> from derivation evidence, including the horizontally scrolled narrow view. +> The candidate preserves ADR 0246's twelve atomic Voice classifications as +> extensible rows, additional assignments retain an authorized evidence Post, +> server-created PROV-O derivation, truth status, and cutoff behavior, imported +> primary Voices acquire no invented derivation, and paged JSON-LD unions +> same-subject multi-Voice relations. Earlier authenticated PostgreSQL/runtime +> evidence remains revision-scoped; protected-main delivery and merge SHA are +> unavailable. +> +> The supporting PRD identity repair remains candidate-only in #847 at +> `a87c6ec96c546c214461727efedeb8e7549d0fdd`, with auto-merge armed, all +> product checks successful, and dependency review plus OpenCode failing +> closed; it still lacks an independent approval. The central binary-evidence +> repair remains candidate-only in `ContextualWisdomLab/.github#1420` at +> `b4dfa6163eb58ac9f9b1a240bcfbb5bfda957332`; it is behind central `main`, +> exact-head path policy and OpenCode fail, Noema is cancelled, and auto-merge +> is currently not armed. Accepted ADRs therefore remain authoritative. Fresh +> remote verification confirms canonical `ContextualWisdomLab/LineageWeave`, +> `RankWeave`, `ThreadWeave`, lowercase `disksage`, `TEPP`, `fast-mlsirm`, and +> `contextual-orchestrator`. No self-approval, admin bypass, force push, +> heuristic classification, invented weight, or hidden-evidence substitution +> was used. + +> Exact-head development-loop overlay: 2026-08-31 19:39 KST. Protected +> `main` remains `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; ninety-one PRs and +> nine non-PR issues are open. PR #780's exact parent for this evidence-only +> update is `79dbedf3d8ea86bb17fda2484e22a5b21566a916`, based on `main`. +> All review threads are resolved and normal squash auto-merge remains armed, +> but no independent `APPROVED` review exists. Fourteen checks succeed, four +> fail, and eleven are skipped; none is pending. Product tests, frontend lint, +> frontend tests/build, ontology publication, CodeQL, Semgrep, OSV, Trivy, +> Scorecard, the queue scan, CodeRabbit, and Devin succeed on that exact head. +> The four failures remain fail-closed external or owner-bound evidence gaps: +> the protected central Pingora policy reads the committed PNG render as +> UTF-8, Noema times out after the governed contextual-orchestrator sidecar is +> ready, Strix exhausts the virtual provider route after bounded retries, and +> GitHub denies dependency-graph comparison with HTTP 403. None is relabeled +> as product success. +> +> The central binary-evidence root repair remains candidate-only in +> `ContextualWisdomLab/.github#1420` at +> `b4dfa6163eb58ac9f9b1a240bcfbb5bfda957332`. It is currently behind central +> `main`, lacks an independent approval, and its hosted OpenCode verdict fails; +> normal squash auto-merge was re-armed without bypass. PR #847's PRD identity +> repair remains candidate-only at +> `a87c6ec96c546c214461727efedeb8e7549d0fdd`, with auto-merge armed and no +> independent approval. Accepted ADRs therefore remain authoritative. +> +> The largest directly actionable buyer gap remains the PR #780 Voice export +> implementation: exact-value UI and CSV distinguish the carrying Post from +> genuine derivation evidence; additional Voices preserve an authorized Post, +> PROV-O derivation, truth status, and cutoff; primary Voices do not acquire an +> invented derivation; and paged JSON-LD unions same-subject multi-Voice +> relations. ADR 0246's twelve atomic classifications remain extensible rows, +> not enumerated combinations. Earlier authenticated synthetic PostgreSQL/API +> and desktop/mobile rendered evidence remains revision-scoped; protected-main +> delivery and merge SHA are unavailable. Canonical remotes were freshly +> verified as `ContextualWisdomLab/LineageWeave`, `RankWeave`, `ThreadWeave`, +> lowercase `disksage`, `TEPP`, `fast-mlsirm`, and +> `contextual-orchestrator`. No self-approval, admin bypass, force push, +> heuristic classification, invented weight, or hidden-evidence substitution +> was used. + +> Development-loop evidence snapshot: 2026-08-31 17:13 KST. Protected +> `main` is `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; eighty-two PRs and +> ten non-PR issues are open. PR #780's exact product parent for this +> documentation snapshot is `e71895c06282692570ca683a726e7c8de3ec8c08`, +> based on `main`, with no +> independent `APPROVED` review. Fourteen checks succeed, three fail, one is +> still running, and eleven are skipped. The product tests are green; the +> failures are fail-closed owner gates: dependency-graph comparison is denied +> with HTTP 403, the required central policy attempts to decode a PNG as +> UTF-8, and the review identity cannot be verified after the governed +> review service starts. This evidence-only update does not transfer those +> results to itself, and pending work is not promoted to completion. +> +> The reusable central-policy root repair remains candidate-only in +> `ContextualWisdomLab/.github#1420`. Its exact head is now +> `b4dfa616aa09eec3169547fef8d03b17e1cc3437` after normal merges of current +> central `main`; the sole CHANGELOG conflict retained both independently +> owned entries; the later synchronization cleanly adopts the protected-main +> Noema module-entrypoint repair. The focused policy and queue-contract +> selection passed 166 tests on the preceding synchronized head; fresh hosted +> evidence is required for the moved head. It still +> lacks an independent approval. GitHub's GraphQL quota prevented enabling +> normal auto-merge on both PRs in this snapshot; `auto_merge` is therefore +> honestly recorded as unavailable rather than armed. No bypass or +> self-approval was attempted. +> +> The largest directly actionable buyer gap remains implemented on PR #780: +> exact-value UI and CSV distinguish the carrying Post from genuine +> derivation evidence, additional Voices preserve authorized Post evidence, +> PROV-O derivation, truth status, and cutoff, and paged JSON-LD unions +> same-subject multi-Voice relations. The candidate keeps ADR 0246's twelve +> atomic Voices extensible and does not enumerate combinations. Fresh local +> verification passes 37 focused Python tests and 29 frontend tests plus +> frontend lint, type checking, and production build. Direct inspection of +> the committed desktop and mobile Storybook renders confirms the narrow +> exact-value view keeps carrying and evidence actions distinct. Earlier +> authenticated synthetic PostgreSQL/API evidence is revision-scoped; +> protected-main delivery and merge SHA remain unavailable. +> +> Development-loop evidence snapshot: 2026-08-31 14:58 KST. Protected +> `main` is `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; seventy-eight PRs and +> ten issues are open. PR #780's exact product parent is +> `3da03c8fcde4ae437f2eabb490f1fe01789e3fdb`; this documentation update +> records that parent rather than transferring evidence to itself. All review +> threads are resolved and normal squash auto-merge is armed, but no +> independent `APPROVED` review exists. Exact-parent product, frontend, +> ontology-publication, and security checks succeeded except two owner-side +> fail-closed gates: dependency-graph comparison returned HTTP 403 and the +> central OpenCode bootstrap rejected PNG evidence as text. The central owner +> repair is now `.github#1420` at +> `d84d65cc1a5b32cda3f5a723a61bab82be0c3d1e`: its valid review finding is +> repaired with bounded PNG ancillary-chunk ordering, cardinality, +> mutual-exclusion, payload, and decompression validation. The owner suite +> passes (2,128 tests, 1 skipped, 21 subtests), review threads are resolved, +> and fresh hosted checks are running; it remains candidate-only and still +> lacks an independent approval. +> +> Fresh LineageWeave validation passes 36 focused Python tests (one live-stack +> test skipped), 29 serial affected frontend tests, lint, type checking, and +> production build. A concurrent whole-frontend run produced eight timeout +> failures while 526 tests passed; the affected selection passes when run +> serially, so this is recorded as non-terminal local load evidence rather +> than hidden or promoted to a product failure. Direct desktop and mobile +> screenshot inspection confirms that the carrying Post and derivation +> evidence remain distinct, including the narrow horizontally scrollable +> exact-value table. The accepted Voice contract remains twelve extensible +> atomic classifications with no enumerated combinations; additional Voices +> retain authorized Post evidence, PROV-O derivation, truth status, and +> cutoff. Protected merge and merge SHA remain unavailable. Canonical remote +> names are `ContextualWisdomLab/LineageWeave`, `RankWeave`, `ThreadWeave`, +> lowercase `disksage`, and `TEPP`. No self-approval, bypass, force push, +> heuristic classification, invented weight, or hidden-evidence substitution +> is used. +> +> Exact-head development-loop overlay: 2026-08-31 13:28 KST. +> Protected `main` remains `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; +> seventy-four open PRs and ten open issues were enumerated in a fresh +> snapshot. PR #780's current implementation and rendered-evidence parent is +> `d7df80e04939bb7d953dbd623c739356fb8c61fd`; this documentation-only overlay +> records that parent rather than transferring its hosted evidence to the +> later documentation revision. All review threads are resolved and normal +> squash auto-merge remains armed, but there is no independent `APPROVED` +> review. Exact-parent Tests, ontology publication, CodeQL, Semgrep, OSV, +> Trivy, Scorecard, Strix, Noema, CodeRabbit, and Devin checks are successful. +> Dependency review fails closed because GitHub denies dependency-graph +> evidence with HTTP 403. The required OpenCode bootstrap fails before review +> because its protected central policy still attempts to decode the committed +> PNG render as UTF-8. The owner repair is +> `ContextualWisdomLab/.github#1420` at +> `a3c79864a656b6e7ed1640d905d2335206109927`: the branch was normally merged +> with the concurrently advanced central `main`, resolving the sole CHANGELOG +> conflict by retaining both products' entries. All 92 focused policy tests +> pass locally; fresh exact-head hosted checks are running, and the +> authenticated OpenCode verdict and independent approval remain absent. +> Normal squash auto-merge is armed there. The repair remains candidate-only +> owner evidence. +> +> Fresh local verification on PR #780's parent passes 37 focused ontology, +> SHACL, Voice-authority, and API tests plus 29 affected frontend tests, +> frontend lint, type checking, and the production build. Direct inspection of +> the committed 1440-by-1400 desktop and 390-by-1688 mobile Storybook renders +> confirms that the carrying Post remains visible and derivation evidence is a +> separate action even in the horizontally scrolled narrow table. This closes +> the largest directly actionable Voice export gap only on the candidate: +> twelve atomic ADR 0246 Voices remain extensible rows rather than enumerated +> combinations; additional assignments retain an authorized evidence Post, +> PROV-O derivation, governed truth status, and cutoff; imported primary Voices +> retain their carrying Post without invented derivation; and paged JSON-LD +> unions same-subject multi-Voice relations. Earlier authenticated synthetic +> Keycloak/PostgreSQL/API acceptance remains revision-scoped. Current-head +> hosted integration, protected merge, and merge SHA remain unavailable. The +> supporting PRD identity conflict stays tracked by issue #807 and candidate +> PR #847; accepted ADRs remain authoritative. Canonical remotes were freshly +> verified as `ContextualWisdomLab/LineageWeave`, `RankWeave`, `ThreadWeave`, +> lowercase `disksage`, and `TEPP`. No self-approval, admin bypass, force push, +> hidden-evidence substitution, heuristic classification, or invented weight +> is used. +> +> Exact-head development-loop overlay: 2026-08-31 09:00 KST. +> Protected `main` remains `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; +> sixty open PRs and ten open issues were enumerated in a fresh snapshot. +> PR #780's latest product implementation revision is +> `4b45377bbadf6fdff023daeb34f82a46661e44de`; this later evidence-only +> overlay does not transfer hosted evidence from that revision. +> Every review thread is resolved +> and normal squash auto-merge remains armed. The focused ontology regression +> selection passes (37 tests); the affected frontend selection passes (29 +> tests), lint succeeds, and the production frontend build succeeds. Fresh +> inspection of the committed desktop and mobile Storybook renders keeps the +> carrying Post visible while the horizontally scrolled evidence action is +> focused. Current-head GitHub checks are terminal but not successful. The +> required workflow bootstrap and dependency review fail closed: the first +> still depends on the candidate-only central workflow repair in +> `ContextualWisdomLab/.github#1420` at +> `212ade1d1750e66851bc8ef57defa2c02a899e12`, where 92 focused policy tests +> pass with 100% statement and branch coverage and normal squash auto-merge is +> armed; the latter is a fail-closed +> dependency-graph 403 rather than a product-test failure. Strix is successful +> on this exact head. The central repair still awaits its current-head OpenCode +> verdict and independent approval and is not protected-main workflow evidence. +> Independent approval is still absent, so protected merge and merge SHA are +> unavailable. The largest directly actionable Voice acceptance gap in this +> PR remains implemented only on this candidate head: exact-value UI and CSV +> distinguish the carrying Post from genuine derivation evidence, additional +> Voices retain authorized Post evidence, PROV-O derivation, truth status, and +> cutoff behavior, and primary Voices do not acquire an invented derivation. +> Authenticated PostgreSQL/API evidence exists on the unchanged implementation +> ancestor; the current exact head has fresh desktop/mobile rendered evidence +> but awaits current-head hosted integration checks. Neither is relabeled as +> protected-main delivery. +> The supporting PRD's duplicate occupational requirement identifiers and +> stale ADR references remain an authority-document gap tracked by issue #807; +> PR #847 is a green candidate repair with normal auto-merge, but it still lacks +> independent approval and therefore is not promoted over accepted ADRs. No +> self-approval, bypass, force +> push, invented weight, heuristic classification, or hidden-evidence +> substitution is used. +> +> Exact-head authenticated-render overlay: 2026-08-31 00:42 KST. +> Protected `main` remains `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; +> thirty-nine open PRs and ten open issues were enumerated in one fresh +> snapshot. PR #780's current implementation revision is +> `a26af45cab561965035e5647f5f15626165479cf`. Rebuilding the official +> `lineageweave` Compose backend and frontend against that revision exposed a +> buyer-blocking Post-detail 500: the governed occupational-construct evidence +> status loader was called but not imported. The minimal root repair restores +> that owned loader import and updates the authenticated API fixture to replay +> its existing extraction-run migration instead of relying on test order. +> Both focused authenticated PostgreSQL/API regressions pass. Genuine Keycloak +> login and the rebuilt frontend/backend then render the Voice exact-value +> table at 1440-by-1000 desktop and 390-by-844 mobile viewports; inspected +> runtime-only screenshots keep the carrying-Post property distinct from the +> evidence column and were not added to git because the authorized runtime is +> not synthetic repository material. At this exact head sixteen hosted checks +> pass, two remain pending, and two fail outside this repository's product +> implementation: the central PNG workflow bootstrap awaits protected delivery +> of `ContextualWisdomLab/.github#1420`, while dependency review receives the +> repository dependency-graph denial. Normal squash auto-merge is armed on +> both PRs. Independent approval, protected merge, and merge SHA remain +> unavailable; no self-approval, bypass, workflow weakening, or force push is +> used. +> +> Exact-head authenticated-acceptance overlay: 2026-08-30 23:59 KST. +> Protected `main` remains `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; +> thirty-three open PRs and ten open issues were enumerated in one fresh +> snapshot. PR #780's authenticated PostgreSQL/API acceptance revision is +> `dd674eb9b90734e8ae5f900c4a3fc82bd213bafd`; this later documentation-only +> overlay does not relabel itself as that implementation revision. The live +> synthetic Keycloak/PostgreSQL test exposed a stale test oracle that expected +> a private `post/{id}` IRI even though the canonical neighborhood projection +> uses the governed `node/node_post/{id}` identity for every Post node. The +> minimal repair changes only that assertion, and the genuine authenticated +> API test now passes. Carrying-Post and derivation-evidence relations remain +> distinct, and no hidden Post is substituted. Rendered authenticated UI, +> terminal hosted Checks, independent approval, protected merge, and merge SHA +> remain unavailable on this moved head. The central binary-evidence root fix +> is still candidate-only in `ContextualWisdomLab/.github#1420`; no local +> workflow weakening, self-approval, bypass, or force push is used. +> +> Exact-head review-repair overlay: 2026-08-30 22:31 KST. Protected `main` +> remains `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; twenty-five open PRs and +> ten open issues were enumerated in one fresh snapshot. PR #780's current +> implementation-evidence revision is +> `047b73010bff5c29847cb4b35a1308c77d14939b`; this later documentation-only +> overlay records that point-in-time evidence without presenting itself as the +> implementation revision. Exact-head review found that the first inverse-link +> SHACL repair still allowed an unrelated second Post to claim the same Voice +> assignment. The minimal root repair validates both directions, so one +> assignment cannot belong to multiple Posts, and adds a negative regression. +> All 52 focused ontology, SHACL, neighborhood, and Voice-authority tests pass. +> Current-head hosted checks were restarted and normal squash auto-merge remains +> enabled, but independent approval, terminal required review/security verdicts, +> protected merge, merge SHA, and authenticated rendered-stack acceptance are +> still unavailable. No stale evidence, self-approval, bypass, or force push is +> used. The carrying Post and derivation-evidence Post remain separate across +> exact-value UI/CSV and JSON-LD; imported primary Voices do not acquire an +> invented derivation. +> +> Exact-head review-repair overlay: 2026-08-30 21:23 KST. Protected `main` +> remains `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; twenty open PRs and ten +> open issues were freshly enumerated. PR #780's current implementation +> revision is `4fa63302282dc6446cf224794ed8833d5c853d97`. Exact-head review found +> that SHACL required a carrying Post but did not prove that the same Post's +> `hasVoiceAssignment` relation referenced the assignment, so contradictory +> carrying and inverse links could validate. The minimal root repair adds the +> inverse-link constraint and positive/mismatched regression coverage. All 75 +> focused ontology, SHACL, and neighborhood tests pass. Hosted Checks, +> independent approval, authenticated rendered-stack acceptance, protected +> merge, and merge SHA remain unavailable on this moved head; older evidence +> is not transferred. Normal governance remains in force with no self-approval, +> admin bypass, or force push. +> +> Exact-head review-repair overlay: 2026-08-30 20:18 KST. Protected `main` +> remains `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; twenty-one PRs and ten +> issues are open. PR #780's current implementation revision is +> `d56e86048cfe62a92d0e6cfa0e5085eb1bc78c34`. Exact-head review exposed a +> real ontology/SHACL conflict: removing invented derivation from imported +> primary Voices also removed the source-Post relation that the published +> `VoiceAssignmentShape` required. The minimal root repair adds the distinct +> `voiceAssignmentCarryingPost` relation for every assignment, keeps +> `voiceAssignmentEvidence` as a `prov:wasDerivedFrom` subproperty only for +> genuine additional assignments, and aligns ADR 0256, the ontology, SHACL, +> JSON-LD projection, authenticated API assertion, and regression tests. +> Seventy-five focused Python tests and all 533 frontend tests pass on that +> implementation revision. Hosted Checks, independent approval, protected +> merge, and a rendered authenticated-stack recheck remain unavailable on the +> moved head; older check/review evidence is not transferred. Normal merge +> governance remains in force with no self-approval, bypass, or force push. +> +> Exact-head development-loop overlay: 2026-08-30 19:20 KST. Protected `main` +> remains `cb187cadee5fb6c46d8a944815ccc154a1e028d1`; eighteen PRs and ten +> issues are open. PR #780's exact implementation-evidence revision is +> `43dfcca20667e79e2c461b38b8fc15a0e2927e4d`; later commits in this candidate +> update only this point-in-time evidence overlay. That revision adds the +> missing authenticated +> PostgreSQL API acceptance and the minimal root repair it exposed. The real +> query failed before returning any neighborhood because PostgreSQL rejected +> an ungrouped `knowledge_graph_edge.created_at`; the candidate now groups the +> edge timestamp, migrates the throwaway integration database through the +> accepted Voice/PROV-O contracts, and proves a genuine Keycloak token can read +> a synthetic primary Voice. Imported primary carrying evidence remains in the +> compatibility exact-value row but no longer becomes invented +> `prov:wasDerivedFrom` or `voiceAssignmentEvidence`. Eighty focused tests, +> including the authenticated PostgreSQL path, pass. Earlier hosted full +> backend/frontend tests, 533 frontend tests, lint, type checking, production +> build, and inspected 1440-pixel desktop/390-pixel mobile synthetic renders +> remain revision-scoped supporting evidence and must be rerun after publish. +> Rendered authenticated-stack acceptance is still unavailable, so this is not +> a protected-main or completed UI acceptance claim. Normal squash auto-merge +> remains enabled; no self-approval or bypass is used. +> The required Pingora policy currently fails before review because it tries to +> UTF-8 decode the candidate PNG screenshots. The owner-repository repair is +> `ContextualWisdomLab/.github#1427` at +> `ef689f9fe4c0b55aece1e394a29475c96e32c06d`; 49 focused policy tests pass, +> but that PR is also candidate-only with normal auto-merge awaiting its own +> exact-head gates. No LineageWeave workaround weakens the central policy. +> Noema's 413, Strix provider/protocol failure, and dependency-review 403 remain +> fail-closed external gate evidence, not successful review or vulnerability +> clearance. Canonical remote identities were freshly checked as +> `ContextualWisdomLab/LineageWeave`, `RankWeave`, `ThreadWeave`, lowercase +> `disksage`, `TEPP`, `fast-mlsirm`, and `contextual-orchestrator`. +> +> Exact-head merge overlay: 2026-08-30 13:40 KST. Protected `main` is now +> `cb187cadee5fb6c46d8a944815ccc154a1e028d1`, the squash merge of PR #782. +> GitHub records no independent `APPROVED` review on that PR, so PR #808 is the +> active fail-closed governance repair and protected-main runtime acceptance for +> the leftover-map coordinate slice remains unverified. PR #780 is being merged +> with this current `main` without transferring its earlier checks or review +> evidence; its Voice export remains candidate-only until fresh exact-head +> checks, independent approval, authenticated PostgreSQL API evidence, and +> desktop/mobile rendered evidence succeed. There are currently fifteen open +> PRs and ten open issues. Stacked PRs #802 through #806 and #809 must not merge +> before the parent governance repair and fresh retargeting evidence. +> The exact-head UI audit found one remaining authority leak in that candidate: +> an imported primary Voice could still render its carrying Post a second time +> as derivation evidence because the table read the compatibility field. The +> minimal repair now uses the authorized non-primary assignment projection as +> the derivation authority, keeps the primary row as a non-navigation evidence +> count, and retains the distinct carrying-Post action. Twenty-eight focused +> frontend tests, lint, type checking, and a production Storybook build passed. +> Fresh 1440 × 1000 and 390 × 844 renders show one derivation action only on the +> additional Voice row; these synthetic screenshots are candidate evidence, +> not authenticated PostgreSQL acceptance or protected-main proof. +> +> Exact-head Voice export and authority audit: 2026-08-30 08:05 KST. Protected +> `main` was `fc13acaa20adca11968238e398d4aafcf62b6cee`; sixteen PRs and ten +> issues were open. The largest directly actionable Voice acceptance gap on +> this head was export ambiguity: the rendered exact-value table already +> offered separate actions for the carrying Post and derivation-evidence Post, +> while CSV exposed only `evidence_post_id`. The current candidate adds explicit +> `carrying_post_id` and `derivation_evidence_post_id` columns without replacing +> the compatibility field. It also repairs an ADR-number collision: Voice +> combinations are governed by ADR 0256, while ADR 0251 governs the distinct +> FJA/I-O-Psychology layer. Protected delivery and authenticated PostgreSQL/UI +> acceptance remain unavailable until this candidate passes its exact-head +> gate and the runtime evidence is collected. Canonical remote names were +> rechecked as `ContextualWisdomLab/LineageWeave`, `RankWeave`, `ThreadWeave`, +> lowercase `disksage`, `TEPP`, `contextual-orchestrator`, and `fast-mlsirm`. +> The supporting PRD still duplicates PRD-FR-2A, PRD-FR-2B, and PRD-FR-2C, +> cites absent ADR 0264, and also assigns occupational linkage to ADR 0256. +> Accepted ADR 0256 instead governs Voice composition. Issue #807 tracks the +> authority reconciliation and unique-reference regression. Until that work +> lands, those later duplicate occupational claims are unverified supporting +> text, not accepted ADR or protected-main product evidence. +> The 17:59 KST queue audit also found PR #782 proposing ADR 0267 / migration +> 0245 / v2.24.0 on the same protected-main base. Its buyer-visible coordinates +> are backed by additional projection from the frozen local NumPy residual-map +> debt, while ADR 0208 assigns residual interaction maps and their coordinates +> to fast-mlsirm's Rust result contract. fast-mlsirm PR #1417 publishes the +> versioned Rust result envelope for person/item coordinates and retained-cell +> interpretation; its exact head has passed the Rust, Python, package, GPU, +> fuzz, dependency, and static-analysis gates, but protected delivery remains +> unavailable pending the current required security verdict and independent +> approval. Normal squash auto-merge is enabled. LineageWeave #782 does not +> consume and persist that owner artifact. It therefore remains a conflicting +> candidate, not a protected-main capability or a permissible continuation of +> the product Gap slice; no local reimplementation or heuristic substitute is +> authorized. Its +> open stacked children #802 through #806 continue the same leftover-map +> segment series. None can be treated as protected-main evidence: #782 must +> resolve the ADR 0208 ownership conflict and protected-merge first, after which +> each surviving child must be retargeted to `main` in parent order and collect +> fresh exact-head checks, independent approval, API/runtime evidence, and +> release/ADR/schema compatibility evidence. +> PR #782 also currently carries 2.24.0, 2.25.0, and 2.26.0 changelog and ADR +> material in one head; those later slices do not cure the ADR 0208 ownership +> conflict and cannot be treated as separately delivered releases. +> The refreshed inventory has sixteen open PRs and ten open issues. PR #780's +> exact-value export repair remains candidate-only: all review threads are +> resolved and the focused frontend suite passes, but independent approval and +> required current-head review/security verdicts remain unsatisfied. Normal +> squash auto-merge stays enabled; no self-approval or policy bypass is used. +> > Exact-head loop overlay: 2026-08-29 13:20 KST. Protected `main` is > `fc13acaa20adca11968238e398d4aafcf62b6cee` (v2.23.0 leftover-map > explained leftover share, #775). Open ready PRs still lack independent @@ -103,7 +885,6 @@ > share `e = R̂² / R²` (ADR 0266 / migration 0244 / v2.23.0) so > `e + s + x = 1` is buyer-auditable. Do not persist leftover-map > coordinates in this slice. - > Exact-head loop overlay: 2026-08-28 KST. Protected `main` was > `bbb191924e9881a5201f1ecf63c854d92992cc1c`; seven PRs and nine issues were > open. PR #763 was `b51d3bd8872b` and PR #762 was `e6ca33dba1b5`; both were diff --git a/docs/screenshots/voice-combination-export-desktop.png b/docs/screenshots/voice-combination-export-desktop.png new file mode 100644 index 000000000..48e3389ef Binary files /dev/null and b/docs/screenshots/voice-combination-export-desktop.png differ diff --git a/docs/screenshots/voice-combination-export-mobile.png b/docs/screenshots/voice-combination-export-mobile.png new file mode 100644 index 000000000..e6d0b65fa Binary files /dev/null and b/docs/screenshots/voice-combination-export-mobile.png differ diff --git a/docs/storybook-inventory.md b/docs/storybook-inventory.md index f426285a6..62c5f9cf3 100644 --- a/docs/storybook-inventory.md +++ b/docs/storybook-inventory.md @@ -13,7 +13,7 @@ operator-facing control you can click before changing product CSS. | `Post/Connect perspective` | Choose one unassigned Voice and an explicit evidence state, then record the open post as its evidence. `Ready`, `Completed`, and `NarrowViewport` cover untouched, successful, and mobile states. | `VoiceAssignmentForm`, `admin-form`, `btn-primary` | | `Evidence/CitationChip` | Click a cited title to open that source post. | `--color-chip-border`, `--radius-chip`, `CitationChip` | | `Evidence/OrganizationAliasChip` | Click a cataloged org; the parenthetical is the unique corroborated SKOS companion. | `--color-chip-border`, `--radius-chip`, `OrganizationAliasChip` | -| `Evidence/OntologyExplorer` | Distinguish Event Lineage from typed ontology facts, inspect Post/Person/Organization/Team/Project shapes, token-backed secondary cues, and truth labels, then open authorized evidence. The named exact-values region supports keyboard scrolling; `LongLabelsAndEvidenceTable` proves complete labels wrap without character-count truncation, while `CombinedVoiceEvidence` covers primary-plus-additional Voice assignments and focuses the evidence action distinct from the carrying-Post action. Desktop, narrow, drawers, legend/filter, empty, truncated, partial, denied, stale, and rejected scenes cover ADR 0184/0222/0251 states. | `OntologyExplorer`, `ontologyLayout`, `--ontology-node-*-fill`, `--color-table-border` | +| `Evidence/OntologyExplorer` | Distinguish Event Lineage from typed ontology facts, inspect Post/Person/Organization/Team/Project shapes, token-backed secondary cues, and truth labels, then open authorized evidence. The named exact-values region supports keyboard scrolling; `LongLabelsAndEvidenceTable` proves complete labels wrap without character-count truncation, while `CombinedVoiceEvidence` covers primary-plus-additional Voice assignments and focuses the evidence action distinct from the carrying-Post action. Desktop, narrow, drawers, legend/filter, empty, truncated, partial, denied, stale, and rejected scenes cover ADR 0184/0222/0256 states. The audited combined-Voice renders are `docs/screenshots/voice-combination-export-desktop.png` and `docs/screenshots/voice-combination-export-mobile.png`. | `OntologyExplorer`, `ontologyLayout`, `--ontology-node-*-fill`, `--color-table-border` | | `Evidence/OntologyExplorer` | Distinguish Event Lineage from typed ontology facts, inspect Post/Person/Organization/Team/Project/Work-evidence shapes, token-backed secondary cues, and truth labels, then open authorized evidence. The populated scene includes one assertion-backed occupational construct without a person-trait promotion. The named exact-values region supports keyboard scrolling; `LongLabelsAndEvidenceTable` proves complete labels wrap without character-count truncation. Desktop, narrow, drawers, legend/filter, empty, truncated, partial, denied, stale, and rejected scenes cover ADR 0184/0222/0255 states. | `OntologyExplorer`, `ontologyLayout`, `--ontology-node-*-fill`, `--color-table-border` | | `AnalysisRun/CutoffKnownBody` | Read the cutoff-known sentence, then compare it with the live body below. | `--color-accent-border`, `--space-panel-block`, `--radius-panel`, `CutoffKnownBody` | | `Analysis/LineageEntityPicker` | Choose which corp to reconstruct, then click Request a lineage reconstruction. | `--space-control-gap`, `--size-control-min`, `--radius-control`, `LineageEntityPicker` | diff --git a/docs/voice-combination-technical-requirements.md b/docs/voice-combination-technical-requirements.md index f075f7e2e..5e5d1086a 100644 --- a/docs/voice-combination-technical-requirements.md +++ b/docs/voice-combination-technical-requirements.md @@ -1,6 +1,6 @@ # Voice-of-X Combination Technical Requirements -This supporting TRD projects ADR 0246, ADR 0251, and ADR 0252. Those ADRs are +This supporting TRD projects ADR 0246, ADR 0256, and ADR 0252. Those ADRs are normative when this document and an implementation differ. ## Scope diff --git a/frontend/src/App.css b/frontend/src/App.css index 3e4c13599..1446ae41b 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1369,13 +1369,17 @@ .ontology-exact-values { margin-top: 1rem; max-width: 100%; +} + +.ontology-exact-values-scroll { + max-width: 100%; overflow-x: auto; overscroll-behavior-inline: contain; scrollbar-gutter: stable; -webkit-overflow-scrolling: touch; } -.ontology-exact-values:focus-visible { +.ontology-exact-values-scroll:focus-visible { outline: 2px solid var(--color-focus-border); outline-offset: 2px; border-radius: var(--radius-control); @@ -1393,6 +1397,19 @@ text-align: left; } +.ontology-exact-values th:first-child, +.ontology-exact-values td:first-child { + position: sticky; + left: 0; + z-index: 1; + background: var(--surface, #fff); +} + +.ontology-exact-values th:first-child { + z-index: 2; + background: var(--color-table-th-bg); +} + .ontology-exact-values .is-selected { background: var(--color-table-row-hover); } diff --git a/frontend/src/components/OntologyExplorer.test.tsx b/frontend/src/components/OntologyExplorer.test.tsx index 7820d383c..c73f52a0f 100644 --- a/frontend/src/components/OntologyExplorer.test.tsx +++ b/frontend/src/components/OntologyExplorer.test.tsx @@ -138,6 +138,24 @@ function neighborhood(overrides: Partial = {}): Ont } describe("OntologyExplorer", () => { + it("keeps the exact-value heading outside the focusable horizontal scroller", () => { + const { container } = render( + , + ); + + const region = screen.getByRole("region", { name: "Exact values" }); + const heading = screen.getByRole("heading", { name: "Exact values" }); + const scroller = container.querySelector(".ontology-exact-values-scroll"); + expect(region).toContainElement(heading); + expect(scroller).toHaveAttribute("tabindex", "0"); + expect(scroller).toContainElement(screen.getByRole("table", { name: "Exact values" })); + expect(scroller).not.toContainElement(heading); + }); + it("renders a project node with a text-labeled diamond", () => { const payload = neighborhood(); const projectNode = { @@ -286,6 +304,19 @@ describe("OntologyExplorer", () => { focusNodeId={POST_ID} neighborhood={{ ...source, + voice_assignments: [ + { + post_id: POST_ID, + voice_type_code: "voc_customer", + voice_type_iri: "https://example.test/voice/customer", + voice_type_label: "Voice of Customer", + is_primary: false, + truth_status_code: "truth_observed", + recorded_at: "2026-01-10T12:00:00+00:00", + provenance_reference: "prov:assignment", + evidence_post_id: EVIDENCE_POST_ID, + }, + ], exact_value_rows: [ ...source.exact_value_rows, { @@ -321,6 +352,47 @@ describe("OntologyExplorer", () => { expect(onOpenEvidence).toHaveBeenCalledWith(EVIDENCE_POST_ID); }); + it("does not present imported primary source evidence as derivation evidence", () => { + const source = neighborhood(); + render( + , + ); + + expect(screen.getByRole("button", { name: "Open post: Demo public post" })).toBeVisible(); + expect(screen.queryByRole("button", { name: "Open evidence: Demo public post" })).not.toBeInTheDocument(); + }); + it("keeps complete long node labels in the rendered graph and exact-value table", () => { const longLabel = "Synthetic multilingual procurement governance decision with complete provenance"; diff --git a/frontend/src/components/OntologyExplorer.tsx b/frontend/src/components/OntologyExplorer.tsx index fb05b0144..16b505bc4 100644 --- a/frontend/src/components/OntologyExplorer.tsx +++ b/frontend/src/components/OntologyExplorer.tsx @@ -526,74 +526,88 @@ function OntologyExactValueTable({ .filter((node) => node.node_type_code === "node_post") .map((node) => [node.node_id, node.display_label]), ); + const derivationEvidenceByVoice = new Map( + (payload.voice_assignments ?? []) + .filter((assignment) => !assignment.is_primary && assignment.evidence_post_id) + .map((assignment) => [ + `${assignment.post_id}\u0000${assignment.voice_type_code}`, + assignment.evidence_post_id as string, + ]), + ); return (

{t("Exact values")}

{payload.exact_value_rows.length === 0 ? (

{t("No related information is available. Open a visible post next.")}

) : ( - - - - - - - - - - - - - - - - {payload.exact_value_rows.map((row) => ( - - - - - - - - + + + + + + + + + ); + })} + +
{t("Exact values")}
{t("Source")}{t("Property")}{t("Target")}{t("Truth status")}{t("Valid from")}{t("Valid to")}{t("Evidence")}{t("Recorded at")}
- - {row.property_label}{row.target_label}{t(TRUTH_LABEL[row.truth_status_code] ?? row.truth_status_code)}{row.valid_from.slice(0, 10) || t("Unknown")}{row.valid_to.slice(0, 10) || t("Unknown")} - {row.property_code === "hasVoiceAssignment" && row.evidence_post_id ? ( +
+ + + + + + + + + + + + + + + {payload.exact_value_rows.map((row) => { + const derivationEvidencePostId = + row.property_code === "hasVoiceAssignment" + ? derivationEvidenceByVoice.get(`${row.source_node_id}\u0000${row.target_node_id}`) + : undefined; + return ( + + - - - ))} - -
{t("Source")}{t("Property")}{t("Target")}{t("Truth status")}{t("Valid from")}{t("Valid to")}{t("Evidence")}{t("Recorded at")}
- ) : row.evidence_count} - {row.recorded_at.slice(0, 10)}
+
{row.property_label}{row.target_label}{t(TRUTH_LABEL[row.truth_status_code] ?? row.truth_status_code)}{row.valid_from.slice(0, 10) || t("Unknown")}{row.valid_to.slice(0, 10) || t("Unknown")} + {derivationEvidencePostId ? ( + + ) : row.evidence_count} + {row.recorded_at.slice(0, 10)}
+
)} ); diff --git a/frontend/src/ontologyLayout.test.ts b/frontend/src/ontologyLayout.test.ts index b609e7e2b..1458436de 100644 --- a/frontend/src/ontologyLayout.test.ts +++ b/frontend/src/ontologyLayout.test.ts @@ -8,6 +8,7 @@ import { } from "./ontologyLayout"; const POST_ID = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaa1"; +const EVIDENCE_POST_ID = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaa2"; const PERSON_ID = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbb1"; const CORP_ID = "cccccccc-cccc-cccc-cccc-ccccccccccc1"; const ONTOLOGY_NAMESPACE = "https://contextualwisdomlab.github.io/LineageWeave/ontology#"; @@ -126,7 +127,7 @@ describe("ontologyLayout", () => { truth_status_code: "truth_observed", recorded_at: "2026-01-10T12:00:00+00:00", provenance_reference: "Evidence-backed additional voice", - evidence_post_id: POST_ID, + evidence_post_id: EVIDENCE_POST_ID, }; const row = { ...source.exact_value_rows[0], @@ -136,7 +137,7 @@ describe("ontologyLayout", () => { target_node_id: assignment.voice_type_code, target_label: assignment.voice_type_label, target_type_code: "node_voice_type", - evidence_post_id: POST_ID, + evidence_post_id: EVIDENCE_POST_ID, }; const withVoice = { ...source, @@ -152,13 +153,91 @@ describe("ontologyLayout", () => { const csv = neighborhoodCsv(withVoice); expect(csv).toContain("Voice of Customer"); - expect(csv.split("\n")[0]).toContain("evidence_post_id"); + expect(csv.split("\n")[0]).toBe( + "edge_id,source_label,property_label,target_label,truth_status_code,recorded_at,ontology_property_iri,evidence_post_id,carrying_post_id,derivation_evidence_post_id", + ); expect(csv).toContain(POST_ID); + const voiceCsvRow = csv.split("\n").find((row) => row.includes("Voice of Customer")); + expect(voiceCsvRow).toContain(`${EVIDENCE_POST_ID},${POST_ID},${EVIDENCE_POST_ID}`); expect(filterNeighborhood(withVoice, "customer")!.voice_assignments).toEqual([assignment]); expect(filterNeighborhood(withVoice, "missing")!.voice_assignments).toEqual([assignment]); expect(accumulateNeighborhoodPages(source, withVoice).voice_assignments).toEqual([assignment]); }); + it("does not label imported primary evidence as derivation evidence in CSV", () => { + const source = payload(); + const primary = { + post_id: POST_ID, + voice_type_code: "voc_customer", + voice_type_iri: "https://example.test/voice/customer", + voice_type_label: "Voice of Customer", + is_primary: true, + truth_status_code: "truth_observed", + recorded_at: "2026-01-10T12:00:00+00:00", + provenance_reference: "Imported primary voice", + evidence_post_id: POST_ID, + }; + const primaryRow = { + ...source.exact_value_rows[0], + edge_id: `voice-assignment:${POST_ID}:voc_customer`, + property_code: "hasVoiceAssignment", + property_label: "Voice carried by this post", + target_node_id: primary.voice_type_code, + target_label: primary.voice_type_label, + target_type_code: "node_voice_type", + evidence_post_id: POST_ID, + }; + + const csv = neighborhoodCsv({ + ...source, + voice_assignments: [primary], + exact_value_rows: [primaryRow], + }); + + expect(csv.split("\n")[1].split(",").slice(-3)).toEqual([ + POST_ID, + POST_ID, + "", + ]); + }); + + it("exports additional Voice derivation evidence from the assignment authority", () => { + const source = payload(); + const assignment = { + post_id: POST_ID, + voice_type_code: "voc_customer", + voice_type_iri: "https://example.test/voice/customer", + voice_type_label: "Voice of Customer", + is_primary: false, + truth_status_code: "truth_observed", + recorded_at: "2026-01-10T12:00:00+00:00", + provenance_reference: "prov:assignment", + evidence_post_id: EVIDENCE_POST_ID, + }; + const voiceRow = { + ...source.exact_value_rows[0], + edge_id: `voice-assignment:${POST_ID}:voc_customer`, + property_code: "hasVoiceAssignment", + property_label: "Voice carried by this post", + target_node_id: assignment.voice_type_code, + target_label: assignment.voice_type_label, + target_type_code: "node_voice_type", + evidence_post_id: undefined, + }; + + const csv = neighborhoodCsv({ + ...source, + voice_assignments: [assignment], + exact_value_rows: [voiceRow], + }); + + expect(csv.split("\n")[1].split(",").slice(-3)).toEqual([ + "", + POST_ID, + EVIDENCE_POST_ID, + ]); + }); + it("merges JSON-LD properties and multi-value relations for one paged subject", () => { const source = payload(); const postIri = `${ONTOLOGY_NAMESPACE}node/node_post/${POST_ID}`; diff --git a/frontend/src/ontologyLayout.ts b/frontend/src/ontologyLayout.ts index dc49de443..f7092168f 100644 --- a/frontend/src/ontologyLayout.ts +++ b/frontend/src/ontologyLayout.ts @@ -138,6 +138,14 @@ export function layoutOntologyNeighborhood(payload: OntologyNeighborhoodPayload) } export function neighborhoodCsv(payload: OntologyNeighborhoodPayload): string { + const derivedVoiceAssignments = new Map( + (payload.voice_assignments ?? []) + .filter((assignment) => !assignment.is_primary) + .map((assignment) => [ + `${assignment.post_id}\u0000${assignment.voice_type_code}`, + assignment.evidence_post_id, + ]), + ); const header = [ "edge_id", "source_label", @@ -147,12 +155,27 @@ export function neighborhoodCsv(payload: OntologyNeighborhoodPayload): string { "recorded_at", "ontology_property_iri", "evidence_post_id", + "carrying_post_id", + "derivation_evidence_post_id", ]; const lines = [header.join(",")]; for (const row of payload.exact_value_rows) { lines.push( header - .map((key) => csvCell(String(row[key as keyof typeof row] ?? ""))) + .map((key) => { + if (key === "carrying_post_id") { + return csvCell(row.property_code === "hasVoiceAssignment" ? row.source_node_id : ""); + } + if (key === "derivation_evidence_post_id") { + const assignmentKey = `${row.source_node_id}\u0000${row.target_node_id}`; + return csvCell( + row.property_code === "hasVoiceAssignment" && derivedVoiceAssignments.has(assignmentKey) + ? derivedVoiceAssignments.get(assignmentKey) ?? "" + : "", + ); + } + return csvCell(String(row[key as keyof typeof row] ?? "")); + }) .join(","), ); } diff --git a/lineageweave/ontology_neighborhood.py b/lineageweave/ontology_neighborhood.py index 991b81f10..e67269343 100644 --- a/lineageweave/ontology_neighborhood.py +++ b/lineageweave/ontology_neighborhood.py @@ -412,13 +412,12 @@ def jsonld_document(self) -> dict[str, object]: } ) for assignment in self.voice_assignments: - post_iri = ontology_node_iri(NODE_POST, assignment.post_id) assignment_iri = _voice_assignment_iri(assignment) evidence_post_id = assignment.evidence_post_id evidence_iri = ( ontology_node_iri(NODE_POST, evidence_post_id) if evidence_post_id is not None - else post_iri if assignment.is_primary else None + else None ) provenance = ( { @@ -432,6 +431,9 @@ def jsonld_document(self) -> dict[str, object]: "@id": assignment_iri, "@type": str(LW.VoiceAssignment), str(LW.assignedVoiceType): {"@id": assignment.voice_type_iri}, + str(LW.voiceAssignmentCarryingPost): { + "@id": ontology_node_iri(NODE_POST, assignment.post_id) + }, str(LW.primaryVoiceAssignment): { "@value": assignment.is_primary, "@type": "xsd:boolean", diff --git a/tests/test_ontology_neighborhood.py b/tests/test_ontology_neighborhood.py index a9421b428..dce3add0a 100644 --- a/tests/test_ontology_neighborhood.py +++ b/tests/test_ontology_neighborhood.py @@ -934,6 +934,9 @@ def test_voice_assignments_join_exact_csv_rows_and_jsonld() -> None: ) assert post_projection[str(LW.hasVoiceAssignment)] == [{"@id": assignment_iri}] assert projected[str(LW.assignedVoiceType)] == {"@id": str(LW.voiceOfProcessType)} + assert projected[str(LW.voiceAssignmentCarryingPost)] == { + "@id": ontology_node_iri(NODE_POST, POST_ID) + } assert projected[str(LW.voiceAssignmentEvidence)] == { "@id": ontology_node_iri(NODE_POST, POST_ID) } @@ -956,10 +959,73 @@ def test_voice_assignments_join_exact_csv_rows_and_jsonld() -> None: assert str(LW.voiceAssignmentEvidence) not in hidden_projection assert "prov:wasDerivedFrom" not in hidden_projection + imported_primary = replace(assignment, is_primary=True, evidence_post_id=None) + primary_projection = next( + item + for item in replace(neighborhood, voice_assignments=(imported_primary,)) + .jsonld_document()["@graph"] + if item.get("@id") == assignment_iri + ) + assert str(LW.voiceAssignmentEvidence) not in primary_projection + assert "prov:wasDerivedFrom" not in primary_projection + assert primary_projection[str(LW.voiceAssignmentCarryingPost)] == { + "@id": ontology_node_iri(NODE_POST, POST_ID) + } + with pytest.raises(OntologyNeighborhoodError, match="offset-aware"): replace(assignment, recorded_at=T0.replace(tzinfo=None)) +def test_jsonld_keeps_every_voice_for_one_post_in_one_subject_projection() -> None: + """Multiple Voice relations for one paged subject cannot overwrite each other.""" + neighborhood = assemble_ontology_neighborhood( + focus_node_type_code=NODE_POST, + focus_node_id=POST_ID, + facts=[], + labels=_labels(), + ) + primary = OntologyVoiceAssignment( + post_id=POST_ID, + voice_type_code="voc", + voice_type_iri=str(LW.voiceOfCustomerType), + voice_type_label="Voice of Customer", + is_primary=True, + truth_status_code=TRUTH_OBSERVED, + recorded_at=T0, + effective_from=T0, + provenance_reference="Imported primary voice", + evidence_post_id=None, + ) + additional = replace( + primary, + voice_type_code="vops", + voice_type_iri=str(LW.voiceOfProcessType), + voice_type_label="Voice of Process", + is_primary=False, + provenance_reference="Evidence-backed additional voice", + evidence_post_id=POST_ID, + ) + + graph = replace( + neighborhood, voice_assignments=(primary, additional) + ).jsonld_document()["@graph"] + post_iri = ontology_node_iri(NODE_POST, POST_ID) + post_voice_projection = next( + item + for item in graph + if item.get("@id") == post_iri and str(LW.hasVoiceAssignment) in item + ) + + assert post_voice_projection[str(LW.hasVoiceAssignment)] == [ + {"@id": str(LW[f"voice-assignment/{POST_ID}/voc"])}, + {"@id": str(LW[f"voice-assignment/{POST_ID}/vops"])}, + ] + assert sum( + item.get("@id") == post_iri and str(LW.hasVoiceAssignment) in item + for item in graph + ) == 1 + + def test_node_bound_truncation_keeps_nearer_hop_over_farther_alphabetically_earlier_type() -> None: """Trim by BFS distance, not by the raw "type:id" key string. diff --git a/tests/test_ontology_shapes.py b/tests/test_ontology_shapes.py index f16a94d8a..0d0f2a2a9 100644 --- a/tests/test_ontology_shapes.py +++ b/tests/test_ontology_shapes.py @@ -77,7 +77,8 @@ def _representative_projection() -> Graph: data.add((voice_assignment, RDF.type, LWn.VoiceAssignment)) data.add((voice_assignment, LWn.assignedVoiceType, LWn.voiceOfCustomerType)) data.add((voice_assignment, LWn.primaryVoiceAssignment, Literal(True))) - data.add((voice_assignment, LWn.voiceAssignmentEvidence, post)) + data.add((voice_assignment, LWn.voiceAssignmentCarryingPost, post)) + data.add((post, LWn.hasVoiceAssignment, voice_assignment)) person = URIRef(LW + "person-okonkwo") data.add((person, RDF.type, LWn.Person)) data.add((person, LWn.personName, Literal("Sam Okonkwo"))) @@ -109,17 +110,60 @@ def _representative_projection() -> Graph: return data -def test_voice_assignment_requires_source_evidence() -> None: - """A projected Voice assignment without its authorized source post fails closed.""" +def test_voice_assignment_requires_carrying_post() -> None: + """A projected Voice assignment without its authorized carrying Post fails closed.""" data = _representative_projection() LWn = Namespace(LW) assignment = URIRef(LW + "voice-assignment/post-alpha/voc") - data.remove((assignment, LWn.voiceAssignmentEvidence, None)) + data.remove((assignment, LWn.voiceAssignmentCarryingPost, None)) conforms, report = _conforms(data) assert conforms is False - assert "voice assignment evidence" in report.lower() + assert "voice assignment carrying post" in report.lower() + + +def test_voice_assignment_rejects_mismatched_carrying_post() -> None: + """The carrying Post and inverse assignment link must identify one pair.""" + data = _representative_projection() + LWn = Namespace(LW) + assignment = URIRef(LW + "voice-assignment/post-alpha/voc") + other_post = URIRef(LW + "post-beta") + data.add((other_post, RDF.type, LWn.Post)) + data.set((assignment, LWn.voiceAssignmentCarryingPost, other_post)) + + conforms, report = _conforms(data) + + assert conforms is False + assert "carrying post must link" in report.lower() + + +def test_voice_assignment_rejects_an_extra_inverse_post_link() -> None: + """No second Post may claim an assignment carried by another Post.""" + data = _representative_projection() + LWn = Namespace(LW) + assignment = URIRef(LW + "voice-assignment/post-alpha/voc") + other_post = URIRef(LW + "post-beta") + data.add((other_post, RDF.type, LWn.Post)) + data.add((other_post, LWn.hasVoiceAssignment, assignment)) + + conforms, report = _conforms(data) + + assert conforms is False + assert "carrying post must link" in report.lower() + + +def test_additional_voice_assignment_requires_derivation_evidence() -> None: + """Only an additional Voice must retain a distinct derivation relation.""" + data = _representative_projection() + LWn = Namespace(LW) + assignment = URIRef(LW + "voice-assignment/post-alpha/voc") + data.set((assignment, LWn.primaryVoiceAssignment, Literal(False))) + + conforms, report = _conforms(data) + + assert conforms is False + assert "require derivation evidence" in report.lower() def test_shipped_shapes_conform_to_shacl_specification() -> None: diff --git a/tests/test_voice_authority_contract.py b/tests/test_voice_authority_contract.py new file mode 100644 index 000000000..960d75798 --- /dev/null +++ b/tests/test_voice_authority_contract.py @@ -0,0 +1,24 @@ +"""Voice composition supporting documents must name their governing ADRs.""" + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_voice_combination_documents_reference_adr_0256_not_0251() -> None: + """Keep Voice composition distinct from ADR 0251's I/O-Psychology layer.""" + requirements = (ROOT / "docs/voice-combination-technical-requirements.md").read_text() + history = (ROOT / "docs/adr/0252-temporal-primary-voice-history.md").read_text() + adr_index = (ROOT / "docs/adr/README.md").read_text() + storybook_index = (ROOT / "docs/storybook-inventory.md").read_text() + + assert "projects ADR 0246, ADR 0256, and ADR 0252" in requirements + assert "Extends ADR 0256" in history + assert "ADR 0256 records when a Voice assignment starts" in history + assert "[0256](0256-evidence-bearing-voice-combinations.md)" in adr_index + assert "[0251](0256-evidence-bearing-voice-combinations.md)" not in adr_index + ontology_row = next( + line for line in storybook_index.splitlines() if "`Evidence/OntologyExplorer`" in line + ) + assert "ADR 0184/0222/0256 states" in ontology_row