# `Pixir.SessionResources`
[🔗](https://github.com/Ranvier-Technologies/pixir/blob/main/lib/pixir/session_resources.ex#L1)

Durable local Session Resources (ADR 0021).

Pixir treats attachments and ACP resource links as local resources owned by
the Session, not as Presenter blobs and not as ambient model context. When the
original bytes are available, they stay on disk under
`.pixir/sessions/<session_id>/resources/`; the Log records only a descriptor
with a Session-local `resource_id` and, for stored payloads, a byte identity
`content_sha256`.

Provider-specific shapes such as Responses `input_image` are projections across
the Leakage Boundary. They are assembled from descriptors only when Pixir
intentionally sends a resource to OpenAI.

Resource path boundaries validate the Session id. Static symlink and same-UID race
hardening for payload paths under `.pixir/sessions/<session_id>/resources/` is
deliberately deferred; the Resource store is not an adversarial filesystem sandbox.

# `descriptor`

```elixir
@type descriptor() :: map()
```

# `copy_referenced_resources`

```elixir
@spec copy_referenced_resources(String.t(), String.t(), [map()], keyword()) ::
  :ok | {:error, map()}
```

Copy stored resource payloads referenced in replayed Events from parent to child Session.

Link-only descriptors without `content_sha256` are skipped. Payload copy uses the same
`resource_id` and checksum paths under the child Session store.

# `data_url`

```elixir
@spec data_url(String.t(), descriptor(), keyword()) ::
  {:ok, String.t()} | {:error, map()}
```

Return the Provider-ready data URL for a resource descriptor.

# `find_descriptor`

```elixir
@spec find_descriptor([map()], String.t()) :: {:ok, descriptor()} | {:error, map()}
```

Find one resource descriptor in folded History by Session resource id.

# `ingest_attachments`

```elixir
@spec ingest_attachments(String.t(), [map()] | nil, keyword()) ::
  {:ok, [descriptor()]} | {:error, map()}
```

Ingest supported attachment maps into the Session Resource store.

Callers may pass T3-style image attachment maps (`type`, `name`, `mimeType`,
`sizeBytes`, `dataUrl`), Pixir-native snake-case variants, or ACP
`resource_link` blocks (`uri`, `name`, `mimeType`, `size`). Local `file://`
links are copied into Pixir's Session Resource store when readable. Remote or
unsupported links are recorded as link-only descriptors and are not
rehydratable until a later explicit import/fetch records bytes.

Raw base64 and local source paths never appear in the returned descriptors.

# `local_attachment_link`

```elixir
@spec local_attachment_link(String.t(), Path.t()) ::
  {:ok, map()}
  | {:error, :empty_path | :remote_uri | :uri_query_or_fragment | :invalid_path}
```

Normalize an operator-supplied local attachment (filesystem path or
`file://` URI) into the `resource_link` map `ingest_attachments/3` accepts.
Relative paths resolve against `workspace`; remote URIs are rejected.
Existence is checked at ingestion, not here.

# `local_file_uri?`

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

The single source of the local `file://` acceptance rule: empty/localhost
host and a real path, nothing remote. Delegate and Workflow attachment
surfaces share it so their mirrors cannot drift.

# `render_descriptor`

```elixir
@spec render_descriptor(descriptor()) :: String.t()
```

Render a compact, text-only descriptor for default replay.

# `render_descriptors`

```elixir
@spec render_descriptors([descriptor()]) :: String.t()
```

Build a text block for one or more descriptors.

# `resource_path`

```elixir
@spec resource_path(String.t(), descriptor(), String.t()) ::
  {:ok, String.t()} | {:error, map()}
```

Resolve a descriptor to an on-disk path without reading the payload.

---

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