Skip to content
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
25beeeb
feat(persistence): retention deletion legal-hold SQL contracts (0007)
seonghobae Aug 14, 2026
1ae354f
fix(persistence): restack retention 0007 live proof onto hang-safe co…
seonghobae Aug 14, 2026
6bffdc8
fix(persistence): split retention live proofs under clippy line limits
seonghobae Aug 14, 2026
408d5f2
ci: re-trigger exact-head checks for PR #45 after thrash cancel
seonghobae Aug 14, 2026
211f26a
fix(persistence): close retention and restore coverage gaps for 0007
seonghobae Aug 14, 2026
ade0856
fix(persistence): legal-hold release path, indexes, and hold/deletion…
seonghobae Aug 14, 2026
4e7c7ac
chore(ci): repair PR 45 review and exact-head failures
seonghobae Aug 14, 2026
24230e1
chore(ci): stage PR 45 regression proof
seonghobae Aug 14, 2026
96b191c
test(persistence): expose tenant binding RED contract
seonghobae Aug 14, 2026
480e3d5
chore(ci): stage PR 45 implementation repair
seonghobae Aug 14, 2026
e216704
chore(ci): retry PR 45 repair with executable RED proof
seonghobae Aug 14, 2026
31adbca
fix(ci): bind audit events with their tenant parameter
seonghobae Aug 14, 2026
b98165f
chore(ci): repair PR 45 audit tenant binding variable
seonghobae Aug 14, 2026
cd3b24a
fix(ci): make PR 45 repair parameter-aware
seonghobae Aug 14, 2026
eff3c63
fix(ci): harden and consolidate PR 45 repair
seonghobae Aug 14, 2026
12459ba
fix(ci): avoid clone-on-copy in PR 45 regressions
seonghobae Aug 14, 2026
f65d04e
test(persistence): specify lifecycle lock contract
seonghobae Aug 14, 2026
f247d89
fix(ci): use valid PostgreSQL advisory-lock signature
seonghobae Aug 14, 2026
44b88e3
fix(persistence): stage valid lifecycle advisory locks
seonghobae Aug 14, 2026
f2df6c9
test(persistence): lock legal-hold races with a supported signature
seonghobae Aug 14, 2026
f96abd8
chore(ci): verify lifecycle lock repair in PR 45
seonghobae Aug 14, 2026
bc22663
chore(ci): remove superseded PR 45 repair workflow
seonghobae Aug 14, 2026
e54bcbd
chore(ci): remove narrow PR 45 repair workflow
seonghobae Aug 14, 2026
db1ae07
fix(ci): leave lifecycle lock repair to symmetric lock script
seonghobae Aug 14, 2026
9265531
test(persistence): use symmetric lifecycle lock regression
seonghobae Aug 14, 2026
839990a
ci(persistence): print exact uncovered repair lines
seonghobae Aug 14, 2026
271b235
ci(persistence): cancel superseded repair heads
seonghobae Aug 14, 2026
9a8a84e
test(persistence): pin lifecycle migration statement ordering
seonghobae Aug 14, 2026
6ce02ec
fix(persistence): drop lifecycle triggers before routines
seonghobae Aug 14, 2026
6eeb931
fix(ci): exclude live SQLx adapter from repair coverage
seonghobae Aug 14, 2026
a68de5c
test(persistence): require lifecycle vocabulary and tombstone guards
seonghobae Aug 14, 2026
4791ab7
fix(persistence): enforce lifecycle schema and request integrity
seonghobae Aug 14, 2026
95ad4d7
fix(test): mark PostgreSQL in migration contract docs
seonghobae Aug 14, 2026
ac90abd
fix(persistence): close PR #45 lifecycle migration and tenant binds
seonghobae Aug 15, 2026
ee391bc
test(persistence): cover retention windows and artifact assert ref path
seonghobae Aug 15, 2026
b100089
test(persistence): cover active hold with closed system_to branch
seonghobae Aug 15, 2026
95ec979
test(persistence): cover retention lifecycle window failures
seonghobae Aug 15, 2026
190a08f
style(persistence): rustfmt retention lifecycle window contract tests
seonghobae Aug 15, 2026
293b1f5
test(persistence): assert active-analysis select excludes tombstones
seonghobae Aug 16, 2026
8e82469
ci: re-trigger exact-head checks for PR #45 after OpenCode thrash
seonghobae Aug 16, 2026
ffb2410
fix(persistence): bind deletion requests and kind-align analysis excl…
cursoragent Aug 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang

### Added

- `persistence_postgres` retention/deletion/legal-hold (migration `0007`): policy rows, legal holds that block completed deletion, evidence tombstones without raw-source restore, and analysis exclusion for revoked/tombstoned documents.
- `persistence_postgres` backup/restore integrity: restored snapshots stay unusable until tenant, canonical `SHA-256`, knowledge-cutoff eligibility, temporal window order, and append-only triggers revalidate; SQL probes raise `restore integrity failed` (ADR 0013).
- `persistence_postgres` concurrent document-write stress: atomic revise `DO` block that requires exactly one open `system_to` close, SQLSTATE mapping onto `ConcurrentWriteConflict` / `DuplicateDocumentRecord`, and live multi-session insert/revise/append-only proofs. No new migration number.
- `tepp_api` naruon HTTP interchange: versioned `https` POST contracts for analysis-run create and modular export authorization that refuse table-access URLs, review/Copilot credential headers, reserved standard-header redefinition, principal-only export idempotency keys, and lexical inference claims (ADR 0011).
Expand Down
1 change: 1 addition & 0 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ TEPP's approved PRD v0.4 and implementation plan are the primary product baselin
| Hourly NIM product-development operations | [`docs/operations/HOURLY_NIM_PRODUCT_DEVELOPMENT.md`](docs/operations/HOURLY_NIM_PRODUCT_DEVELOPMENT.md) |
| Actions workflow fleet audit | [`docs/operations/ACTIONS_WORKFLOW_FLEET.md`](docs/operations/ACTIONS_WORKFLOW_FLEET.md) |
| Actions fleet research doctoring | [`docs/research/actions-workflow-fleet.md`](docs/research/actions-workflow-fleet.md) |
| Retention/deletion/legal-hold doctoring | [`docs/research/retention-deletion-legal-hold.md`](docs/research/retention-deletion-legal-hold.md) |
| Hourly NIM OpenCode doctoring | [`docs/doctoring/hourly-nim-opencode-development.md`](docs/doctoring/hourly-nim-opencode-development.md) |
| Change history | [`CHANGELOG.md`](CHANGELOG.md) |

Expand Down
4 changes: 4 additions & 0 deletions crates/persistence_postgres/src/artifact_sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ mod tests {
with_ref.source_size_bytes = 0;
let referenced = insert_source_artifact_sql(&with_ref).expect("ref");
assert!(referenced.contains("s3://tepp/object"));
let referenced_assertion =
assert_source_artifact_matches_sql(&with_ref).expect("referenced assertion");
assert!(referenced_assertion.contains("s3://tepp/object"));
assert!(referenced_assertion.contains("IS NOT DISTINCT FROM 's3://tepp/object'"));

assert_eq!(
insert_source_artifact_sql(&SourceArtifactRecord {
Expand Down
25 changes: 10 additions & 15 deletions crates/persistence_postgres/src/document_sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,21 +68,16 @@ pub fn revise_document_sqls(record: &DocumentRecord) -> Result<[String; 2], Pers
pub fn revise_document_atomic_sql(record: &DocumentRecord) -> Result<String, PersistenceError> {
let insert = insert_document_sql(record)?;
Ok(format!(
"DO $tepp$ \
DECLARE closed_count integer; \
BEGIN \
PERFORM 1 FROM document_record \
WHERE document_record_id = '{document_id}'::uuid AND system_to IS NULL \
FOR UPDATE NOWAIT; \
UPDATE document_record SET system_to = '{system_from}'::timestamptz \
WHERE document_record_id = '{document_id}'::uuid AND system_to IS NULL; \
GET DIAGNOSTICS closed_count = ROW_COUNT; \
IF closed_count <> 1 THEN \
RAISE EXCEPTION 'concurrent document revision conflict' \
USING ERRCODE = 'serialization_failure'; \
END IF; \
{insert}; \
END $tepp$",
"DO $tepp$ DECLARE closed_count integer; BEGIN \
PERFORM 1 FROM document_record WHERE document_record_id = '{document_id}'::uuid \
AND system_to IS NULL FOR UPDATE NOWAIT; \
UPDATE document_record SET system_to = '{system_from}'::timestamptz \
WHERE document_record_id = '{document_id}'::uuid AND system_to IS NULL; \
GET DIAGNOSTICS closed_count = ROW_COUNT; \
IF closed_count <> 1 THEN \
RAISE EXCEPTION 'concurrent document revision conflict' \
USING ERRCODE = 'serialization_failure'; END IF; \
{insert}; END $tepp$",
system_from = record.system_from.to_rfc3339(),
document_id = record.document_record_id,
))
Expand Down
28 changes: 28 additions & 0 deletions crates/persistence_postgres/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ pub enum PersistenceError {
ConcurrentWriteConflict,
/// A restored snapshot failed integrity revalidation and is not usable.
RestoreIntegrityFailed,
/// A retention, hold, deletion, or tombstone record failed closed validation.
InvalidRetentionLifecycle,
/// An active legal hold blocked completed deletion.
LegalHoldBlocksDeletion,
/// Tombstoned evidence cannot be restored, and raw-source deletion cannot keep reproduction available.
UngovernedEvidenceRestore,
}

impl fmt::Display for PersistenceError {
Expand All @@ -65,6 +71,9 @@ impl fmt::Display for PersistenceError {
Self::InvalidAuditEvent => "invalid audit event",
Self::ConcurrentWriteConflict => "concurrent write conflict",
Self::RestoreIntegrityFailed => "restore integrity failed",
Self::InvalidRetentionLifecycle => "invalid retention lifecycle",
Self::LegalHoldBlocksDeletion => "legal hold blocks deletion",
Self::UngovernedEvidenceRestore => "ungoverned evidence restore",
};
formatter.write_str(message)
}
Expand Down Expand Up @@ -96,6 +105,8 @@ pub enum MigrationContractError {
MissingAppendOnlyTrigger,
/// Temporal interval ordering checks were incomplete when declared.
MissingTemporalIntervalConstraint,
/// Retention/legal-hold migration declarations were incomplete.
MissingRetentionLegalHold,
}

impl fmt::Display for MigrationContractError {
Expand All @@ -111,6 +122,7 @@ impl fmt::Display for MigrationContractError {
Self::MissingTenantSessionGuc => "missing tenant session guc",
Self::MissingAppendOnlyTrigger => "missing append-only immutability trigger",
Self::MissingTemporalIntervalConstraint => "missing temporal interval constraint",
Self::MissingRetentionLegalHold => "missing retention legal hold",
};
formatter.write_str(message)
}
Expand Down Expand Up @@ -197,6 +209,18 @@ mod tests {
PersistenceError::RestoreIntegrityFailed.to_string(),
"restore integrity failed"
);
assert_eq!(
PersistenceError::InvalidRetentionLifecycle.to_string(),
"invalid retention lifecycle"
);
assert_eq!(
PersistenceError::LegalHoldBlocksDeletion.to_string(),
"legal hold blocks deletion"
);
assert_eq!(
PersistenceError::UngovernedEvidenceRestore.to_string(),
"ungoverned evidence restore"
);
assert_eq!(
MigrationContractError::SingleWordObjectName.to_string(),
"single-word database object name"
Expand Down Expand Up @@ -237,5 +261,9 @@ mod tests {
MigrationContractError::MissingTemporalIntervalConstraint.to_string(),
"missing temporal interval constraint"
);
assert_eq!(
MigrationContractError::MissingRetentionLegalHold.to_string(),
"missing retention legal hold"
);
}
}
29 changes: 28 additions & 1 deletion crates/persistence_postgres/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
//! exactly one open row to close, and live `SQLx` maps racing SQLSTATEs onto
//! typed conflict errors. Restore integrity probes refuse to mark analytical
//! state usable until tenant, digest, cutoff, temporal windows, and append-only
//! triggers revalidate.
//! triggers revalidate. Retention, deletion, and legal-hold SQL (migration
//! `0007`) records policy-driven lifecycle without restoring tombstoned
//! evidence or completing a deletion under an active hold.

mod artifact_sql;
mod concurrent_write;
Expand All @@ -38,6 +40,7 @@ mod model_run_sql;
mod naming;
mod relation_sql;
mod restore_integrity;
mod retention_sql;
mod sql_session;
mod sqlx_gate;
#[cfg(feature = "live-sqlx")]
Expand Down Expand Up @@ -164,6 +167,30 @@ pub use restore_integrity::backup_scope_tables;
pub use restore_integrity::mark_restored_state_usable;
/// SQL probes that fail closed on unusable restored rows.
pub use restore_integrity::restore_integrity_probe_sqls;
/// Auditable deletion request row.
pub use retention_sql::DeletionRequestRecord;
/// Append-only evidence tombstone row.
pub use retention_sql::EvidenceTombstoneRecord;
/// Legal or contractual hold row.
pub use retention_sql::LegalHoldRecord;
/// Tenant-scoped retention policy row.
pub use retention_sql::RetentionPolicyRecord;
/// Render insert SQL for a completed deletion after hold evaluation.
pub use retention_sql::insert_completed_deletion_request_sql;
/// Render insert SQL for a validated deletion request.
pub use retention_sql::insert_deletion_request_sql;
/// Render insert SQL for a validated evidence tombstone.
pub use retention_sql::insert_evidence_tombstone_sql;
/// Render insert SQL for a validated legal hold.
pub use retention_sql::insert_legal_hold_sql;
/// Render insert SQL for a validated retention policy.
pub use retention_sql::insert_retention_policy_sql;
/// Render SQL that releases one active legal hold.
pub use retention_sql::release_legal_hold_sql;
/// Render active-analysis selection that excludes revoked or tombstoned documents.
pub use retention_sql::select_active_analysis_document_sql;
/// Render supersede SQL for a successive retention policy.
pub use retention_sql::supersede_retention_policy_sql;
/// Recording SQL transport for offline contract tests.
pub use sql_session::RecordingSqlSession;
/// Live SQL transport contract.
Expand Down
Loading
Loading