Skip to content

fix(cli): destination link_count is observed, not constrained — completes d75f50d5 - #176

Merged
marlon-costa-dc merged 2 commits into
0.12.0-devfrom
fix/atomic-file-destination-hardlinks
Sep 20, 2026
Merged

marlon-costa-dc merged 2 commits into
0.12.0-devfrom
fix/atomic-file-destination-hardlinks

Conversation

@marlon-costa-dc

@marlon-costa-dc marlon-costa-dc commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Problem (measured in ai-hub CI)

d75f50d relaxed the OSError law — hardlinked destinations are safe to read and replace — but the typed AtomicFileState model still pinned link_count: ge=1, le=1. Any read of a package-manager-hardlinked file (uv hardlinks wheel content) then died in pydantic: link_count: Input should be less than or equal to 1, input_value=2. Symptom: gen fixed point red for every ai-hub PR (Binary read failed … templates/bootstrap/mise).

Change

  • AtomicFileState.link_count: ge=1 (observed reality) — staged-temp exclusivity stays enforced by _validate_exclusive_link (OSError, EMLINK).
  • Physical-tree inventory: files with st_nlink > 1 now fail the cleanup-authority verb as a causal typed failure (cleanup authority refused: … another physical name owns the same inode) instead of a pydantic accident — the documented refusal, preserved.
  • Contract tests updated to the law: hard destinations read (link_count=2 captured) and replace (pathname-only, owner untouched); symlinks still rejected.

Evidence

Atomic suite: 65 passed (contract + physical-tree + directory suites); ruff check/format clean.


Summary by cubic

Fixes gen fixed point failures from hardlinked destinations: reading a package-manager-hardlinked file previously crashed in pydantic because AtomicFileState.link_count was capped at 1, but the model now records the observed count (>= 1). Cleanup authority over multi-link files is still refused, now as an explicit OSError (EMLINK) instead of an incidental pydantic error.

  • Staged-temp exclusivity stays enforced by _validate_exclusive_link.
  • Hardlinked reads capture link_count=2; replacing a hardlinked destination swaps only that pathname, leaving the owner inode and bytes intact.
  • Symlinked destinations remain rejected.
  • Contract tests now cover hard-read, hard-replace with owner inode/byte preservation, and symlink rejection.

Written for commit 5e44283. Summary will update on new commits.

Review in cubic

Marlon Costa added 2 commits September 19, 2026 19:23
…ink-safe reads and replaces complete d75f50d

d75f50d relaxed the OSError law (hardlinked destinations are safe to
read and replace) but the typed AtomicFileState model still pinned
link_count <= 1, so any read of a package-manager-hardlinked file died
in pydantic ('Input should be less than or equal to 1') — the gen
bootstrap splice over uv-hardlinked wheel content.

The model now records the observed link count (>= 1); staged-temp
exclusivity stays enforced by _validate_exclusive_link, and the
physical-tree inventory keeps its cleanup-authority refusal — now as a
causal typed failure instead of a pydantic accident. Contract tests
updated to the law: hard destinations read (link_count=2 captured) and
replace (pathname-only), symlinks still rejected.
@sonarqubecloud

Copy link
Copy Markdown

@marlon-costa-dc
marlon-costa-dc merged commit 45a706f into 0.12.0-dev Sep 20, 2026
5 of 6 checks passed
@marlon-costa-dc
marlon-costa-dc deleted the fix/atomic-file-destination-hardlinks branch September 20, 2026 02:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant