Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Reviewed fallback production-date evidence

## Problem

DiskSage already ranks production-time evidence as embedded metadata, explicit filename date,
filesystem creation time, then modification time. The copy gate nevertheless rejected every
candidate that lacked an embedded, high-confidence production date, even after an operator had
approved the exact evidence shown in the review UI. That made the fallback ranking unusable for
opaque archives and older files while adding no extra protection after an evidence-bound review.

## Decision

Embedded, high-confidence production time remains the only path that can pass the date gate
without a review decision. A lower-ranked date may pass only when all of the following are true:

1. The planner marks the candidate as requiring review.
2. The append-only decision is valid and has disposition `approved`.
3. The decision candidate fingerprint and review fingerprint match the rebuilt candidate.
4. The normal planner, path, provider, destination, metadata-fingerprint, and copy gates pass.

Held, absent, invalid, mismatched, or stale decisions do not waive the embedded-date gate. The
headless CLI does not accept or load review decisions, so it cannot use fallback dates to copy.

Embedded dates below high confidence receive an explicit
`embedded-production-date-confidence-not-high` review reason. Filename and filesystem fallbacks
already receive `production-date-not-from-embedded-metadata`.

## Safety invariants

- A filename date is never trusted automatically.
- An approval is bound to the source, destination, provider, file identity, selected date,
confidence, review reasons, and all displayed metadata evidence.
- Replanning occurs before the decision is stored and again before copying.
- Approval changes only review/date eligibility. It cannot bypass a planner block, unsafe path,
provider mismatch, changed fingerprint, destination collision, copy verification, or provider
synchronization proof.
- The source deletion API remains absent. Local eviction still requires an immutable copy receipt
and provider-native synchronization evidence.

## Verification

- Rust unit tests cover approved, absent, and held fallback-date decisions.
- Planner tests cover medium-confidence embedded dates becoming review-required.
- Svelte type checking verifies that the UI mirrors the backend gate.
- Existing stale/tampered decision and provider synchronization tests remain authoritative.
6 changes: 6 additions & 0 deletions src-tauri/src/cloud.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1706,6 +1706,8 @@ pub fn plan_cloud_archive(
}
if !production_time_source.starts_with("embedded:") {
review_reasons.push("production-date-not-from-embedded-metadata".into());
} else if production_time_confidence != "high" {
review_reasons.push("embedded-production-date-confidence-not-high".into());
}
let embedded_dates: BTreeSet<&str> = lineage_metadata
.evidence
Expand Down Expand Up @@ -2617,6 +2619,10 @@ mod tests {
"embedded:unknown"
);
assert_eq!(report.candidates[0].production_time_confidence, "medium");
assert!(report.candidates[0].requires_review);
assert!(report.candidates[0]
.review_reasons
.contains(&"embedded-production-date-confidence-not-high".to_string()));
}

#[test]
Expand Down
23 changes: 21 additions & 2 deletions src-tauri/src/cloud_transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ pub fn candidate_blockers_with_review(
let destination = Path::new(&candidate.dst);
let root = Path::new(&cloud_root.path);
let mut blockers = Vec::new();
let mut exact_review_approved = false;

if candidate.review_fingerprint.len() != 64
|| !candidate
Expand Down Expand Up @@ -140,13 +141,17 @@ pub fn candidate_blockers_with_review(
Some(decision) if decision.disposition == CloudReviewDisposition::Held => {
blockers.push("review-held".into());
}
Some(_) => {}
Some(_) => exact_review_approved = true,
}
}
if candidate.blocked_reason.is_some() {
blockers.push("planner-blocked".into());
}
if !embedded_high_confidence(candidate) {
// Embedded, high-confidence production time remains the only evidence that can pass without
// an operator decision. Lower-ranked evidence (explicit filename date, filesystem creation,
// then modification) may enter the copy-only phase only when an approval is bound to the
// exact candidate evidence and destination above. The headless CLI never supplies a decision.
if !embedded_high_confidence(candidate) && !exact_review_approved {
blockers.push("embedded-high-confidence-date-required".into());
}
if candidate.metadata_fingerprint.trim().is_empty() {
Expand Down Expand Up @@ -876,7 +881,21 @@ mod tests {
)
.unwrap();
assert!(candidate_blockers_with_review(&reviewed, &root(), Some(&filename_approval))
.is_empty());

assert!(candidate_blockers_with_review(&reviewed, &root(), None)
.contains(&"embedded-high-confidence-date-required".to_string()));

let filename_hold = crate::cloud_review::create_decision(
&reviewed,
CloudReviewDisposition::Held,
13,
)
.unwrap();
let held_blockers =
candidate_blockers_with_review(&reviewed, &root(), Some(&filename_hold));
assert!(held_blockers.contains(&"review-held".to_string()));
assert!(held_blockers.contains(&"embedded-high-confidence-date-required".to_string()));
}

#[test]
Expand Down
10 changes: 6 additions & 4 deletions src/lib/CloudArchive.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,12 @@

function copyEligible(candidate: api.CloudCandidate): boolean {
const decision = matchingReviewDecision(candidate);
return candidate.blocked_reason === null
&& (!candidate.requires_review || decision?.disposition === "approved")
&& candidate.production_time_confidence === "high"
const exactApproval = decision?.disposition === "approved";
const embeddedHighConfidence = candidate.production_time_confidence === "high"
&& candidate.production_time_source.startsWith("embedded:");
return candidate.blocked_reason === null
&& (!candidate.requires_review || exactApproval)
&& (embeddedHighConfidence || exactApproval);
}

function reviewDecision(candidate: api.CloudCandidate): api.CloudReviewDecision | null {
Expand Down Expand Up @@ -300,7 +302,7 @@
충돌 제외 잠재 회수 {fmtBytes(report.potentially_reclaimable_bytes)}
</div>
<p class="warning">
복사는 내부 메타데이터가 고신뢰이고, 검토 사유가 있으면 현재 증거에 결박된 명시적 승인이 있는 후보만 가능합니다. 원본 삭제 기능은 제공하지 않으며, 업로드 증거가 확인되어도 허가 정보만 표시합니다.
내장 고신뢰 생산일은 자동 자격을 얻습니다. 파일명·파일시스템 등 보조 생산일은 현재 메타데이터와 목적지에 결박된 명시적 승인이 있어야만 복사할 수 있습니다. 원본 삭제 기능은 제공하지 않으며, 업로드 증거가 확인되어도 허가 정보만 표시합니다.
</p>
{#if copied}
<div class="receipt">
Expand Down
Loading