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
|
// Frozen evidence normalization can write the record. Keep it inside
|
||||||
// use admission so observational reads cannot race physical cleanup.
|
// use admission so observational reads cannot race physical cleanup.
|
||||||
let _use_lease = self.occupancy.acquire_use(working_directory_id)?;
|
let _use_lease = self.occupancy.acquire_use(working_directory_id)?;
|
||||||
self.read_binding_record(working_directory_id, true)
|
self.read_binding_record(working_directory_id)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_binding_record(
|
fn read_binding_record(
|
||||||
&self,
|
&self,
|
||||||
working_directory_id: &str,
|
working_directory_id: &str,
|
||||||
migrate: bool,
|
|
||||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||||
let working_directory_root = self.working_directory_root(working_directory_id);
|
let working_directory_root = self.working_directory_root(working_directory_id);
|
||||||
let path = working_directory_root.join(MATERIALIZATION_RECORD);
|
let path = working_directory_root.join(MATERIALIZATION_RECORD);
|
||||||
@@ -793,7 +783,7 @@ impl RuntimeGitMaterializer {
|
|||||||
"materialization evidence is invalid",
|
"materialization evidence is invalid",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
if migrate && canonical != raw {
|
if canonical != raw {
|
||||||
let temporary = path.with_extension(format!("migration-{}.tmp", uuid::Uuid::now_v7()));
|
let temporary = path.with_extension(format!("migration-{}.tmp", uuid::Uuid::now_v7()));
|
||||||
let result = (|| -> std::io::Result<()> {
|
let result = (|| -> std::io::Result<()> {
|
||||||
let mut file = fs::OpenOptions::new()
|
let mut file = fs::OpenOptions::new()
|
||||||
@@ -4783,9 +4773,16 @@ mod tests {
|
|||||||
materializer.read_binding(id).unwrap_err().code,
|
materializer.read_binding(id).unwrap_err().code,
|
||||||
"working_directory_cleanup_resource_busy"
|
"working_directory_cleanup_resource_busy"
|
||||||
);
|
);
|
||||||
materializer.read_binding_for_cleanup(id).unwrap();
|
|
||||||
assert_eq!(fs::read(&path).unwrap(), original);
|
|
||||||
drop(cleanup);
|
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();
|
materializer.read_binding(id).unwrap();
|
||||||
let canonical: serde_json::Value =
|
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
|
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
|
`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
|
hash surviving checkout content. Saved removal authority is not a current
|
||||||
cleanliness assertion; only an actual removal retry runs the full content checks.
|
cleanliness assertion; an actual removal retry rechecks current safety conditions
|
||||||
|
and the metadata of surviving entries.
|
||||||
|
|
||||||
## Runtime safety boundary
|
## Runtime safety boundary
|
||||||
|
|
||||||
The Runtime keeps removal content/identity evidence outside the tree being
|
The Runtime keeps removal identity and metadata evidence outside the tree being
|
||||||
removed. This is a factual witness for partial deletion, not a new recovery
|
removed, in `.cleanup-authority`. This is factual authority for partial deletion,
|
||||||
state or cleanup stage. Surviving files are rechecked against it; an intact,
|
not a new recovery state or cleanup stage. Do not edit it manually.
|
||||||
currently clean checkout can renew obsolete evidence without discarding newly
|
|
||||||
ignored/untracked content. Do not edit this witness 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.
|
Mount-safe removal currently requires Linux `openat2` and `/proc` inspection.
|
||||||
Unavailable inspection fails closed with `mount_check_unavailable`; there is no
|
Unavailable inspection fails closed with `mount_check_unavailable`; there is no
|
||||||
fallback to recursive deletion across an unchecked filesystem boundary.
|
fallback to recursive deletion across an unchecked filesystem boundary.
|
||||||
Runtime HTTP list, detail and removal requests run synchronous provider work on
|
Runtime HTTP list, detail and removal requests run synchronous provider work on
|
||||||
blocking workers rather than the HTTP executor. Removal reserves only the target
|
blocking workers rather than the HTTP executor. Removal reserves only the target
|
||||||
Workdir under the Runtime state mutex, then releases that mutex before content
|
Workdir under the Runtime state mutex, then releases that mutex before metadata
|
||||||
inspection and unlink. Unrelated Worker list/detail, session observation and
|
and Git inspection and unlink. Unrelated Worker list/detail, session observation
|
||||||
subscription snapshots remain available while removal runs. Conflicting use,
|
and subscription snapshots remain available while removal runs. Conflicting use,
|
||||||
attachment, creation or removal of the same Workdir is rejected promptly rather
|
attachment, creation or removal of the same Workdir is rejected promptly rather
|
||||||
than waiting under the global mutex.
|
than waiting under the global mutex.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user