From bac61cc0d892f77d1188f35c7e986d45cc5ba28d Mon Sep 17 00:00:00 2001 From: heznpc Date: Mon, 7 Sep 2026 23:12:30 +0900 Subject: [PATCH 1/3] docs: define project and asset evidence migration contract --- docs/ARCHITECTURE.md | 4 + docs/PROJECT_ASSET_MODEL_V1.md | 213 +++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 docs/PROJECT_ASSET_MODEL_V1.md diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e6b51d1..510a1e7 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -129,6 +129,10 @@ store, including case variants and resolved symlink aliases. ## Good contribution areas +The [Project / Asset Model v1 proposal](PROJECT_ASSET_MODEL_V1.md) defines the next +identity/evidence contract, producer boundaries and staged migration. It is design intent +for review, not an implemented common model; the execution boundaries above remain authoritative. + - Verified Korean/Japanese application whitelist entries. - False-positive fixtures that contain no personal scan data. - New declarative rules with negative tests. diff --git a/docs/PROJECT_ASSET_MODEL_V1.md b/docs/PROJECT_ASSET_MODEL_V1.md new file mode 100644 index 0000000..4eb2561 --- /dev/null +++ b/docs/PROJECT_ASSET_MODEL_V1.md @@ -0,0 +1,213 @@ +# Project / Asset Model v1 + +Status: **Design intent — review before implementation.** The Swift declarations below +describe the first migration contract; they are not currently shipped APIs. + +Modore explains the structure and provenance of durable local state, supplies evidence +for protection and recovery decisions, and revalidates every action at a separate +execution boundary. The project/asset model describes state and grants no authority. + +## Baseline and scope + +The baseline candidate is PR #109, ending at `1ac962f`: logical Codex session grouping, +multi-fragment inspection, checked recovery accounting, bounded previews, and a durable +display-only recovery journal. Integration status belongs to Git/CI, not this proposal. + +Currently implemented: + +- `WorkModels.swift` composes sessions, worktrees, repository assessments, scan failures, + and unscanned roots into `WorkProject`. +- `StorageItem` has a transient UUID and path/kind/recipe fields; it has no project link. + `StorageHistoryItem` persists a separate key and floating-point GiB estimates. +- `ObservedProcessRow` records PID, ownership and CPU, without a project path observation. +- `fileaccess.py` emits path/session associations and explicit content-scan omissions; + it is not bundled and has no Swift caller. +- `RecoveryHistory` is display-only and contains no approval token. + +The first implementation adds identity/evidence value types and adapts Work without +changing its rows, ordering, selection, counts, Git labels or action behavior. Storage +attribution is a subsequent change. No graph database, collector rewrite, parser split, +executor change or persistent content index belongs in that first implementation. + +## Identity: preserve the current key without overstating it + +`WorkProjectBuilder.canonical` currently removes one trailing slash and lowercases the +path. It does not call realpath, inspect case sensitivity, prove existence or verify a +repository. Root selection uses the longest known ancestor, then the conventional +`/.claude/worktrees/` or `/.git/worktrees/` shape. A conventional shape is inferred +membership, not confirmed Git registry membership. + +The v1 project ID wraps this existing comparison key. Keep the original spelling for +display. Unknown workspace sessions retain the current unassigned bucket in the app; +that bucket is not a filesystem project and has no `ProjectIdentity`. + +```swift +public struct ProjectIdentity: Hashable, Codable, Sendable { + public let comparisonKey: String +} + +public struct AssetIdentity: Hashable, Codable, Sendable { + public let namespace: String + public let key: String +} + +public enum EvidenceSource: String, Codable, Sendable { + case scree, repositoryAssessment, storageScan, processObservation + case fileAccess, sessionBackup, cleanupReceipt, recoveryHistory +} + +public enum ObservationState: String, Codable, Sendable { + case observed, inferred, unknown, failed, notCollected +} + +public enum CoverageState: String, Codable, Sendable { + case complete, partial, unknown +} + +public struct Coverage: Equatable, Codable, Sendable { + public let state: CoverageState + public let scope: String + public let limitations: [String] +} + +public enum EvidenceMethod: String, Codable, Sendable { + case recordedWorkspace, knownRootAncestry, conventionalWorktreePath + case gitRegistry, pathAncestry, projectManifest + case processWorkingDirectory, processOpenFile + case transcriptRead, transcriptWrite, transcriptShellReference + case archiveHashVerification, cleanupReceipt, recoveryCheckpoint +} + +public struct AssetEvidence: Equatable, Codable, Sendable { + public let source: EvidenceSource + public let observationID: String + public let method: EvidenceMethod + public let observedAt: Date? + public let state: ObservationState + public let coverage: Coverage +} +``` + +Implementation supplies explicit public initializers and validation at decoding/adaptation +boundaries. Identity fields must be nonempty; unknown identity stays outside these types. +Namespace values are adapter-owned constants, not arbitrary producer-controlled routes. +Initial namespaces distinguish filesystem paths, logical provider sessions and physical +transcript artifacts. A logical session key includes its provider; artifact keys cannot +reuse that session key. File replacement at the same path is a new observation, even +when its display identity remains the same. Observation IDs identify a producer run and +record, never a fresh UUID generated every time the same snapshot is rendered. + +Pure domain types accept already interpreted values. Filesystem resolution, Git commands, +remote parsing and case-sensitivity probes stay in OS adapters. Repository remotes are +optional observed metadata, not identity: two clones of one remote remain two local +projects. Credential-bearing remote URLs must never enter display or evidence payloads. + +Phase 1 retains today's case-folding behavior, including its limitation on case-sensitive +volumes. Correcting that behavior needs a separately reviewed identity migration and +selection/history compatibility tests. Never describe the legacy key as a verified +canonical root or use it as an executor target. + +## Evidence and relation semantics + +`observedAt` is the producer's observation time. Missing or ambiguous legacy timestamps +remain nil; adapter load time must not impersonate observation time. Collection time, +provider event time and source-file modification time are distinct. Freshness is assessed +by a consumer using a supplied clock and policy; it is not a durable `isFresh` Boolean. + +Coverage is always relative to the declared scope. Complete enumeration of discovered +sessions says nothing about undiscovered stores or files beyond a content-read cap. +Partial scans may support positive observations but cannot prove absence. Do not turn +timeouts, omitted rows, unknown roots or empty failed results into “unused” or “safe”. +Conflicting evidence is retained for assessment rather than overwritten by latest arrival. + +Phase 2 introduces a typed `ProjectAssetMembership` containing one asset, one project +and a nonempty evidence collection. This is the first `belongsTo` relation. Do not add +other relation cases until a producer and a consumer exist. The reserved vocabulary is: + +| Relation | Direction and claim | +| --- | --- | +| belongsTo | asset → local project; attribution within a declared scope | +| producedBy | asset → observed producing task/tool; requires production evidence | +| usedBy | asset → process instance; requires observed cwd/open-file evidence | +| touchedBy | asset → logical session; retains read/write/shell-reference method | +| derivedFrom | asset → source artifact; lineage evidence, not rebuildability | +| backedUpBy | artifact → backup artifact; verification coverage remains explicit | +| protects | backup/retained artifact → protected subject; explicit protection evidence | +| observedWith | subject → another subject in one observation; no causal assertion | + +Use dedicated endpoint types as each relation arrives, not a bag of arbitrary strings. +Process identity must include a start identity/time and observation context, since PIDs +are reused. A transcript tool invocation is a recorded claim about access; it does not +prove successful OS access. Shell references are weaker still. Keep these distinctions +in the method and assessment presented to the user. + +Path ancestry, a project manifest, and a verified repository root are separate evidence +records. `node_modules` can be classified as a dependency candidate by its name, but that +does not establish that it can be rebuilt. Shared caches may have several consumers and +must not be forced into one owning project. Nested repositories use the most specific +established root; ambiguous, missing or symlink-dependent matches remain unresolved. + +`ProtectionAssessment`, `RebuildAssessment`, `CleanupAssessment` and +`ContinuityAssessment` belong above these facts. Each assessment names its policy/version, +input observations, evaluation time, and reasons, with unknown/incomplete/conflicting +outcomes. None can construct an approval token, executable plan or validated target. + +## Producer contract + +Keep producer implementations and composition in app `Services/`; put only pure schema +and semantics in `shared/ModoreDomain`. The first Work adapter takes the existing inputs +to `WorkProjectBuilder` and associates their identity with the evidence actually supplied. +It does not request another scan or invent missing timestamps. + +Subsequent adapters return immutable snapshots with a producer/run identifier, schema +version, observation time (when known), coverage, records and path-specific failures. +The application owns cancellation, time/output limits and publication of a consistent +snapshot. Late output from an earlier run cannot overwrite a newer observation. Existing +facts kept after a failed refresh retain their original time and show the refresh failure. + +| Producer | Adaptation | Required limits/unknowns | +| --- | --- | --- | +| scree + Mothball | Work identities and membership evidence | root caps, scan failures, conventional worktree inference | +| storage scan | path asset, measured occupancy, project membership | invalid size/status, unknown ownership, overlapping paths | +| process observation | runtime usage evidence | current CPU row alone cannot identify a project; bounded path probes required | +| fileaccess | session/path touch evidence | propagate store failures, truncation, omitted IDs/rows, budget and timestamp limits | +| backup verification | backed-up artifact coverage | selected transcript never means all logical-session fragments | +| receipts + recovery history | historical outcomes | no retrospective token/plan reconstruction; unknown volume delta stays unknown | + +Storage adapters convert legacy GiB only at the boundary using checked byte conversion; +they must inspect original measurement validity, since the current DTO can coerce malformed +input to zero. Occupancy uses nonnegative optional Int64 bytes, while volume change uses +signed optional Int64 bytes. Estimated/measured and partial/complete remain distinct. +Do not sum parent and child assets twice or infer a project's reclaimable total from sizes +alone. Keep old storage-history serialization until an explicit versioned migration exists. + +Before fileaccess ships in the app, add its bounded Swift caller, bundled runtime allowlist +entry, transitive dependency/payload audit, decoder and failure-path tests together. Read +it through the existing pinned runtime/process transport. Metadata-only output is still +private owner data; use synthetic paths and transcripts in committed fixtures. + +## Migration and acceptance gates + +1. **Identity/evidence contract.** Add pure types and unit tests for invalid identity, + serialization and unknown/partial observations. Adapt `WorkProject` with an optional + identity for the unassigned case, preserving its existing String UI ID and initializer + compatibility. Keep the builder algorithm and all action inputs unchanged. Run existing + WorkProject/GitAssessmentState/selection tests and inspect the Work UI with the built app. +2. **Storage membership.** Introduce typed membership and a read-only composer. Verify + nested roots, same-prefix sibling paths, unassigned assets, ambiguous ownership, + symlinks, partial scans and overlap accounting. Work and Storage reference the same + project key. UI explains attribution; cleanup still goes through its current preview. +3. **Runtime and touches.** Reuse fileaccess and add bounded runtime path evidence. Verify + PID reuse, stale observations, denied probes and partial transcript coverage. Demonstrate + a synthetic project's observed session/path relationship in the native app. +4. **Continuity.** Attach verified backup coverage and display-only recovery outcomes. + Demonstrate restart rendering without approval resurrection; distinguish one fragment + backed up from the entire logical session protected. +5. **Retrieval.** Separately design resumable large-file reads, an incremental SQLite + content index, exact event locators, logical-session manifest backup and durable evidence + packs. Logical grouping alone does not close large-artifact search coverage. + +Each stage starts from integrated main, has its own PR and CI, and demonstrates the changed +behavior through the actual runtime. A test pass alone is not evidence of a working UI. +All stages preserve preview → fresh measurement → exact target → explicit approval → +process/filesystem revalidation → execution → receipt → volume measurement/history. From dff7e59419cba58be5df8f05b5d5a8db2990db2f Mon Sep 17 00:00:00 2001 From: heznpc Date: Mon, 7 Sep 2026 23:17:39 +0900 Subject: [PATCH 2/3] docs: record the integrated and verified baseline --- docs/PROJECT_ASSET_MODEL_V1.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/PROJECT_ASSET_MODEL_V1.md b/docs/PROJECT_ASSET_MODEL_V1.md index 4eb2561..1761b7e 100644 --- a/docs/PROJECT_ASSET_MODEL_V1.md +++ b/docs/PROJECT_ASSET_MODEL_V1.md @@ -9,9 +9,11 @@ execution boundary. The project/asset model describes state and grants no author ## Baseline and scope -The baseline candidate is PR #109, ending at `1ac962f`: logical Codex session grouping, +The baseline is [PR #109](https://github.com/heznpc/Modore/pull/109), merged into main +at `8e2f412` after full CI passed. Its five commits end at `1ac962f`: logical Codex session grouping, multi-fragment inspection, checked recovery accounting, bounded previews, and a durable -display-only recovery journal. Integration status belongs to Git/CI, not this proposal. +display-only recovery journal. The native app was built and its Work and recovery-history +screens inspected; all 513 Mac tests passed locally, including the isolated live recovery harness. Currently implemented: From 1a67b9e5238429945f2209d211b3b046982796c7 Mon Sep 17 00:00:00 2001 From: heznpc Date: Mon, 7 Sep 2026 23:27:19 +0900 Subject: [PATCH 3/3] docs: separate collection outcomes from positive evidence --- docs/ARCHITECTURE.md | 5 +- docs/PROJECT_ASSET_MODEL_V1.md | 190 +++++++++++++++++++++++++++------ 2 files changed, 160 insertions(+), 35 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 510a1e7..a0ae8df 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -131,7 +131,10 @@ store, including case variants and resolved symlink aliases. The [Project / Asset Model v1 proposal](PROJECT_ASSET_MODEL_V1.md) defines the next identity/evidence contract, producer boundaries and staged migration. It is design intent -for review, not an implemented common model; the execution boundaries above remain authoritative. +for review, not an implemented common model. Its contract separates collection outcomes, +positive evidence with explicit provenance references, and assessments. The first migration +must produce evidence from a real Work input while preserving the existing UI identity; +the execution boundaries above remain authoritative. - Verified Korean/Japanese application whitelist entries. - False-positive fixtures that contain no personal scan data. diff --git a/docs/PROJECT_ASSET_MODEL_V1.md b/docs/PROJECT_ASSET_MODEL_V1.md index 1761b7e..34b6fb1 100644 --- a/docs/PROJECT_ASSET_MODEL_V1.md +++ b/docs/PROJECT_ASSET_MODEL_V1.md @@ -26,8 +26,9 @@ Currently implemented: it is not bundled and has no Swift caller. - `RecoveryHistory` is display-only and contains no approval token. -The first implementation adds identity/evidence value types and adapts Work without -changing its rows, ordering, selection, counts, Git labels or action behavior. Storage +The first implementation adds identity/provenance/coverage value types and one Work +producer adapter that emits actual evidence from session workspace inputs. It preserves +Work's rows, ordering, selection, counts, Git labels and action behavior. Storage attribution is a subsequent change. No graph database, collector rewrite, parser split, executor change or persistent content index belongs in that first implementation. @@ -58,8 +59,19 @@ public enum EvidenceSource: String, Codable, Sendable { case fileAccess, sessionBackup, cleanupReceipt, recoveryHistory } -public enum ObservationState: String, Codable, Sendable { - case observed, inferred, unknown, failed, notCollected +public struct EvidenceReference: Hashable, Codable, Sendable { + public let source: EvidenceSource + public let runID: String + public let recordID: String +} + +// Positive support for a stated claim; never a collection result. +public enum EvidenceBasis: String, Codable, Sendable { + case observed, inferred, recordedClaim, reference +} + +public enum CollectionOutcome: String, Codable, Sendable { + case complete, partial, failed, notCollected, unknown } public enum CoverageState: String, Codable, Sendable { @@ -68,6 +80,7 @@ public enum CoverageState: String, Codable, Sendable { public struct Coverage: Equatable, Codable, Sendable { public let state: CoverageState + // Opaque display descriptions. Consumers must not parse these strings. public let scope: String public let limitations: [String] } @@ -76,18 +89,38 @@ public enum EvidenceMethod: String, Codable, Sendable { case recordedWorkspace, knownRootAncestry, conventionalWorktreePath case gitRegistry, pathAncestry, projectManifest case processWorkingDirectory, processOpenFile - case transcriptRead, transcriptWrite, transcriptShellReference + case transcriptReadInvocation, transcriptWriteInvocation, transcriptShellReference case archiveHashVerification, cleanupReceipt, recoveryCheckpoint } -public struct AssetEvidence: Equatable, Codable, Sendable { - public let source: EvidenceSource - public let observationID: String +public struct EvidenceRecord: Equatable, Codable, Sendable { + public let reference: EvidenceReference public let method: EvidenceMethod + public let basis: EvidenceBasis public let observedAt: Date? - public let state: ObservationState public let coverage: Coverage } + +public enum CollectionFailureCode: String, Codable, Sendable { + case unavailable, unreadable, malformed, timedOut, cancelled, unsupported +} + +public struct ProducerFailure: Equatable, Codable, Sendable { + public let code: CollectionFailureCode + public let recordID: String? + public let detail: String // Display only; never an assessment input. +} + +public struct ProducerSnapshot: Codable, Sendable { + public let producer: EvidenceSource + public let runID: String + public let schemaVersion: Int + public let observedAt: Date? + public let outcome: CollectionOutcome + public let coverage: Coverage + public let records: [Record] + public let failures: [ProducerFailure] +} ``` Implementation supplies explicit public initializers and validation at decoding/adaptation @@ -96,8 +129,13 @@ Namespace values are adapter-owned constants, not arbitrary producer-controlled Initial namespaces distinguish filesystem paths, logical provider sessions and physical transcript artifacts. A logical session key includes its provider; artifact keys cannot reuse that session key. File replacement at the same path is a new observation, even -when its display identity remains the same. Observation IDs identify a producer run and -record, never a fresh UUID generated every time the same snapshot is rendered. +when its display identity remains the same. An `EvidenceReference` is the explicit tuple +`source + runID + recordID`: run IDs identify collection attempts; record IDs are unique +within that producer run. References remain stable when a snapshot is rendered again. +New collection attempts receive new run IDs even if their values happen to match. Validate +nonempty IDs and require every emitted evidence reference to match its enclosing snapshot's +producer/run. Assessment inputs retain these exact references, not display strings or +an ambiguous combined observation ID. A reference identifies evidence, not an executable target. Pure domain types accept already interpreted values. Filesystem resolution, Git commands, remote parsing and case-sensitivity probes stay in OS adapters. Repository remotes are @@ -111,6 +149,30 @@ canonical root or use it as an executor target. ## Evidence and relation semantics +Keep three layers separate, followed by the existing action boundary: + +```text +Collection outcome: complete / partial / failed / notCollected / unknown + → Evidence: observed / inferred / recordedClaim / reference + → Assessment: protection / rebuildability / cleanup / continuity + → Action planning + independent revalidation +``` + +`EvidenceRecord` contains only positive factual support and its provenance. It has no +failed, unknown or not-collected case. `CollectionOutcome`, coverage and producer failures +describe collection, not relations. Unknown attribution is an unresolved attribution result +in app composition, never `belongsTo(unknown)` or a membership supported only by a failure. +The typed observation containing an evidence record must state its subject and claim; +provenance by itself is not a fact about arbitrary endpoints. + +`EvidenceBasis` qualifies that claim, not the certainty of the collection machinery. +For example, a transcript Read invocation uses `transcriptReadInvocation + recordedClaim`: +the record supports “this session recorded a Read invocation for this path,” not “the OS +successfully read this file.” A shell path mention uses `transcriptShellReference + reference`. +Adapters must validate method/basis combinations; neither can be silently promoted to +direct OS access evidence. `recordedWorkspace` supports a provider-recorded workspace claim; +ancestry and conventional worktree mapping are separate inferred claims. + `observedAt` is the producer's observation time. Missing or ambiguous legacy timestamps remain nil; adapter load time must not impersonate observation time. Collection time, provider event time and source-file modification time are distinct. Freshness is assessed @@ -122,8 +184,21 @@ Partial scans may support positive observations but cannot prove absence. Do not timeouts, omitted rows, unknown roots or empty failed results into “unused” or “safe”. Conflicting evidence is retained for assessment rather than overwritten by latest arrival. +`Coverage.scope` is an opaque, display-only explanation, and `limitations` are display-only +diagnostics. Consumers must not parse, compare labels or search these strings to choose a +verdict. A producer that needs machine-readable scope adds its own typed endpoint/scope +contract alongside coverage. This is required before proving absence: `.complete` plus a +scope string alone cannot establish that the relevant stores, paths or time range were +exhaustively examined. Use typed outcome/failure codes for control flow; add typed limit +fields when a consumer needs them, rather than interpreting diagnostic text. + Phase 2 introduces a typed `ProjectAssetMembership` containing one asset, one project -and a nonempty evidence collection. This is the first `belongsTo` relation. Do not add +and positive evidence specifically supporting those endpoints and that membership claim. +Its validated constructor requires nonempty relevant evidence, not just an arbitrary +nonempty array: unrelated observations or a shell reference alone cannot establish ownership. +Without that support, the composer retains unresolved attribution and creates no membership. +This is the first shared `belongsTo` relation; Phase 1 already emits a scoped Work observation +from the existing workspace input without introducing a general relationship model. Do not add other relation cases until a producer and a consumer exist. The reserved vocabulary is: | Relation | Direction and claim | @@ -151,18 +226,39 @@ established root; ambiguous, missing or symlink-dependent matches remain unresol `ProtectionAssessment`, `RebuildAssessment`, `CleanupAssessment` and `ContinuityAssessment` belong above these facts. Each assessment names its policy/version, -input observations, evaluation time, and reasons, with unknown/incomplete/conflicting -outcomes. None can construct an approval token, executable plan or validated target. +exact input `EvidenceReference` values, evaluation time, and typed outcome/reasons, +including unknown/incomplete/conflicting outcomes. None can construct an approval token, +executable plan or validated target. ## Producer contract Keep producer implementations and composition in app `Services/`; put only pure schema -and semantics in `shared/ModoreDomain`. The first Work adapter takes the existing inputs -to `WorkProjectBuilder` and associates their identity with the evidence actually supplied. -It does not request another scan or invent missing timestamps. - -Subsequent adapters return immutable snapshots with a producer/run identifier, schema -version, observation time (when known), coverage, records and path-specific failures. +and semantics in `shared/ModoreDomain`. The first Work adapter consumes an actual +`SessionIndexEntry` and the existing builder's resolved project key. It emits a typed Work +workspace observation with the logical session identity, recorded workspace, project identity +and `EvidenceRecord` using `recordedWorkspace + recordedClaim`. If the builder maps that +workspace to an ancestor or conventional worktree root, represent that mapping separately +as inferred evidence rather than asserting the session recorded that root. Reuse the +builder's result and matching rule, without changing its grouping algorithm. Missing +workspace produces no membership evidence and stays in the existing unassigned bucket. + +Phase 1 uses `ProducerSnapshot` at this adapter boundary. +The existing collection invocation supplies a run ID that survives re-adaptation/rendering; +the adapter derives record IDs deterministically within that run. It does not request +another scan or invent missing timestamps. Observation payloads and adapter code stay in +the app; only the envelope and pure value types live in `ModoreDomain`. + +Snapshots distinguish collection outcome from positive records. Failed/not-collected/unknown +attempts emit no current positive records; recovered valid records from a run interrupted by +failure require `.partial` and explicit coverage/failures. `.complete` means the producer +finished its declared collection, not that every possible product claim has complete coverage. +Record coverage may still be partial, for example when a metadata listing completes but +content was bounded. Failures refer to affected input record IDs when known and do not +become `EvidenceReference` values. Old successful records remain in their original snapshot. + +Subsequent producers/adapters use the same immutable envelope, with their own typed payloads +and scope contracts where needed. Phase 2 and Phase 3 require producer/DTO contract changes, +not merely adapters over today's lossy or incomplete rows. The application owns cancellation, time/output limits and publication of a consistent snapshot. Late output from an earlier run cannot overwrite a newer observation. Existing facts kept after a failed refresh retain their original time and show the refresh failure. @@ -170,19 +266,31 @@ facts kept after a failed refresh retain their original time and show the refres | Producer | Adaptation | Required limits/unknowns | | --- | --- | --- | | scree + Mothball | Work identities and membership evidence | root caps, scan failures, conventional worktree inference | -| storage scan | path asset, measured occupancy, project membership | invalid size/status, unknown ownership, overlapping paths | -| process observation | runtime usage evidence | current CPU row alone cannot identify a project; bounded path probes required | +| storage scan | preserve raw measurement validity before DTO coercion, then compose assets/membership | invalid size/status, unknown ownership, overlapping paths | +| process observation | extend producer and DTO with process instance identity, then runtime evidence | PID alone is insufficient; bounded start-identity/path probes required | | fileaccess | session/path touch evidence | propagate store failures, truncation, omitted IDs/rows, budget and timestamp limits | | backup verification | backed-up artifact coverage | selected transcript never means all logical-session fragments | | receipts + recovery history | historical outcomes | no retrospective token/plan reconstruction; unknown volume delta stays unknown | -Storage adapters convert legacy GiB only at the boundary using checked byte conversion; -they must inspect original measurement validity, since the current DTO can coerce malformed -input to zero. Occupancy uses nonnegative optional Int64 bytes, while volume change uses +Phase 2 must first extend the storage decoder/DTO to retain raw measurement validity before +coercion. The current `StorageItem` has already turned malformed/non-finite `sizeGB` into +zero; an adapter downstream cannot recover whether that zero was measured. Preserve a typed +measurement state and optional checked byte value while keeping legacy display fields +compatible. Perform conversion where raw values are still available, distinguishing missing, +malformed, non-finite, overflow and timed-out values from a valid measured zero. Do not +infer validity from the coerced DTO value or a default `measureStatus`. +Occupancy uses nonnegative optional Int64 bytes, while volume change uses signed optional Int64 bytes. Estimated/measured and partial/complete remain distinct. Do not sum parent and child assets twice or infer a project's reclaimable total from sizes alone. Keep old storage-history serialization until an explicit versioned migration exists. +Phase 3 must extend the observation producer and `ObservedProcessRow` contract with a +process start identity/time and collection context before emitting `usedBy`. Today's PID +and transient row UUID cannot distinguish reused PIDs. Collect start identity and bounded +cwd/open-file evidence for the same process instance, rechecking instance identity around +the probes. Missing/denied start data or an instance change yields unresolved runtime +attribution; never synthesize a start time from collection time or treat a UUID as OS identity. + Before fileaccess ships in the app, add its bounded Swift caller, bundled runtime allowlist entry, transitive dependency/payload audit, decoder and failure-path tests together. Read it through the existing pinned runtime/process transport. Metadata-only output is still @@ -190,17 +298,31 @@ private owner data; use synthetic paths and transcripts in committed fixtures. ## Migration and acceptance gates -1. **Identity/evidence contract.** Add pure types and unit tests for invalid identity, - serialization and unknown/partial observations. Adapt `WorkProject` with an optional - identity for the unassigned case, preserving its existing String UI ID and initializer - compatibility. Keep the builder algorithm and all action inputs unchanged. Run existing - WorkProject/GitAssessmentState/selection tests and inspect the Work UI with the built app. -2. **Storage membership.** Introduce typed membership and a read-only composer. Verify - nested roots, same-prefix sibling paths, unassigned assets, ambiguous ownership, +1. **Identity/evidence contract.** Add pure identity/provenance/coverage types and the + snapshot envelope, then one Work workspace producer adapter. Test real `SessionIndexEntry` + fixtures through that adapter: a recorded workspace creates the expected positive + evidence and exact source/run/record reference; inferred ancestor mapping remains + distinct; an empty workspace creates none. Verify repeat adaptation preserves references, + a new run changes provenance, and missing timestamps remain unknown. Test partial/failed/ + not-collected snapshots without manufacturing evidence from collection failures. + Check invalid identity, serialization and snapshot/reference consistency. Adapt + `WorkProject` with an optional identity for the unassigned case, preserving its String UI ID and initializer + compatibility. Keep the builder algorithm and all action inputs unchanged. Assert + identical IDs, row equality, ordering, selection and UI output for the same fixtures; + run existing WorkProject/GitAssessmentState/selection tests and inspect the Work UI with + the built app. Unused evidence scaffolding does not satisfy this gate. Storage is excluded. +2. **Storage membership.** First preserve validity in the raw decoder/DTO and test valid + zero versus missing, malformed, non-finite, overflow and timed-out measurements. Then + introduce validated typed membership and a read-only composer; no supporting evidence + means unresolved attribution. Verify nested roots, same-prefix sibling paths, + unassigned assets, ambiguous ownership, symlinks, partial scans and overlap accounting. Work and Storage reference the same project key. UI explains attribution; cleanup still goes through its current preview. -3. **Runtime and touches.** Reuse fileaccess and add bounded runtime path evidence. Verify - PID reuse, stale observations, denied probes and partial transcript coverage. Demonstrate +3. **Runtime and touches.** Extend the process producer/DTO with start identity and collection + context, and verify that probes refer to that same instance. Reuse fileaccess and add + bounded runtime path evidence. Test PID reuse during probing, missing start identity, + stale observations, denied probes and partial transcript coverage. Verify transcript + invocation/reference evidence never becomes successful OS access evidence. Demonstrate a synthetic project's observed session/path relationship in the native app. 4. **Continuity.** Attach verified backup coverage and display-only recovery outcomes. Demonstrate restart rendering without approval resurrection; distinguish one fragment