Skip to content

feat(ontology): preserve temporal imported primary Voice intervals (ADR 0252) - #752

Closed
seonghobae wants to merge 61 commits into
mainfrom
feat/voice-primary-history
Closed

feat(ontology): preserve temporal imported primary Voice intervals (ADR 0252)#752
seonghobae wants to merge 61 commits into
mainfrom
feat/voice-primary-history

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Retain non-overlapping half-open effective intervals (effective_from ~ effective_to) in source_post_voice under ADR 0252.
  • Preserve recurring A → B → A imported-primary changes across source updates without deleting former states.
  • Reject overlapping primary intervals at the database boundary via a GiST exclusion constraint.
  • Align live reads (effective_to is null), knowledge-cutoff reads (effective_from <= cutoff < effective_to), and ontology-snapshot reads with TRD and PRD contracts.
  • Update PRD, TRD (docs/voice-combination-technical-requirements.md), UML models, ADR 0252, and product gap baseline.

Verification

  • pytest tests/test_source_post_voice* tests/test_ontology*: 142 passed.
  • test_public_docstrings.py: 100% passed.
  • backend/tests/test_api.py schema integration tests passed.
  • Frontend pnpm run lint && pnpm run test && pnpm run build: all passed.

Stack

Depends on #717 (feat/voice-of-x-combinations). Retarget after parent merges.


Devin Review

seonghobae and others added 30 commits August 26, 2026 22:43
Expand voc_type from five to twelve post types (Supplier, Employee,
Business, Regulator, Investor, Society, Process added) grounded in
stakeholder identification theory and quality-engineering practice, and
mirror six new counterparty relationship codes so any post class can
type each named organization. Ontology SKOS concepts/object properties,
idempotent migration 0222, classifier vocabulary + prompt, round-trip
and parser tests stay in sync.
@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 50 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7cff47cf-0129-442f-a356-043caa790051

📥 Commits

Reviewing files that changed from the base of the PR and between 96109cf and 564f9df.

📒 Files selected for processing (20)
  • ARCHITECTURE.md
  • CHANGELOG.md
  • backend/app/ontology_neighborhood_ingestion.py
  • backend/tests/test_api.py
  • docs/adr/0245-io-occupational-taxonomy-in-the-published-ontology.md
  • docs/adr/0252-temporal-primary-voice-history.md
  • docs/adr/README.md
  • docs/product-requirements.md
  • docs/product-technical-gap-baseline.md
  • docs/voice-combination-technical-requirements.md
  • lineageweave/ontology_neighborhood.py
  • migrations/0237_source_post_voice_combination.sql
  • migrations/0243_source_post_voice_history.sql
  • tests/test_ontology_neighborhood.py
  • tests/test_ontology_neighborhood_ingestion.py
  • tests/test_post_filter_options.py
  • tests/test_source_post_voice_history_schema.py
  • tests/test_source_post_voice_ingestion.py
  • tests/test_source_post_voice_schema.py
  • tests/test_source_state_serialization.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 6 new potential issues.

Devin Review

Comment thread backend/app/main.py Outdated
Comment thread backend/app/ontology_neighborhood_ingestion.py
Comment thread migrations/0243_source_post_voice_history.sql
Comment thread backend/app/main.py Outdated
Comment thread backend/app/main.py Outdated
Comment on lines +29 to +55
create or replace function synchronize_source_post_primary_voice()
returns trigger
language plpgsql
as $$
declare
change_at timestamptz := clock_timestamp();
begin
update source_post_voice
set effective_to = change_at
where post_id = new.post_id
and effective_to is null
and (is_primary or voice_type_code = new.voc_type_code);

insert into source_post_voice
(post_id, voice_type_code, is_primary, truth_status_code,
effective_from, recorded_at)
values (
new.post_id,
new.voc_type_code,
true,
'truth_observed',
case when tg_op = 'INSERT' then least(new.created_at, change_at) else change_at end,
change_at
);
return new;
end;
$$;

@devin-ai-integration devin-ai-integration Bot Aug 27, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Primary-voice trigger relies on the source row lock for serialization

The rewritten synchronize_source_post_primary_voice drops the explicit for update and on conflict, closing the current primary and any current row for the incoming voice type before a plain insert. Non-overlapping intervals under concurrent A→B→A updates depend on the AFTER UPDATE trigger running while the source_post row is already locked, so clock_timestamp() reads distinct instants per serialized transaction.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@seonghobae
seonghobae changed the base branch from feat/voice-of-x-combinations to main August 27, 2026 10:36
@seonghobae
seonghobae enabled auto-merge (squash) August 27, 2026 15:23
@opencode-agent
opencode-agent Bot disabled auto-merge August 27, 2026 15:23
@seonghobae
seonghobae enabled auto-merge (squash) August 27, 2026 15:26

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 new potential issues.

Devin Review

Comment thread CHANGELOG.md
Comment on lines +12 to +17
- Imported primary Voice changes now retain non-overlapping effective intervals
(ADR 0252), including recurring A → B → A values. Live, knowledge-cutoff,
and ontology-snapshot reads select the same period contract; PostgreSQL
rejects overlapping primary intervals instead of relying on application
ordering.
- Normalized Voice-of-X composition persistence (ADR 0251): every imported

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changelog has a truncated, wrong-ADR duplicate entry

Inserting the new bullet leaves the existing ADR 0256 line truncated at "every imported", and its continuation is reattached to a duplicate bullet now citing ADR 0251. The changelog ends up with two composition-persistence entries carrying conflicting ADR numbers.

Prompt for agents
In CHANGELOG.md the Unreleased/Added section now contains a broken entry. The original single bullet was 'Normalized Voice-of-X composition persistence (ADR 0256): every imported primary voice is mirrored into source_post_voice; ...'. This PR inserted a new ADR 0252 bullet between the first line ('...(ADR 0256): every imported') and its continuation ('primary voice is mirrored...'), and also duplicated the composition-persistence bullet with a different ADR number (0251). This leaves a truncated dangling line ending at 'every imported' plus two contradictory entries. Fix by removing the orphaned/truncated ADR 0256 line so the composition-persistence bullet appears once with the correct ADR number, and keep the new ADR 0252 bullet as its own separate entry.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread docs/adr/README.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](0251-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) |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Duplicate supporting-document row in ADR map

A second lineage-bi-research-notes.md row is added to the supporting-document table, duplicating the existing one. The two rows also disagree: one lists ADR 0238 and the other omits it.

Suggested change
| [`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) |
| [`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) |
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +911 to 916
and voice.effective_from <= coalesce($2::timestamptz, $3::timestamptz)
and (
voice.effective_to is null
or coalesce($2::timestamptz, $3::timestamptz) < voice.effective_to
)
and voice.recorded_at <= $3::timestamptz

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Cutoff-null neighborhood read now keyed to snapshot instant

The rewritten query in _load_voice_assignments replaces the old cutoff-null branch with coalesce(knowledge_cutoff, snapshot_at). This matches ADR 0252: a frozen-snapshot continuation selects the interval containing that snapshot rather than the current open row, while a live read (snapshot=now) still returns the current row. Non-overlapping primary intervals guarantee one row per (post, voice).

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +16 to +27
create unique index if not exists source_post_voice_current_pair_idx
on source_post_voice (post_id, voice_type_code)
where effective_to is null;

alter table source_post_voice
drop constraint if exists source_post_voice_primary_period_excl;
alter table source_post_voice
add constraint source_post_voice_primary_period_excl
exclude using gist (
post_id with =,
tstzrange(effective_from, effective_to, '[)') with &&
) where (is_primary);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Redundant current-pair index and double overlap enforcement

source_post_voice_current_pair_idx duplicates the existing source_post_voice_current_type_idx (identical unique predicate), and primary-interval overlap is now enforced both by the 0237 validate trigger and the new GiST exclusion constraint. Harmless, but worth consolidating.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Closing as superseded by #761. PR #752's net contribution (temporal Imported-primary-Voice history, ADR 0252) was promoted onto current main via a three-way merge that keeps main's newer shared files while grafting the missing migration 0243, the effective_from/effective_to dataclass fields, the neighborhood payload/JSON-LD exports, and the coalesce cutoff predicate. #752's own branch was built on an older Voi-combination base and cannot rebase cleanly; #761 carries the same feature, 1597 tests green.

@seonghobae seonghobae closed this Aug 27, 2026
auto-merge was automatically disabled August 27, 2026 15:38

Pull request was closed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant