Skip to content

feat(persistence): persist exact text_segment byte spans without 0007 - #99

Merged
seonghobae merged 5 commits into
mainfrom
cursor/bc-709aa359-b025-4fbc-9a53-304c83501192-1885
Aug 24, 2026
Merged

feat(persistence): persist exact text_segment byte spans without 0007#99
seonghobae merged 5 commits into
mainfrom
cursor/bc-709aa359-b025-4fbc-9a53-304c83501192-1885

Conversation

@cursor

@cursor cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • #45 owns migration 0007 and remains the preferred merge; this increment does not allocate 0008.
  • Adds typed text_segment insert/lookup on the existing 0006 table so membership can attach to an exact UTF-8 span instead of raw SQL.
  • Half-open [start_byte, end_byte) recovers the known hello span from hello world. Inverted, empty, and negative spans fail closed before SQL is rendered.
  • Cutoff-eligible document reads keep a later-available world span out of a February historical fit (ADR 0008 / ADR 0013).

Claim boundary

  • SQL adapter for the physical 0006 columns only. Not a document_record foreign key, Unicode scalar columns, segment_type_code, or CSAP/SOC 2 claim.
  • Does not change tepp_api, service_tls, or naruon/orchestrator listeners (#87 / #90 / #92).

Test plan

  • RED: text_segment_sql_contract failed with E0432 / missing InvalidTextSegment
  • cargo test -p persistence_postgres --lib --tests
  • cargo clippy -p persistence_postgres --all-targets -- -D warnings
  • cargo test -p persistence_postgres --features live-sqlx --test live_postgres --no-run
  • python3 scripts/check_workspace_contract.py, check_docstrings.py, validate_documentation.py
  • Exact-head CI; independent review
Open in Web View Automation 

Add typed insert/lookup for the existing 0006 text_segment table so
membership can attach to a recovered UTF-8 span instead of raw SQL.
Cutoff-eligible document reads keep later-available spans out of a
historical fit. No new migration number.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
# Conflicts:
#	CHANGELOG.md
#	crates/persistence_postgres/src/error.rs
#	crates/persistence_postgres/src/lib.rs
#	crates/persistence_postgres/src/live_repository.rs
#	crates/persistence_postgres/tests/live_postgres.rs
@seonghobae
seonghobae marked this pull request as ready for review August 20, 2026 19:13
@seonghobae

Copy link
Copy Markdown
Contributor

Current HEAD 93d3678c378f092d2899b30e5b1622399819578a includes origin/main and preserves the text-segment and retention/legal-hold paths. The historical persistence_postgres clippy::too_many_lines failure is addressed at the live integration test boundary. Verified at this exact HEAD: cargo fmt --all -- --check, cargo test --workspace --offline, cargo clippy --workspace --all-targets --all-features --offline -- -D warnings, quality tests (89), workspace contract, documentation validation, Rust docstrings, and git diff --check. Please provide the required independent approval; no protected-main bypass was used.

devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae

Copy link
Copy Markdown
Contributor

Current-head RLS fix (5dd50ff): insert_text_segment now binds record.tenant_record_id before rendering/executing the INSERT, matching all sibling tenant-scoped writes. The recording contract asserts the tenant GUC precedes the insert. Local persistence_postgres tests (51), fmt, clippy, and live-sqlx no-run compilation pass. Please re-review this exact head.

@seonghobae

Copy link
Copy Markdown
Contributor

Current-head review refresh for 5dd50ff:

  • Exact text_segment byte spans are persisted through the typed SQL boundary without the retired 0007 contract, and the database-facing tests preserve provenance and membership semantics.
  • git diff --check, documentation validation, workspace contract, docstring contract, and cargo fmt --all -- --check passed locally.
  • Current hosted checks are pass=13, pending=1, fail=0; coverage-evidence is still pending, so merge remains withheld until the exact-head required check completes and qualifying approvals exist.
  • No actionable source defect was found at this exact head; no approval is being self-issued.

@seonghobae

Copy link
Copy Markdown
Contributor

Rebased current head c4ed064 onto origin/main. The changelog and hourly queue conflict were resolved by retaining the text-segment product gap contract and current-main invariants; inherited documentation trailing whitespace was removed. Local merge-tree, git diff --cached --check, and cargo fmt --all -- --check pass. Exact-head hosted checks and required independent approvals remain required before protected merge.

@seonghobae
seonghobae enabled auto-merge (squash) August 24, 2026 00:58
…25-4fbc-9a53-304c83501192-1885

# Conflicts:
#	CHANGELOG.md
#	docs/TRACEABILITY.md
#	docs/validation/temporal-event-foundation.md
@seonghobae
seonghobae merged commit 112aa5d into main Aug 24, 2026
24 of 26 checks passed

@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.

Open in Devin Review

| Event mention/instance | `event_core` | partial | — | unit + fail-closed promotion | Task 5 / PR #13 |
| Multiple membership | `membership_core` | partial | — | unit + ESS weights | Task 7 / PR #12 + #25 |
| Forward transition DAG | `relation_graph` | implemented-main | — | unit + cycle rejection | Task 6 / PR #14 |
| Bitemporal persistence + live SQL port | `persistence_postgres` | partial | typed `text_segment` SQL | migration contracts + recording transport + optional PgPool + live CI + tenant RLS + `0005`/`0006` + event relation/mention/instance + source-artifact + audit-event + concurrent-write + restore integrity (#37–#44 implemented-main) + typed `text_segment` insert/cutoff lookup (active PR) | Task 8 / PR #16 + #23 + #26 + #27 + #29 + #30–#44 + text-segment SQL |

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 persistence row in capability ledger

The capability table now lists Bitemporal persistence + live SQL port twice: the added row carries Open PR status typed text_segment SQL, while the prior row still reads . The intended update to the single existing row instead left a stale, contradictory duplicate.

Prompt for agents
The capability ledger in docs/validation/temporal-event-foundation.md now contains two rows for the same capability, Bitemporal persistence + live SQL port (the newly added row at line 21 and the pre-existing row at line 22). The new row updates the Open PR status column to typed text_segment SQL, but the old row with an em dash was not removed. Consolidate these into a single row so the capability appears exactly once, keeping the updated Open PR status and evidence text.
Open in Devin Review

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

Comment on lines +38 to +43
pub fn validate(&self) -> Result<(), PersistenceError> {
if self.start_byte < 0 || self.end_byte <= self.start_byte {
return Err(PersistenceError::InvalidTextSegment);
}
Ok(())
}

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: Adapter validation duplicates but does not weaken the DB CHECK constraint

TextSegmentRecord::validate (segment_sql.rs) enforces start_byte >= 0 && end_byte > start_byte, which exactly mirrors the physical text_segment_byte_span CHECK (start_byte >= 0 AND end_byte > start_byte) in migrations/0006_typed_membership_assignment.up.sql. The half-open span semantics and fail-closed behavior are consistent between the adapter and the database, so there is no divergence risk here.

Open in Devin Review

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

Comment on lines +345 to +365
/// Returns transport failures.
pub fn submit_text_segment_by_id(
&mut self,
text_segment_id: Uuid,
) -> Result<(), PersistenceError> {
let sql = select_text_segment_by_id_sql(text_segment_id);
self.session.execute(&sql)
}

/// Look up cutoff-eligible text segments for one document identity.
///
/// # Errors
///
/// Returns transport failures.
pub fn submit_text_segments_for_document_as_of(
&mut self,
document_record_id: Uuid,
knowledge_cutoff: &KnowledgeCutoff,
) -> Result<(), PersistenceError> {
let sql = select_text_segments_for_document_as_of_sql(document_record_id, knowledge_cutoff);
self.session.execute(&sql)

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: Text-segment lookups also skip tenant binding, but this matches sibling read methods

submit_text_segment_by_id (live_repository.rs) and submit_text_segments_for_document_as_of (:358-365) do not bind the session tenant GUC. Under FORCE RLS a read with an unset GUC fails closed (returns zero rows), so a standalone lookup would silently return nothing. However this matches the established pattern for other read methods (submit_membership_assignments_for_document at :290, submit_source_artifact_by_id at :376, submit_active_analysis_document at :486), which also rely on a previously bound GUC. So it is not a new inconsistency introduced by this PR, only the insert path is anomalous.

Open in Devin Review

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

Comment on lines +7 to +12
/// One append-only exact-span observation on a document.
///
/// Maps to physical `text_segment` from migration `0006`. Byte offsets are
/// half-open `[start_byte, end_byte)` over the document UTF-8 bytes.
/// `document_record_id` is required; a foreign key remains a later migration
/// (`#45` owns `0007`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 text_segment lacks an append-only mutation trigger

text_segment is created in migration 0006 and granted only SELECT, INSERT to tepp_app_runtime, but unlike the append-only identity tables in migration 0004 it has no reject_append_only_mutation statement-level trigger. The PR only adds a SQL adapter and does not touch migrations, so this is out of scope, but if segment spans are intended to be append-only observations (as the doc comment implies: "One append-only exact-span observation"), the absence of a mutation-rejecting trigger means a superuser or future grant change could silently update/delete spans. Worth confirming against the accepted ERD append-only intent.

Open in Devin Review

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

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.

2 participants