fix: fence Journal originals before publication and reconcile removal intent

Protect attributed originals before the first public commit; distinguish pending Session material from missing committed sources. Gate removal admission by the Journal Feature and resolve Subject ownership from durable singleton authority. Resume explicit erasure through signed delete transport and proof-bound purge plans without creating a Journal Job or archive.

Validation: changed-crate suites, root check, doc/fmt/diff/naming checks, Server/Runtime builds and worker_environment isolation suite passed. Public standalone scripted dialog connects Host, automatic Job, sequential tools and drain while dialog continues. Backend public embedded WorkerRemove tests connect removal forwarding, durable Jobs, retention, Runtime and SQLite; Session/attribution/profile fixture setup bypasses creation/auth routes. Runtime signed generated-client tests separately exercise request authentication.

Limits: real-model semantic acceptance condition 6 remains unevaluated without a new explicit model/credential/budget grant. Conditions 1/7/8/11 do not have complete product E2E coverage: worker_environment only verifies isolated startup/connection/cleanup. Compaction tests connect local hooks/reader/store rather than the full public dialog-to-Journal chain. Erasure racing a previously prepared Archive removal plan fails closed with conflict; automatic stale-plan replacement is not implemented.
This commit is contained in:
2026-10-16 03:59:45 +09:00
parent 9debf43645
commit 5bd49760e5
19 changed files with 1846 additions and 149 deletions
+36
View File
@@ -803,6 +803,20 @@ pub struct SessionMaterialDescriptor {
pub total_bytes: u64,
}
/// Explicit Host erasure, distinct from ordinary retention policy or pin release.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SessionMaterialEraseRequest {
pub workspace_id: String,
pub source: SessionPublicSource,
pub expected_session_id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SessionMaterialEraseResponse {
pub session_id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SessionMaterialPinRequest {
@@ -860,6 +874,11 @@ pub struct SessionMaterialReadPage {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "availability", rename_all = "snake_case")]
pub enum SessionMaterialCatalogAvailability {
/// The saved active pointer is pending and storage proves no Session material
/// has ever been published. Missing/corrupt materialized sources are not empty.
NotMaterialized {
session_id: String,
},
Page {
page: SessionMaterialCatalogPage,
},
@@ -1123,6 +1142,16 @@ pub trait RuntimeApi {
#[query] request: WorkerSessionHistoryRequest,
) -> Result<WorkerSessionHistoryAvailability, RuntimeApiError>;
#[post(
"/v1/session-public/material/retention/erase",
status = 200,
error_status = 400
)]
async fn session_material_erase(
&self,
#[body] request: SessionMaterialEraseRequest,
) -> Result<SessionMaterialEraseResponse, RuntimeApiError>;
#[post(
"/v1/session-public/material/retention/pin",
status = 200,
@@ -1588,6 +1617,13 @@ mod tests {
})
}
async fn session_material_erase(
&self,
_request: SessionMaterialEraseRequest,
) -> Result<SessionMaterialEraseResponse, RuntimeApiError> {
Err(test_error(501))
}
async fn session_material_pin(
&self,
_request: SessionMaterialPinRequest,