aeat.core.paths module¶
Shared path normalization and containment helpers.
Centralises the small set of Path primitives that every other
aeat module needs: resolving repo-relative paths against
PROJECT_ROOT via resolve_project_path(), normalising user-provided
settings with normalize_project_relative_path(), and safely resolving
caller-provided sub-paths under a fixed root without allowing path-traversal
escapes.
The containment helpers (resolve_relative_subpath() and
resolve_record_json_path()) refuse backslashes, parent references,
absolute components, and any resolved path that escapes the owning root. They
raise CoreValidationError and are the load-bearing
defence against caller-controlled identifier injection on the on-disk store
paths.
These helpers validate and compose paths only. They do not read, write, create, or secure files; persistence adapters that need registered storage errors wrap this module in their own typed containment layer.
is_windows_long_path_error(), windows_long_paths_enabled(), and
windows_storage_root_long_path_margin() are the Windows MAX_PATH
(260-character) hardening surface: classifying an OSError that legacy
Windows raises once a resolved path exceeds the limit, reading the
machine-wide long-path opt-in, and computing whether a candidate storage root
leaves enough headroom for the deepest object path the bucket / outbound
storage layout can produce.
- PROJECT_ROOT¶
Absolute filesystem path to the repository root.
Used to anchor repo-relative defaults. It is not the process cwd and not a runtime storage root selected from settings.
- WINDOWS_MAX_PATH: int¶
Legacy Windows
CreateFileWpath-length ceiling in UTF-16 code units. Windows 10 1607+ can lift this per-application via theLongPathsEnabledregistry value combined with alongPathAwareapplication manifest; a workstation that has neither still enforces this ceiling.
- WINDOWS_WORST_CASE_OBJECT_PATH_SUFFIX_LENGTH: int¶
Worst-case path suffix (leading separator through file extension) that the bucket-directory layout can append below a configured storage root:
\buckets\<uuid-36>\blobs\<hmac-8>--<label-64>.meta.json. Mirrorsaeat.adapters.persistence.storage._namespace_registry.BUCKETS_DIRNAME/BUCKET_BLOBS_DIRNAMEand the outboundLocalFileSystemProviderfilename shape (<hmac_prefix_8>--<label>.meta.json,labelcapped at 64 chars). Kept as a literal here (not imported) because this module sits below the persistence and outbound-storage layers in the dependency graph; the two call sites that use this constant assert their real deepest-suffix shapes against it in tests.
- is_windows_long_path_error(exc)[source]¶
Return whether
excis a Windows path-length-ceiling failure.Classifies
WinError 3(ERROR_PATH_NOT_FOUND) andWinError 206(ERROR_FILENAME_EXCED_RANGE) — the two concrete Windows API error codes a legacy (non long-path-aware) workstation raises once a resolved path walks pastWINDOWS_MAX_PATH. AlwaysFalseon non-Windows platforms and for any otherOSError, so callers can unconditionally probe every caughtOSErrorwithout a platform guard of their own.- Parameters:
exc (
OSError) – The caughtOSError(or subclass, e.g.FileNotFoundError) to classify.- Return type:
- Returns:
Truewhenexc.winerrornames a known long-path failure.
- windows_long_paths_enabled()[source]¶
Report the machine-wide Windows long-path opt-in, if determinable.
Reads
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem \LongPathsEnabled— the registry value Windows 10 1607+ consults to liftWINDOWS_MAX_PATHfor manifest-declared long-path-aware applications (this CLI is built with alongPathAwaremanifest via its packaging).- Return type:
- Returns:
Truewhen the value is present and non-zero,Falsewhen present and zero (or when the platform is Windows but the value is absent — the pre-1607 / not-yet-opted-in default), andNoneon a non-Windows platform where the concept does not apply, or when the registry cannot be read at all (a probe is best-effort; it never raises).
- windows_storage_root_long_path_margin(root)[source]¶
Return the headroom, in characters, before an object write risks
MAX_PATH.Computes
WINDOWS_MAX_PATH - len(str(root.resolve())) - WINDOWS_WORST_CASE_OBJECT_PATH_SUFFIX_LENGTH. A positive result is the number of characters of slack remaining; zero or negative means the deepest object the bucket / outbound-storage layout can write already meets or exceeds the legacyMAX_PATHceiling from this root. Platform-independent by design: the margin is informative on every OS, but only Windows without the long-path opt-in enforces the ceiling it measures against.- Parameters:
root (
Path) – The candidate storage root (aeat_local_storage_rootor an outbound-storage provider root).- Return type:
- Returns:
The signed character margin described above.
- resolve_project_path(value)[source]¶
Resolve a repo-relative path against
PROJECT_ROOT.Absolute paths are returned as absolute resolved paths. Relative paths are interpreted as repository-relative, not cwd-relative, which keeps config defaults stable regardless of where the CLI process starts. This helper is not a containment guard: callers that accept subpaths under an owning root should use
resolve_relative_subpath().- Parameters:
value (
str|Path) – An absolute or repo-relative path; user-style~references are expanded.- Return type:
Path- Returns:
The fully resolved absolute
pathlib.Path.
- normalize_project_relative_path(value)[source]¶
Normalise an optional path setting to an absolute repo-rooted path.
Used by settings validators for optional path fields. It preserves
Noneand delegates path semantics toresolve_project_path(); it does not verify that the resulting path exists.- Parameters:
value (
Path|None) – Optional configured path, orNone.- Return type:
Path|None- Returns:
NonewhenvalueisNone; otherwise the resolved absolute path produced byresolve_project_path().
- resolve_relative_subpath(root, relative_path, *, context)[source]¶
Resolve
relative_pathunderrootand enforce containment.The returned path is resolved and proven to stay under
rootafter normalization. The helper performs no filesystem mutation and does not assert that the target exists; callers decide whether a missing file is valid for their operation.- Parameters:
root (
Path) – The fixed parent directory that the result must live under.relative_path (
str) – A POSIX-style sub-path supplied by an untrusted-ish caller. Backslashes, absolute components, empty parts,.and..parts are all rejected.context (
str) – Short human-readable label used in raised error messages so the caller can attribute the failure.
- Return type:
Path- Returns:
The resolved absolute path inside
root.- Raises:
CoreValidationError – When
relative_pathis malformed or when the resolved path escapesroot.
- resolve_record_json_path(root, record_id, *, context)[source]¶
Resolve a file-backed record id to
<root>/<record_id>.jsonsafely.The token allow-list prevents path separators, dot components, and overlong filename material from becoming a filesystem path. It is not domain-id validation: callers still own UUID/modelo/CSV/hash shape checks before they choose
record_id.- Parameters:
root (
Path) – The directory that owns the JSON sidecar files.record_id (
str) – A simple filename token. Must match a strict[A-Za-z0-9][A-Za-z0-9._-]{0,127}shape so the resolved path cannot escaperootvia path separators or parent references.context (
str) – Short human-readable label used in raised error messages.
- Return type:
Path- Returns:
The resolved absolute path of the JSON sidecar.
- Raises:
CoreValidationError – When
record_idis not a simple filename token or the resolved path escapesroot.
- file_stat_fingerprint(path)[source]¶
Return a cache-key fingerprint triple for a single file.
The triple
(name, size_bytes, mtime_ns)is a stable, low-cost proxy for file identity used by file-backed loader caches. Any in-place modification that changes size or mtime invalidates the cache without requiring a full content hash.This is not an integrity hash or evidence digest. For byte-level verification use
aeat.core.hashing.hash_file()oraeat.core.hashing.sha256_file().