fix: reconcile erased Archive removals and exercise live compaction recording
Reconcile immutable Runtime removal input under a confirmed permanent erase fence. A confirmed source_removed receipt is audited without republishing erased archives; unknown receipt plus attested remaining aggregate permits an audited stale plan and proof-bound purge replacement. Unknown receipt and inventory remain unresolved. Distinguish domain tombstones from scoped Runtime physical erasure acknowledgements. Erase diagnostic originals and interrupted staging bound to the same archived Session/removal operation, validating Workspace/Worker/fingerprint and saved receipts without rewriting their historical retained flags. Finalize Backend removal only after physical acknowledgement, including previous Retain disposition. Connect public standalone protocol, real Worker request-threshold compaction twice within one turn, cancellation and real pre-AI input rollback, original canonical readers, existing Jobs and sequential Journal tools. Gate generation while compaction completes; verify all durable originals were served, summary/rolled-back input exclusion, exact final cursor, no extra input for successor drain, and one ongoing open entry. External model streams/usage are scripted, not semantic evaluation. Validation: Backend1112lib+11bin (existing ignore1), Runtime429lib+38bin, standalone72lib+27integration (existing model ignore1), runtime-api10, root check/doc/fmt/diff/naming, Server/Runtime builds, isolated worker_environment2 passed. Earlier broad suites remain recorded on the prior source. Fixtures and injected boundaries are not arbitrary crash/remote-transport-loss coverage; worker_environment only verifies startup/connection/cleanup. Acceptance clarification: T-734 assignment answer event22 explicitly defers real-model semantic condition6 to user use, excluding it from this completion scope. It remains unexecuted and unevaluated, not passed. Technical review findings still require a fresh independent verdict on this source.
This commit is contained in:
@@ -659,8 +659,38 @@ fn catalog_rejects_oversize_record_with_explicit_resource_limit() {
|
||||
fn archive_scope_and_legacy_active_pointer_are_checked_and_erase_retry_never_restores_originals() {
|
||||
let f = Fixture::new();
|
||||
f.stop();
|
||||
let request = f.retention_request(SessionDisposition::Archive);
|
||||
f.runtime.execute_worker_retention(&request).unwrap();
|
||||
let mut request = f.retention_request(SessionDisposition::Archive);
|
||||
request.diagnostics_disposition = DiagnosticsDisposition::Retain;
|
||||
let log = f
|
||||
.root()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("runs/run-original/worker.out.log");
|
||||
std::fs::create_dir_all(log.parent().unwrap()).unwrap();
|
||||
std::fs::write(&log, "archived diagnostic original").unwrap();
|
||||
let original_receipt = f.runtime.execute_worker_retention(&request).unwrap();
|
||||
assert!(original_receipt.diagnostics_retained);
|
||||
let diagnostics = f
|
||||
.temp
|
||||
.path()
|
||||
.join("archives/diagnostics")
|
||||
.join(&request.operation_id);
|
||||
assert!(
|
||||
diagnostics
|
||||
.join("runs/run-original/worker.out.log")
|
||||
.is_file()
|
||||
);
|
||||
let staged_diagnostics =
|
||||
diagnostics.with_file_name(format!(".{}.tmp-42-1", request.operation_id));
|
||||
std::fs::create_dir_all(&staged_diagnostics).unwrap();
|
||||
std::fs::write(
|
||||
staged_diagnostics.join("partial-original.log"),
|
||||
"incomplete copy",
|
||||
)
|
||||
.unwrap();
|
||||
let unrelated = diagnostics.with_file_name("another-operation");
|
||||
std::fs::create_dir_all(&unrelated).unwrap();
|
||||
std::fs::write(unrelated.join("untouched.log"), "unrelated diagnostic").unwrap();
|
||||
let source = Source::Archived {
|
||||
archive_id: "canonical-archive".into(),
|
||||
};
|
||||
@@ -756,12 +786,76 @@ fn archive_scope_and_legacy_active_pointer_are_checked_and_erase_retry_never_res
|
||||
.pin_session_public_source_scoped(&f.scope, &source, &f.session.to_string(), pin)
|
||||
.is_err()
|
||||
);
|
||||
// An old removal receipt is evidence only and must not republish erased bytes.
|
||||
f.runtime.execute_worker_retention(&request).unwrap();
|
||||
// An old receipt is historical evidence, not permission to republish erased bytes.
|
||||
assert_eq!(
|
||||
f.runtime.execute_worker_retention(&request).unwrap(),
|
||||
original_receipt
|
||||
);
|
||||
assert!(!diagnostics.exists());
|
||||
assert!(!staged_diagnostics.exists());
|
||||
assert!(unrelated.join("untouched.log").is_file());
|
||||
assert!(!root.exists());
|
||||
assert!(retention.pins(f.session).unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn erasure_rejects_mismatched_archived_diagnostics_then_resumes_under_the_same_fence() {
|
||||
let f = Fixture::new();
|
||||
f.stop();
|
||||
let mut request = f.retention_request(SessionDisposition::Archive);
|
||||
request.diagnostics_disposition = DiagnosticsDisposition::Retain;
|
||||
let receipt = f.runtime.execute_worker_retention(&request).unwrap();
|
||||
let source = Source::Archived {
|
||||
archive_id: request.archive_id.clone().unwrap(),
|
||||
};
|
||||
let manifest = f
|
||||
.temp
|
||||
.path()
|
||||
.join("archives/diagnostics")
|
||||
.join(&request.operation_id)
|
||||
.join("manifest.json");
|
||||
let original: serde_json::Value =
|
||||
serde_json::from_slice(&std::fs::read(&manifest).unwrap()).unwrap();
|
||||
for field in ["workspace_id", "input_fingerprint", "source_worker_id"] {
|
||||
let mut changed = original.clone();
|
||||
changed[field] = serde_json::json!(uuid::Uuid::now_v7().to_string());
|
||||
std::fs::write(&manifest, serde_json::to_vec(&changed).unwrap()).unwrap();
|
||||
assert!(
|
||||
f.runtime
|
||||
.delete_session_public_source_scoped(
|
||||
&f.scope,
|
||||
&source,
|
||||
&f.session.to_string(),
|
||||
SessionDeletionIntent::ErasePermanently
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert!(manifest.exists());
|
||||
assert!(matches!(
|
||||
runtime_session_retention(f.temp.path()).ensure_live(f.session),
|
||||
Err(session_store::StoreError::SessionErased(_))
|
||||
));
|
||||
}
|
||||
std::fs::write(&manifest, serde_json::to_vec(&original).unwrap()).unwrap();
|
||||
f.runtime
|
||||
.delete_session_public_source_scoped(
|
||||
&f.scope,
|
||||
&source,
|
||||
&f.session.to_string(),
|
||||
SessionDeletionIntent::ErasePermanently,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!manifest.parent().unwrap().exists());
|
||||
assert_eq!(
|
||||
f.runtime.execute_worker_retention(&request).unwrap(),
|
||||
receipt
|
||||
);
|
||||
assert!(
|
||||
receipt.diagnostics_retained,
|
||||
"historical receipt must not be rewritten after erasure"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn material_request_bounds_return_explicit_unavailable_without_previews() {
|
||||
let f = Fixture::new();
|
||||
|
||||
@@ -297,14 +297,15 @@ pub(crate) fn remove_archived_session_copies(
|
||||
}
|
||||
let session_root = entry.path().join("session");
|
||||
let owner = match fs::read(entry.path().join("manifest.json")) {
|
||||
Ok(bytes) => Some(
|
||||
serde_json::from_slice::<WorkerSessionArchiveManifest>(&bytes)?.source_session_id,
|
||||
),
|
||||
Ok(bytes) => Some(serde_json::from_slice::<WorkerSessionArchiveManifest>(
|
||||
&bytes,
|
||||
)?),
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => None,
|
||||
Err(error) => return Err(error.into()),
|
||||
};
|
||||
if let Some(owner) = owner {
|
||||
if owner == session.to_string() {
|
||||
if owner.source_session_id == session.to_string() {
|
||||
erase_archived_diagnostics(runtime_root, &owner)?;
|
||||
match fs::remove_dir_all(&session_root) {
|
||||
Ok(()) => (),
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => (),
|
||||
@@ -326,6 +327,89 @@ pub(crate) fn remove_archived_session_copies(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Erasure includes diagnostic originals of the same archived removal operation.
|
||||
/// The immutable removal receipt is historical evidence and is never rewritten.
|
||||
/// Staging names are bound to that exact operation; unrelated archives are untouched.
|
||||
fn erase_archived_diagnostics(
|
||||
runtime_root: &Path,
|
||||
archive: &WorkerSessionArchiveManifest,
|
||||
) -> Result<(), session_store::StoreError> {
|
||||
let invalid = || {
|
||||
session_store::StoreError::Io(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
"Archived diagnostics erasure identity mismatch",
|
||||
))
|
||||
};
|
||||
if archive.schema_version != ARCHIVE_SCHEMA_VERSION {
|
||||
return Err(invalid());
|
||||
}
|
||||
validate_id("operation_id", &archive.operation_id).map_err(|_| invalid())?;
|
||||
let receipt_path = runtime_root
|
||||
.join("retention/operations")
|
||||
.join(format!("{}.json", archive.operation_id));
|
||||
if let Some(receipt) = read_operation_receipt(&receipt_path).map_err(|_| invalid())? {
|
||||
if receipt.request.workspace_id != archive.workspace_id
|
||||
|| receipt.request.source_runtime_id != archive.source_runtime_id
|
||||
|| receipt.request.worker_id != archive.source_worker_id
|
||||
|| receipt.request.operation_id != archive.operation_id
|
||||
|| receipt.request.input_fingerprint != archive.input_fingerprint
|
||||
|| receipt.request.archive_id.as_ref() != Some(&archive.archive_id)
|
||||
|| receipt.result.archive.as_ref() != Some(archive)
|
||||
{
|
||||
return Err(invalid());
|
||||
}
|
||||
}
|
||||
let parent = runtime_root.join("archives/diagnostics");
|
||||
let entries = match fs::read_dir(&parent) {
|
||||
Ok(entries) => entries,
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()),
|
||||
Err(error) => return Err(error.into()),
|
||||
};
|
||||
let staging_prefix = format!(".{}.tmp-", archive.operation_id);
|
||||
for entry in entries {
|
||||
let entry = entry?;
|
||||
let name = entry.file_name();
|
||||
let Some(name) = name.to_str() else {
|
||||
continue;
|
||||
};
|
||||
let staged = name.strip_prefix(&staging_prefix).is_some_and(|suffix| {
|
||||
suffix.split_once('-').is_some_and(|(pid, sequence)| {
|
||||
!pid.is_empty()
|
||||
&& !sequence.is_empty()
|
||||
&& pid.bytes().all(|b| b.is_ascii_digit())
|
||||
&& sequence.bytes().all(|b| b.is_ascii_digit())
|
||||
})
|
||||
});
|
||||
if name != archive.operation_id && !staged {
|
||||
continue;
|
||||
}
|
||||
if !entry.file_type()?.is_dir() {
|
||||
return Err(invalid());
|
||||
}
|
||||
let manifest = match fs::read(entry.path().join("manifest.json")) {
|
||||
Ok(bytes) => Some(serde_json::from_slice::<DiagnosticsArchiveManifest>(
|
||||
&bytes,
|
||||
)?),
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound && staged => None,
|
||||
Err(error) => return Err(error.into()),
|
||||
};
|
||||
if let Some(manifest) = manifest {
|
||||
if manifest.schema_version != ARCHIVE_SCHEMA_VERSION
|
||||
|| manifest.workspace_id != archive.workspace_id
|
||||
|| manifest.source_runtime_id != archive.source_runtime_id
|
||||
|| manifest.source_worker_id != archive.source_worker_id
|
||||
|| manifest.operation_id != archive.operation_id
|
||||
|| manifest.input_fingerprint != archive.input_fingerprint
|
||||
{
|
||||
return Err(invalid());
|
||||
}
|
||||
}
|
||||
fs::remove_dir_all(entry.path())?;
|
||||
}
|
||||
sync_directory(&parent, "erase archived Worker diagnostics").map_err(|_| invalid())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn retention_error(error: session_store::StoreError) -> RuntimeError {
|
||||
RuntimeError::InvalidRequest(format!("Session retention rejected operation: {error}"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user