refactor: remove Workdir cleanup content hashing
This commit is contained in:
@@ -741,22 +741,12 @@ impl RuntimeGitMaterializer {
|
||||
// Frozen evidence normalization can write the record. Keep it inside
|
||||
// use admission so observational reads cannot race physical cleanup.
|
||||
let _use_lease = self.occupancy.acquire_use(working_directory_id)?;
|
||||
self.read_binding_record(working_directory_id, true)
|
||||
}
|
||||
|
||||
fn read_binding_for_cleanup(
|
||||
&self,
|
||||
working_directory_id: &str,
|
||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||
// Cleanup already owns exclusive admission and validates the original
|
||||
// bytes. Its witnessed status write performs any canonicalization.
|
||||
self.read_binding_record(working_directory_id, false)
|
||||
self.read_binding_record(working_directory_id)
|
||||
}
|
||||
|
||||
fn read_binding_record(
|
||||
&self,
|
||||
working_directory_id: &str,
|
||||
migrate: bool,
|
||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||
let working_directory_root = self.working_directory_root(working_directory_id);
|
||||
let path = working_directory_root.join(MATERIALIZATION_RECORD);
|
||||
@@ -793,7 +783,7 @@ impl RuntimeGitMaterializer {
|
||||
"materialization evidence is invalid",
|
||||
)
|
||||
})?;
|
||||
if migrate && canonical != raw {
|
||||
if canonical != raw {
|
||||
let temporary = path.with_extension(format!("migration-{}.tmp", uuid::Uuid::now_v7()));
|
||||
let result = (|| -> std::io::Result<()> {
|
||||
let mut file = fs::OpenOptions::new()
|
||||
@@ -4783,9 +4773,16 @@ mod tests {
|
||||
materializer.read_binding(id).unwrap_err().code,
|
||||
"working_directory_cleanup_resource_busy"
|
||||
);
|
||||
materializer.read_binding_for_cleanup(id).unwrap();
|
||||
assert_eq!(fs::read(&path).unwrap(), original);
|
||||
drop(cleanup);
|
||||
// Actual cleanup admission reads the legacy record without rewriting it
|
||||
// before change protection has admitted removal.
|
||||
fs::write(binding.root.join("README.md"), "preserve change").unwrap();
|
||||
assert_eq!(
|
||||
materializer.cleanup_working_directory(id).unwrap_err().code,
|
||||
"working_directory_cleanup_changes_present"
|
||||
);
|
||||
assert_eq!(fs::read(&path).unwrap(), original);
|
||||
git(binding.root(), &["restore", "README.md"]);
|
||||
|
||||
materializer.read_binding(id).unwrap();
|
||||
let canonical: serde_json::Value =
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -46,25 +46,96 @@ retry can reconcile the provider's current state.
|
||||
|
||||
A retained partial-removal Workdir is listed as `cleanup_pending` with cleanliness
|
||||
`unknown`. List/detail observation checks its saved identity but does not read or
|
||||
hash surviving checkout content. The saved removal witness is not a current
|
||||
cleanliness assertion; only an actual removal retry runs the full content checks.
|
||||
hash surviving checkout content. Saved removal authority is not a current
|
||||
cleanliness assertion; an actual removal retry rechecks current safety conditions
|
||||
and the metadata of surviving entries.
|
||||
|
||||
## Runtime safety boundary
|
||||
|
||||
The Runtime keeps removal content/identity evidence outside the tree being
|
||||
removed. This is a factual witness for partial deletion, not a new recovery
|
||||
state or cleanup stage. Surviving files are rechecked against it; an intact,
|
||||
currently clean checkout can renew obsolete evidence without discarding newly
|
||||
ignored/untracked content. Do not edit this witness manually.
|
||||
The Runtime keeps removal identity and metadata evidence outside the tree being
|
||||
removed, in `.cleanup-authority`. This is factual authority for partial deletion,
|
||||
not a new recovery state or cleanup stage. Do not edit it manually.
|
||||
|
||||
### Metadata-only removal authority
|
||||
|
||||
Initial cleanup, ordinary retry, partial-removal recovery and each pre-unlink
|
||||
check use descriptor-relative entry metadata, without following symlinks or
|
||||
crossing mount boundaries. Cleanup does not read regular-file bodies or compute
|
||||
content hashes, and does not create, refresh or compare durable content digests.
|
||||
The inventory records:
|
||||
|
||||
- entry identity (device/inode) and filesystem type;
|
||||
- executable bits for regular files, plus size and modification time with
|
||||
nanosecond precision for regular files and symlinks;
|
||||
- identity and type only for directories. Directory size, permissions and
|
||||
timestamps are not a change witness: cleanup's own child unlinks change
|
||||
directory timestamps.
|
||||
|
||||
This is metadata change protection, not a byte-equality guarantee. Deliberate
|
||||
same-size writes that restore the recorded timestamp are not promised to be
|
||||
detected. Symlink targets are not traversed or read for a content digest.
|
||||
Necessary entry enumeration, metadata inspection and unlink remain; their work
|
||||
is not proportional to the total regular-file body size. Normal Git inspection
|
||||
may still read repository content, and the Runtime still reads its small managed
|
||||
metadata records. Neither is a cleanup-specific full-content scan.
|
||||
|
||||
### Clean checkout and ordinary retry
|
||||
|
||||
Initial admission and metadata renewal from a usable checkout still require the
|
||||
normal Git dirty check, including non-ignored untracked files. Dirty content is
|
||||
not authorized for removal. Ignored entries already present at the initial clean
|
||||
attempt are included in that attempt's metadata authority; Git ignoring a path
|
||||
alone is not permission to discard later changes to it.
|
||||
|
||||
On retry with an intact, usable Git checkout, new or metadata-changed ignored
|
||||
entries and entries outside the tracked checkout/Git set are protected with
|
||||
`changes_present`. A currently clean checkout may renew tracked-file and Git-entry
|
||||
metadata only after the same Workdir root, checkout and Git identities and
|
||||
materialization ownership are confirmed. Clean Git does not renew authority for
|
||||
changed ignored/outside entries. A replaced Workdir root, checkout or Git directory,
|
||||
or substituted ownership, is rejected with `ownership_unknown`.
|
||||
|
||||
With the new metadata format, surviving entries that match a subset of the
|
||||
persisted metadata authority can be removed after partial deletion even when Git
|
||||
is no longer usable or present. Any surviving Runtime materialization record must
|
||||
still be validated as the same materialization. Missing entries do not invalidate
|
||||
that authority, and directory timestamp changes from earlier unlinks do not block
|
||||
retry. If new entries or metadata changes break the subset match and usable Git
|
||||
is unavailable, the partial cleanup returns `changes_unknown`, not
|
||||
`changes_present`: it cannot establish current change safety or renew the
|
||||
metadata authority. Unknown partial residue without metadata authority likewise
|
||||
returns `changes_unknown`; a prior failed attempt is not proof of cleanliness.
|
||||
Validated metadata-only residue (the Runtime materialization record and, at most,
|
||||
an empty checkout directory) may finish without Git.
|
||||
|
||||
### Existing digest-based authority
|
||||
|
||||
Old `.cleanup-authority` entries may contain `digest` fields. Those digests are
|
||||
ignored/discarded, not recomputed or compared, and do not authorize residual file
|
||||
content under the new contract. Migration can establish new metadata authority
|
||||
only with a usable current clean Git checkout, the same root, checkout and Git
|
||||
identities, and validated materialization ownership. Even then, surviving ignored
|
||||
entries or other entries outside the tracked checkout/Git set block migration
|
||||
with `changes_unknown`: legacy entries have no size/mtime metadata against which
|
||||
to verify these non-tracked survivors. Clean Git alone cannot authorize them.
|
||||
|
||||
An incomplete legacy cleanup cannot use the new-format subset rule. Residual
|
||||
files without usable clean Git return `changes_unknown`; they are not adopted,
|
||||
and digest regeneration is not required. Metadata-only residue may finish only
|
||||
with validated saved ownership, confirmed remaining identities, and validation
|
||||
of any surviving Runtime materialization record. An empty same-identity root can
|
||||
finish even when its record was already removed. Replaced root/checkout/Git identities or substituted ownership remain
|
||||
`ownership_unknown`. Ordinary retry and Runtime restart use these same rules;
|
||||
manual metadata edits and a new recovery lifecycle are not required.
|
||||
|
||||
Mount-safe removal currently requires Linux `openat2` and `/proc` inspection.
|
||||
Unavailable inspection fails closed with `mount_check_unavailable`; there is no
|
||||
fallback to recursive deletion across an unchecked filesystem boundary.
|
||||
Runtime HTTP list, detail and removal requests run synchronous provider work on
|
||||
blocking workers rather than the HTTP executor. Removal reserves only the target
|
||||
Workdir under the Runtime state mutex, then releases that mutex before content
|
||||
inspection and unlink. Unrelated Worker list/detail, session observation and
|
||||
subscription snapshots remain available while removal runs. Conflicting use,
|
||||
Workdir under the Runtime state mutex, then releases that mutex before metadata
|
||||
and Git inspection and unlink. Unrelated Worker list/detail, session observation
|
||||
and subscription snapshots remain available while removal runs. Conflicting use,
|
||||
attachment, creation or removal of the same Workdir is rejected promptly rather
|
||||
than waiting under the global mutex.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user