# `GgenIgniter.ArtifactIdentity`
[🔗](https://github.com/seanchatmangpt/ggen_igniter/blob/v26.9.8/lib/ggen_igniter/artifact_identity.ex#L1)

A real, first-class ARTIFACT IDENTITY primitive: turns a `(base_dir,
raw_path)` pair -- an arbitrary, possibly-relative, possibly-alias-laden
path string as it appears in a `%GgenIgniter.PendingActuation{}`'s
`target`, a `GgenIgniter.Manifest` entry's `outputs` key, or a
`GgenIgniter.Receipt`'s `files` entry -- into one CANONICAL identity
string, so two lexically different raw strings that resolve to the same
real on-disk location are recognized as the SAME identity everywhere this
pipeline reasons about "is this the same output."

## Why this module exists (a real, confirmed adversarial finding, not a
speculative hardening pass)

`.ggen_igniter_factory/redteam-concurrency-nondeterminism.md` (read fresh
before writing this module, its real reproducer re-run against the fix
below) demonstrates a REAL, reproduced defect: `:admit`'s duplicate-
output-path guard in `GgenIgniter.Reactors.ReconcileReactor` used to group
pending writes by the raw `target` STRING
(`Enum.group_by(& &1.target)`) -- never canonicalized. Two targets whose
`--out`/`to:` strings differ only by a redundant `/./` path segment (one
concrete, independently filesystem-verified example; `//`, `..`, and
symlink-based aliases are the same root cause by the same reasoning)
resolve to the SAME real inode while comparing as different Elixir
strings, silently bypassing the guard and letting `:actuate`'s real
`Task.async_stream/3` concurrency race unprotected on the shared real
file -- genuine, empirically-confirmed last-writer-wins, with the
pipeline reporting `standing: :alive` (full success) regardless of which
target's content was actually discarded.

A second real, confirmed defect closed by this module's `walk_real_path/3`:
on a case-insensitive/case-preserving filesystem (macOS default
APFS/HFS+), each existing non-symlink path segment's REAL on-disk entry
casing is now looked up via `File.ls!/1`-equivalent (`real_case_segment/2`)
rather than the caller's raw spelling being preserved verbatim -- so two
differently-cased spellings of the SAME real file (e.g. `lib/Foo.ex` vs
`lib/foo.ex` when only one really exists) canonicalize IDENTICALLY, the
same way a `/./`-alias or a symlink alias already did. On a genuinely
case-sensitive filesystem (Linux ext4) this is a strict no-op: the
case-insensitive match this correction looks for cannot exist there
because two distinct real files with different casing are two distinct
real directory entries, and `File.read_link/1` already proved the raw
spelling itself resolves.

This module closes that gap as a real, reusable, independently-tested
primitive rather than a one-off string-munge inlined into `:admit` --
see `GgenIgniter.Reactors.ReconcileReactor`'s `admit_pending/2` for the
real wiring, `GgenIgniter.PendingActuation`'s `canonical_target` field for
where every planned actuation carries its own real identity, and
`GgenIgniter.Manifest`/`GgenIgniter.Receipt`'s call sites in
`ReconcileReactor` for where the manifest's `outputs` keys and the
receipt's `files` entries are now built from this same real identity
rather than a raw path string.

## Two-tier resolution, honestly bounded

`canonicalize/2` resolves in two tiers, in order:

  1. **Lexical normalization** (`Path.expand/2`): relative-vs-absolute
     forms unified against `base_dir`, `.`/`..` segments resolved,
     repeated separators collapsed. This ALONE is what closes the exact
     `/./`-alias reproducer above (neither colliding target need exist on
     disk yet for this tier to unify them).
  2. **Real filesystem identity** (symlink/realpath resolution): for
     every path SEGMENT that already exists on disk, this module walks it
     exactly like POSIX `realpath(3)` -- resolving any symlink
     encountered (including in an INTERMEDIATE directory segment, so a
     directory reached via two different real symlinked paths still
     collapses to one identity), with a bounded expansion budget so a
     symlink cycle can never loop forever.

**Honest limit, stated plainly (per this module's own design brief):**
the moment resolution reaches a path segment that does not exist on disk
at all, real filesystem resolution stops there -- the remaining,
not-yet-existing suffix is appended to the already-resolved (real, or
lexically-expanded, whichever tier got that far) prefix VERBATIM, after
its own `.`/`..` segments were already normalized by `Path.expand/2` in
tier 1. This is the best available identity for a target that does not
exist yet: there is no real inode to resolve a not-yet-created file
against. Concretely: a brand-new file inside an EXISTING (possibly
symlinked) directory gets that directory's real identity as its prefix
(stronger than bare lexical expansion); a brand-new file whose parent
directory ALSO does not exist yet gets pure lexical normalization only,
identical to `Path.expand/2`'s own result. Two aliases of a not-yet-
existing path that would only converge via a symlink CREATED after this
function returns are, honestly, not detected -- there is no way to
observe a filesystem fact that does not exist yet.

# `canonicalize`

```elixir
@spec canonicalize(String.t(), String.t()) :: String.t()
```

Resolves `raw_path` (relative or absolute, `base_dir`-relative when
relative) into one canonical identity string -- see this module's
moduledoc for the real two-tier algorithm and its honestly-disclosed
limit for not-yet-existing targets.

Two raw strings that are lexically different but denote the same real
on-disk location (a redundant `.`/`..` segment, a repeated separator, a
relative-vs-absolute spelling of the same path, or -- for a path that
already exists -- two different symlinked routes to the same real inode)
canonicalize to the IDENTICAL string.

# `same_target?`

```elixir
@spec same_target?(String.t(), String.t(), String.t()) :: boolean()
```

Whether `path_a` and `path_b` (both resolved relative to the same
`base_dir`) denote the SAME real artifact identity -- built directly on
`canonicalize/2`, never a second, parallel comparison scheme.

# `within_root?`

```elixir
@spec within_root?(String.t(), String.t()) :: boolean()
```

Whether `raw_path`'s real canonical identity remains INSIDE the
authorized project root (`base_dir`, itself canonicalized the same way --
so a symlinked project root does not itself defeat this guard). Refuses
(`false`) any path that escapes `base_dir` via `..` traversal, an
absolute path pointing elsewhere, or a symlink whose real target lands
outside the root.

`base_dir` itself is considered within its own root (`within_root?(dir,
dir)` is `true`, matching `File.rm_rf!/1`-style "the root itself is a
valid target" conventions elsewhere in this codebase); every other path
must resolve to a REAL descendant of that root.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
