Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7a2c15e
refactor(cli): behavior-preserving simplify pass across the command l…
mikolalysenko Jul 2, 2026
68a19d2
fix(cli): unify env-bool parsing — one parser policy, scrub covers lo…
mikolalysenko Jul 2, 2026
a870d16
fix(remove): implement --dry-run — preview, no mutations
mikolalysenko Jul 2, 2026
7c74562
fix(get): --one-off fails honestly instead of silently saving anyway
mikolalysenko Jul 2, 2026
06a9b10
refactor(scan): invert the mode fold — ScanMode is the source of truth
mikolalysenko Jul 2, 2026
eeec558
refactor(core): warm-up dedups — normalize_file_path, serialize_sorte…
mikolalysenko Jul 2, 2026
18f67f4
refactor(core): consolidate 7 atomic_write copies into utils::fs::ato…
mikolalysenko Jul 2, 2026
e62e132
refactor(vendor): hoist shared leaf helpers into vendor/common.rs
mikolalysenko Jul 2, 2026
a7efc3a
fix(vendor): ungate go_h1_of_zip — minimal builds failed to compile
mikolalysenko Jul 2, 2026
24fad4d
refactor(crawlers): delegate coordinate guards to path_safety, dedup …
mikolalysenko Jul 2, 2026
0f7c8af
fix(vendor): restore ApplyResult import in composer_lock test mod
mikolalysenko Jul 2, 2026
8d05ea3
refactor(vendor-cli): collapse the 8-way dispatch into a local macro
mikolalysenko Jul 2, 2026
1e4771e
refactor(scan): extract gc into scan/gc.rs (move-only)
mikolalysenko Jul 2, 2026
1c95e66
refactor(scan): extract discovery into scan/discovery.rs (move-only)
mikolalysenko Jul 2, 2026
a5f9b3d
refactor(scan): extract hosted into scan/hosted.rs (move-only)
mikolalysenko Jul 2, 2026
404a353
refactor(scan): extract vendor_flow into scan/vendor_flow.rs (move-only)
mikolalysenko Jul 2, 2026
df322c4
refactor(scan): add module docs to scan/mod.rs, tidy blank line (move…
mikolalysenko Jul 2, 2026
3a88093
docs: sync CLI_CONTRACT + README with reality — unlock documented, ta…
mikolalysenko Jul 2, 2026
207a650
docs: fix four more README/contract inaccuracies found during the sync
mikolalysenko Jul 2, 2026
8ad9b90
fix: address all five confirmed findings from the adversarial review
mikolalysenko Jul 2, 2026
a27fe16
cleanup pass, wip
mikolalysenko Jul 3, 2026
d6f1c85
chore: remove accidentally committed .DS_Store files
mikolalysenko Jul 3, 2026
05d8d66
wip clean up
mikolalysenko Jul 3, 2026
87dcb28
.
mikolalysenko Jul 3, 2026
7ded025
refactor: remove all ecosystem feature flags — every ecosystem always…
mikolalysenko Jul 3, 2026
c70d7ea
Merge branch 'worktree-remove-feature-flags' into refactor/core-dedups
mikolalysenko Jul 4, 2026
04a8a2b
clean up pass complete
mikolalysenko Jul 4, 2026
d36b5d7
Merge branch 'worktree-remove-feature-flags' into refactor/core-dedups
mikolalysenko Jul 4, 2026
130ef54
follow up bug fix sweep
mikolalysenko Jul 4, 2026
6487b21
bug fix sweep
mikolalysenko Jul 6, 2026
73439b3
commit changes so far
mikolalysenko Jul 6, 2026
8f4d150
fix CI: admit maven at the vendor service gate; clippy manual_strip
mikolalysenko Jul 6, 2026
5dae1dc
fix: 10 prod bugs + 7 test-harness bugs surfaced by a no-fail-fast fu…
mikolalysenko Jul 7, 2026
cea6084
fix(windows CI): docker_vendor_common_selftest ran against the WSL ba…
mikolalysenko Jul 7, 2026
d81fb8b
fix(windows): maven package_path mixed OS separator into the reported…
mikolalysenko Jul 7, 2026
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
Prev Previous commit
Next Next commit
refactor(core): consolidate 7 atomic_write copies into utils::fs::ato…
…mic_write_bytes

pth_hook/edit, package_json/update, composer_setup, gem_setup/update,
go_redirect, patch/apply (write_atomic), and manifest/operations
(write_manifest's inline block) were byte-identical reimplementations of
the stage+fsync+rename+dir-fsync pattern; each becomes a 2-line
delegating wrapper keeping its per-file 'why this must not tear' doc
(the go_mod_edit precedent). blob_fetcher's write_cache_entry_atomic is
deliberately exempt (no fsync — re-downloadable content-addressed cache)
and now says so, so the next dedup pass doesn't flag it. ~−230 lines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
  • Loading branch information
mikolalysenko and claude committed Jul 2, 2026
commit 18f67f4fb6a1a0590f173ea619aab22e8d930731
Binary file modified crates/.DS_Store
Binary file not shown.
Binary file modified crates/socket-patch-core/.DS_Store
Binary file not shown.
6 changes: 6 additions & 0 deletions crates/socket-patch-core/src/api/blob_fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,12 @@ pub fn format_fetch_result(result: &FetchMissingBlobsResult) -> String {
/// makes the final path always either the complete bytes or absent, never a
/// torn intermediate, matching the stage+rename discipline used by the
/// patch-apply and copy-on-write write paths.
///
/// Deliberately LIGHTER than [`crate::utils::fs::atomic_write_bytes`] (no
/// file fsync, no dir fsync, `.socket-dl-` prefix): these are re-downloadable
/// content-addressed cache entries, not user-owned files — post-crash loss
/// of a cache entry is harmless, so the extra durability isn't worth the
/// I/O. Do not "consolidate" this into the hardened writer.
async fn write_cache_entry_atomic(dest: &Path, bytes: &[u8]) -> std::io::Result<()> {
let parent = dest.parent().ok_or_else(|| {
std::io::Error::new(
Expand Down
50 changes: 4 additions & 46 deletions crates/socket-patch-core/src/composer_setup/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,53 +266,11 @@ fn remove_command_from_event(scripts: &mut Map<String, Value>, event: &str) -> b

/// Atomically write `content` to `path`.
///
/// A bare `fs::write` truncates the target before writing, so a crash, power
/// loss, or interrupted process mid-write would leave the user's committed
/// `composer.json` truncated or empty — destroying the file we only meant to
/// append two script events to. Instead we write to a sibling stage file,
/// fsync it, then rename over the target (rename is atomic on the same
/// filesystem) so a reader ever sees either the old bytes or the complete new
/// bytes. Mirrors the hardened writer in `package_json/update.rs`.
/// The user's committed `composer.json` must never be left torn by a crash
/// mid-write when we only meant to append two script events. Delegates to
/// the crate-wide hardened writer.
async fn atomic_write(path: &Path, content: &str) -> std::io::Result<()> {
let parent = path.parent().unwrap_or_else(|| Path::new("."));
let stem = path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| "composer.json".to_string());
let stage = parent.join(format!(".socket-stage-{}-{}", stem, uuid::Uuid::new_v4()));

let mut file = tokio::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&stage)
.await?;

use tokio::io::AsyncWriteExt;
if let Err(e) = file.write_all(content.as_bytes()).await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}
if let Err(e) = file.sync_all().await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}
drop(file);

if let Err(e) = tokio::fs::rename(&stage, path).await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}

// The rename only updated the parent directory entry; fsync the directory
// so the rename itself survives a crash. Best-effort, Unix only.
#[cfg(unix)]
{
if let Ok(dir) = tokio::fs::File::open(parent).await {
let _ = dir.sync_all().await;
}
}

Ok(())
crate::utils::fs::atomic_write_bytes(path, content.as_bytes()).await
}

/// Wire the project: append our command to the composer script events.
Expand Down
50 changes: 4 additions & 46 deletions crates/socket-patch-core/src/gem_setup/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,53 +61,11 @@ impl GemEditResult {

/// Atomically write `content` to `path`.
///
/// A bare `fs::write` truncates the target before writing, so a crash, power
/// loss, or interrupted process mid-write would leave the user's committed
/// `Gemfile` truncated or empty — destroying the file we only meant to
/// append a three-line block to. Instead we write to a sibling stage file,
/// fsync it, then rename over the target (rename is atomic on the same
/// filesystem) so a reader ever sees either the old bytes or the complete new
/// bytes. Mirrors the hardened writer in `composer_setup` / `package_json`.
/// The user's committed `Gemfile` must never be left torn by a crash
/// mid-write when we only meant to append a three-line block. Delegates to
/// the crate-wide hardened writer.
async fn atomic_write(path: &Path, content: &str) -> std::io::Result<()> {
let parent = path.parent().unwrap_or_else(|| Path::new("."));
let stem = path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| "Gemfile".to_string());
let stage = parent.join(format!(".socket-stage-{}-{}", stem, uuid::Uuid::new_v4()));

let mut file = tokio::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&stage)
.await?;

use tokio::io::AsyncWriteExt;
if let Err(e) = file.write_all(content.as_bytes()).await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}
if let Err(e) = file.sync_all().await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}
drop(file);

if let Err(e) = tokio::fs::rename(&stage, path).await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}

// The rename only updated the parent directory entry; fsync the directory
// so the rename itself survives a crash. Best-effort, Unix only.
#[cfg(unix)]
{
if let Ok(dir) = tokio::fs::File::open(parent).await {
let _ = dir.sync_all().await;
}
}

Ok(())
crate::utils::fs::atomic_write_bytes(path, content.as_bytes()).await
}

/// Stable substring identifying our managed block — `setup --check` and the
Expand Down
43 changes: 1 addition & 42 deletions crates/socket-patch-core/src/manifest/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,48 +104,7 @@ pub async fn write_manifest(
let path = path.as_ref();
let content = serde_json::to_string_pretty(manifest)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;

let parent = path.parent().unwrap_or_else(|| Path::new("."));
let stem = path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| "manifest.json".to_string());
let stage = parent.join(format!(".socket-stage-{}-{}", stem, uuid::Uuid::new_v4()));

let mut file = tokio::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&stage)
.await?;

use tokio::io::AsyncWriteExt;
if let Err(e) = file.write_all(content.as_bytes()).await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}
if let Err(e) = file.sync_all().await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}
drop(file);

if let Err(e) = tokio::fs::rename(&stage, path).await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}

// Durability: `sync_all` flushed the file's data, but the rename only
// updated the parent directory entry. fsync the directory so the rename
// itself survives a crash. Unix only; best-effort, since a directory we
// can't open for fsync must not fail an otherwise-successful write.
#[cfg(unix)]
{
if let Ok(dir) = tokio::fs::File::open(parent).await {
let _ = dir.sync_all().await;
}
}

Ok(())
crate::utils::fs::atomic_write_bytes(path, content.as_bytes()).await
}

#[cfg(test)]
Expand Down
50 changes: 4 additions & 46 deletions crates/socket-patch-core/src/package_json/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,53 +8,11 @@ use super::detect::{

/// Atomically write `content` to `path`.
///
/// A bare `fs::write` truncates the target before writing, so a crash, power
/// loss, or interrupted process mid-write would leave the user's
/// `package.json` truncated or empty — destroying the file we only meant to
/// append two scripts to. Instead we write to a sibling stage file, fsync it,
/// then rename over the target (rename is atomic on the same filesystem) so the
/// reader ever sees either the old bytes or the complete new bytes. Mirrors the
/// hardened writer in `manifest/operations.rs`.
/// The user's `package.json` must never be left torn by a crash mid-write
/// when we only meant to append two scripts. Delegates to the crate-wide
/// hardened writer.
async fn atomic_write(path: &Path, content: &str) -> std::io::Result<()> {
let parent = path.parent().unwrap_or_else(|| Path::new("."));
let stem = path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| "package.json".to_string());
let stage = parent.join(format!(".socket-stage-{}-{}", stem, uuid::Uuid::new_v4()));

let mut file = tokio::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&stage)
.await?;

use tokio::io::AsyncWriteExt;
if let Err(e) = file.write_all(content.as_bytes()).await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}
if let Err(e) = file.sync_all().await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}
drop(file);

if let Err(e) = tokio::fs::rename(&stage, path).await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}

// The rename only updated the parent directory entry; fsync the directory
// so the rename itself survives a crash. Best-effort, Unix only.
#[cfg(unix)]
{
if let Ok(dir) = tokio::fs::File::open(parent).await {
let _ = dir.sync_all().await;
}
}

Ok(())
crate::utils::fs::atomic_write_bytes(path, content.as_bytes()).await
}

/// Result of updating a single package.json.
Expand Down
59 changes: 5 additions & 54 deletions crates/socket-patch-core/src/patch/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,61 +553,12 @@ async fn nearest_existing_ancestor(path: &Path) -> Option<&Path> {
None
}

/// Write `content` to `target` atomically via stage + rename.
///
/// Two-phase commit:
/// 1. Create `<parent>/.socket-stage-<filename>-<uuid>` (leading dot
/// so editor globs ignore it; uuid suffix so concurrent callers
/// never collide — defense in depth on top of the apply lock).
/// 2. `write_all` the content, then `sync_all()` so the bytes are
/// durably on disk before the rename.
/// 3. `rename(stage, target)` — atomic on POSIX, best-effort on
/// Windows. On failure unlink the stage so we don't leave a
/// dotfile behind in the package directory.
/// Write `content` to `target` atomically via stage + rename, so a patched
/// package file is only ever seen as the complete old or complete new
/// bytes. Delegates to the crate-wide hardened writer (permissions/chown
/// are restored separately by [`restore_file_permissions`]).
async fn write_atomic(target: &Path, content: &[u8]) -> std::io::Result<()> {
let parent = target.parent().unwrap_or_else(|| Path::new("."));
let stem = target
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| "anon".to_string());
let stage = parent.join(format!(".socket-stage-{}-{}", stem, uuid::Uuid::new_v4()));

let mut file = tokio::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&stage)
.await?;

use tokio::io::AsyncWriteExt;
if let Err(e) = file.write_all(content).await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}
if let Err(e) = file.sync_all().await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}
drop(file);

if let Err(e) = tokio::fs::rename(&stage, target).await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}

// Durability: `sync_all` above flushed the file's *data*, but the
// rename only updated the parent directory entry. fsync the
// directory so the rename itself survives a crash — otherwise a
// post-crash filesystem could surface the old name (or neither).
// Unix only; best-effort, since a directory we can't open for fsync
// must not fail an otherwise-successful write.
#[cfg(unix)]
{
if let Ok(dir) = tokio::fs::File::open(parent).await {
let _ = dir.sync_all().await;
}
}

Ok(())
crate::utils::fs::atomic_write_bytes(target, content).await
}

/// Restore the post-write permission state on `filepath`.
Expand Down
48 changes: 5 additions & 43 deletions crates/socket-patch-core/src/patch/go_redirect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,50 +540,12 @@ pub(crate) async fn ensure_module_go_mod(copy_dir: &Path, module: &str) -> std::
atomic_write(&go_mod, format!("module {module}\n").as_bytes()).await
}

/// Atomically commit `content` to `path` via stage + fsync + rename. Mirrors the
/// hardened writer in [`crate::patch::go_mod_edit`]: a reader/recovering process
/// only ever sees the complete old or complete new bytes, never a truncated
/// intermediate.
/// Atomically commit `content` to `path` via stage + fsync + rename, so a
/// reader/recovering process only ever sees the complete old or complete
/// new bytes of a synthesized `go.mod`. Delegates to the crate-wide
/// hardened writer.
async fn atomic_write(path: &Path, content: &[u8]) -> std::io::Result<()> {
let parent = path.parent().unwrap_or_else(|| Path::new("."));
let stem = path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| "go.mod".to_string());
let stage = parent.join(format!(".socket-stage-{}-{}", stem, uuid::Uuid::new_v4()));

let mut file = tokio::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&stage)
.await?;

use tokio::io::AsyncWriteExt;
if let Err(e) = file.write_all(content).await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}
if let Err(e) = file.sync_all().await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}
drop(file);

if let Err(e) = tokio::fs::rename(&stage, path).await {
let _ = tokio::fs::remove_file(&stage).await;
return Err(e);
}

// The rename only updated the parent directory entry; fsync the directory so
// the rename itself survives a crash. Best-effort, Unix only.
#[cfg(unix)]
{
if let Ok(dir) = tokio::fs::File::open(parent).await {
let _ = dir.sync_all().await;
}
}

Ok(())
crate::utils::fs::atomic_write_bytes(path, content).await
}

fn synthesized_result(
Expand Down
Loading