-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add ontology-based macOS orphan cleanup #211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
1cb27ef
feat: prove ZIP content inclusion without extraction
seonghobae 584f222
feat: version archive inclusion evidence for naruon
seonghobae 7c51399
feat: bound cloud offload and cleanup evidence
seonghobae 7d6a631
feat: revalidate developer artifact cleanup
seonghobae 578cc89
feat: add ontology-based orphan cleanup planning
5ec9a93
fix(cloud): review app-managed data before archive copy
6ee78d1
feat(cloud): persist ontology relations in lineage
seonghobae 576f959
feat: track provider sync state and dynamic offload goal
seonghobae c57fcf8
fix: limit automatic sync polling to transient states
seonghobae ada342f
feat: expose verified cloud source eviction
seonghobae 97e1599
feat: preserve provider sync state in Naruon lineage
seonghobae 97b9606
test: cover source evicted ADR snapshot
seonghobae 61c9f88
feat: safely prune stale worktree metadata
seonghobae ec26eed
docs: clarify cloud eviction safety boundary
seonghobae a925a41
feat: persist dynamic cloud goal snapshots
seonghobae 2010244
test: reject unsafe cloud ADR receipt identifiers
seonghobae 7babed8
fix: validate cloud ADR receipt identifiers
seonghobae 39a0d5b
test: bind orphan relation display by predicate
seonghobae 10e687b
feat: select orphan location relation by predicate
seonghobae bd8252f
fix: render orphan location relation semantically
seonghobae 1d397b9
test: define fail-closed worktree CLI argument contract
seonghobae 0179881
fix: make worktree CLI argument handling fail closed
seonghobae e9aa5da
test: define successful clean-plan help contract
seonghobae c35e025
fix: make clean-plan help a successful bounded outcome
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # ADR-0001: Cloud offload provider state drives the goal | ||
|
|
||
| **Status:** Accepted | ||
| **Date:** 2026-08-13 | ||
| **Scope:** DiskSage cloud copy, provider attestation, and source-eviction gate | ||
|
|
||
| ## Context | ||
|
|
||
| A local File Provider copy is not proof that iCloud, OneDrive, or Google Drive | ||
| has uploaded the bytes. In particular, macOS can report a file as local and | ||
| current while `is_uploaded=false`. Manual re-checks and hand-maintained task | ||
| notes allow the displayed goal to drift from the evidence that protects the | ||
| source file. | ||
|
|
||
| ## Decision | ||
|
|
||
| DiskSage records the provider-native state in every `ProviderSyncEvidence`: | ||
| `complete`, `pending-upload`, `not-ubiquitous`, `not-local-current`, | ||
| `uploading`, `excluded-from-sync`, `sync-paused`, `remote-unavailable`, or | ||
| `content-mismatch`. Legacy records deserialize as `unknown` and retain their | ||
| original boolean gate. | ||
|
|
||
| The runtime goal is derived from the same evidence and exposed by both the | ||
| Rust command output and the UI: | ||
|
|
||
| `copy-verified → pending-provider-sync → provider-sync-confirmed → eviction-ready → source-evicted`. | ||
|
|
||
| After each attestation, DiskSage atomically updates per-receipt, | ||
| machine-readable snapshots at the app-data `cloud-adr` and `cloud-goals` | ||
| directories. The ADR contains identifiers, state, decision, consequences, and | ||
| the evidence record ID. The Goal snapshot additionally records the current | ||
| completion-gate booleans and safety invariant. The immutable provider evidence | ||
| remains the authority for content hashes and timestamps. `eviction-ready` | ||
| never deletes the source. | ||
|
|
||
| ## Consequences | ||
|
|
||
| - `local-current / not-uploaded` is visible as `pending-upload` and keeps the | ||
| source-retention goal active. | ||
| - UI polling can update the Goal without another manual copy or attestation | ||
| operation. | ||
| - ADR and Goal state are auditable from the same evidence record and cannot be | ||
| silently edited in place by the provider check. | ||
| - A stale Goal file is replaceable projection data; reconciliation must compare | ||
| it with the immutable evidence record before acting. | ||
| - A separate explicit trash operation is still required after an eviction | ||
| permit; it is not automatic. | ||
| - The source-eviction command moves the source to the OS Trash only after a | ||
| fresh provider attestation and updates the Goal/ADR to `source-evicted`. | ||
|
|
||
| ## References | ||
|
|
||
| - `src-tauri/src/cloud_transfer.rs` (`ProviderSyncState`, `CloudOffloadGoalState`) | ||
| - `src-tauri/src/cloud_adr.rs` (dynamic ADR snapshot writer) | ||
| - `src-tauri/src/cloud_adr.rs` (dynamic Goal snapshot writer) | ||
| - `src-tauri/src/provider_sync.rs` (iCloud/File Provider/API classification) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| { | ||
| "goal_id": "disksage-cloud-offload", | ||
| "status": "active", | ||
| "state_source": "runtime:cloud-goals/<receipt-id>-latest.json", | ||
| "adr_source": "runtime:cloud-adr/<receipt-id>-latest.json", | ||
| "states": [ | ||
| "copy-verified", | ||
| "pending-provider-sync", | ||
| "provider-sync-confirmed", | ||
| "eviction-ready", | ||
| "source-evicted" | ||
| ], | ||
| "completion_gates": [ | ||
| "metadata-and-lineage-bound", | ||
| "copy-content-verified", | ||
| "provider-sync-state-complete", | ||
| "immutable-evidence-record-valid", | ||
| "explicit-eviction-permit" | ||
| ], | ||
| "safety_invariant": "source-retained-until-an-explicit-trash-step" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| # DiskSage cloud offload operator runbook | ||
|
|
||
| 이 문서는 `/Users/seonghobae/Downloads` 같은 로컬 원본을 iCloud Drive, OneDrive, | ||
| Google Drive에 보관할 때의 운영 순서를 정의한다. 계획·복사·원본 회수는 서로 다른 | ||
| 상태이며, 앞 단계의 성공만으로 다음 단계를 승인하지 않는다. | ||
|
|
||
| ## 1. 권한과 증거의 범위 | ||
|
|
||
| - 로컬 스캔과 내장 메타데이터 판독은 클라우드 OAuth 없이 수행한다. | ||
| - File Provider 루트 탐지에는 macOS 개인정보 보호 권한이 필요할 수 있다. | ||
| - OneDrive와 Google Drive의 원격 용량·계정 소유권 확인에는 OAuth PKCE 연결이 필요하다. | ||
| Desktop public client ID만 사용하며 client secret은 저장하거나 입력하지 않는다. | ||
| - iCloud는 macOS 네이티브 quota 상태를 사용하지만, quota만으로 업로드 완료를 증명하지 | ||
| 않는다. | ||
|
|
||
| ## 2. 후보 판정 | ||
|
|
||
| 생산일 증거 우선순위는 다음과 같다. | ||
|
|
||
| 1. 파일 내부 메타데이터(EXIF, ffprobe, 문서 core properties, ZIP central directory 등) | ||
| 2. 명시적인 파일명 날짜(저신뢰 보조 힌트) | ||
| 3. 파일시스템 생성 시각 | ||
| 4. 파일시스템 수정 시각 | ||
|
|
||
| `2026-04-28`이나 `251210` 같은 파일명 토큰만으로 생산일을 확정하지 않는다. 내장 | ||
| 메타데이터와 파일명 날짜가 충돌하면 후보를 검토 상태로 둔다. `.crdownload`, 누락된 | ||
| multipart archive, 읽을 수 없는 archive index는 원자적 복사 계획이 없으면 차단한다. | ||
|
|
||
| 메타데이터 도구와 중복 content hash도 계획 전체 예산 안에서만 실행한다. 초기 계획은 | ||
| 가장 큰 eligible 파일 최대 32개와 10초의 외부 probe 예산, 16 MiB의 중복 hash 예산을 | ||
| 사용한다. 예산을 넘긴 후보에는 `metadata-probe-status` 또는 content-hash 지연 증거와 | ||
| `content-metadata-probe-deferred`/`exact-duplicate-content-probe-deferred` 검토 사유가 | ||
| 남고, 보고서에는 해당 지연 notice가 추가된다. 이 후보를 복사하려면 새 계획에서 필요한 | ||
| 메타데이터와 digest를 다시 확인해야 한다. | ||
|
|
||
| 캐시 메타데이터 manifest도 항목당 2초 또는 100,000개 record에서 멈춘다. 이 경우 | ||
| `scan_complete=false`와 `metadata-manifest-bounded`가 남으며, 읽힌 bytes는 부분값일 수 | ||
| 있다. 불완전 manifest는 GUI와 Rust 정리 게이트에서 자동 거부되므로, 새 읽기 전용 계획이 | ||
| 완료된 뒤에만 별도 항목 승인으로 진행한다. | ||
|
|
||
| ## 3. 계획과 복사 | ||
|
|
||
| 1. DiskSage에서 원본 루트를 스캔하고 클라우드 루트를 다시 탐지한다. | ||
| 2. 후보의 `metadata_fingerprint`, `review_fingerprint`, bytes, 원본 상대 경로, | ||
| production-time source/confidence, context를 검토한다. | ||
| 3. 공급자 용량과 동기화 상태를 검증한 뒤 계획을 다시 생성한다. 이전 preview나 | ||
| 로컬 provider 폴더 존재만으로는 복사 승인을 재사용하지 않는다. | ||
| 4. 민감 맥락·저신뢰 생산일·컨테이너 내용을 가진 후보는 해당 fingerprint에 결박된 | ||
| 명시적 approve/hold 결정이 있어야 한다. | ||
| 5. 복사는 `create-only`와 콘텐츠 hash 검증을 거치며, 원본은 그대로 둔다. copy-only | ||
| receipt의 `lineage.capacity`에는 그 복사를 허용한 용량 snapshot, evidence fingerprint, | ||
| requested/reserve 계산, `can_fit` 결과가 함께 결박된다. immutable receipt와 provider | ||
| evidence가 생성되어야 복사 단계가 완료된 것으로 본다. 이미 존재하는 동일 목적지를 | ||
| 채택하는 경로는 새 바이트를 쓰지 않으므로 capacity lineage가 없을 수 있다. | ||
|
|
||
| ## 4. 원본 회수 | ||
|
|
||
| 원본 회수는 복사와 별도의 승인이다. provider-native/API evidence가 receipt의 | ||
| destination, bytes, digest, 위치와 일치하고 `sync_complete`인 경우에만 eviction | ||
| permit이 생성된다. permit 없이 원본을 Trash로 보내지 않는다. 회수 전에는 source | ||
| metadata와 content digest를 다시 확인하고, 실패하면 staging을 복구한다. | ||
|
|
||
| 복사 직후와 각 attestation·휴지통 이동 뒤에는 app-data의 | ||
| `cloud-goals/<receipt-id>-latest.json`을 원자적으로 갱신하고, attestation 이후에는 | ||
| `cloud-adr/<receipt-id>-latest.json`도 갱신한다. ADR은 결정·결과를, Goal은 | ||
| 현재 상태와 completion gate를 보여주는 교체 가능한 projection이다. 복사 직후에는 | ||
| provider/evidence gate가 명시적으로 false다. `pending-upload`나 | ||
| `is_local_current=true`/`is_uploaded=false`는 Goal을 `pending-provider-sync`로 유지하며 | ||
| eviction permit을 만들지 않는다. 운영 도구는 Goal 파일을 권한 증거로 사용하지 말고, | ||
| 항상 immutable receipt/provider evidence를 재검증해야 한다. | ||
|
|
||
| ## 5. 승인 문구의 범위 | ||
|
|
||
| `승인`, `네` 같은 일반 동의는 현재 후보에 결박되지 않는다. 실행 직전에 DiskSage가 | ||
| 새 계획을 만들고 다음 항목을 제시해야 한다. | ||
|
|
||
| - 정확한 source/destination 경로 | ||
| - bytes와 source modified 시각 | ||
| - metadata/review fingerprint | ||
| - 공급자·계정 범위·용량 evidence fingerprint | ||
| - copy-only인지, provider attestation인지, source eviction인지 | ||
|
|
||
| 사용자는 copy-only와 source eviction을 각각 승인한다. 어느 한 단계의 성공을 다음 | ||
| 단계의 승인으로 간주하지 않는다. | ||
|
|
||
| ## 6. stale Git worktree 감사 | ||
|
|
||
| `disksage-git-worktree-audit`는 `git worktree list --porcelain`을 5초 안에 끝내지 | ||
| 못하면 `.git/worktrees` 관리자 등록을 읽기 전용으로 확인한다. 관리자 파일은 크기와 | ||
| 읽기 시간을 제한하며, 비어 있거나 읽기 timeout인 `gitdir`는 실제 worktree 경로로 | ||
| 추정하지 않고 `<worktree-admin:...>` 증거로 남긴다. 이 fallback 보고서는 | ||
| `evidence_complete: false`이므로 `registration_fingerprint`를 보관하고 수동 검토할 | ||
| 때까지 `git worktree prune/remove`나 파일 삭제를 실행하지 않는다. 완전한 감사에서 | ||
| `metadata_prune_eligible_count`가 양수이면 UI의 명시적 승인 문구를 통해 | ||
| `prune_stale_worktree_metadata`를 실행할 수 있다. 이 명령은 재감사와 fingerprint | ||
| 일치를 먼저 확인하고 `git worktree prune --expire now`만 실행한다. worktree 디렉터리, | ||
| 브랜치, 파일은 삭제하지 않으며 사후 감사에서 stale 등록 감소를 확인하지 못하면 실패한다. | ||
|
|
||
| ## 7. 조건부 통합 경계 | ||
|
|
||
| 기본 판단·hash·capacity 계산은 Rust와 오프라인 llama.cpp 경로를 사용한다(Ollama | ||
| 사용 안 함). Noema/contextual-orchestrator는 실제 agent/external-LLM 계약이 생길 | ||
| 때만 연결한다. semantic-data-portal과 pg-erd-cloud는 영속 catalog/DB 경계가 필요할 | ||
| 때만 연결하고, fast-mlsirm은 binary/polytomous LLM-as-a-Judge 계약이 생길 때만 | ||
| 판정기로 사용한다. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
개인 로컬 계정 식별자를 문서에서 제거하십시오.
Line 3의 예시 경로에는 개인 로컬 계정 식별자가 포함됩니다.
$HOME/Downloads또는/Users/example/Downloads같은 비식별 경로를 사용하십시오.🤖 Prompt for AI Agents