# `DocShell.Generate.Collection`
[🔗](https://github.com/futhr/doc_shell/blob/v2.0.0/lib/doc_shell/generate/collection.ex#L1)

Describes, writes, and validates a portable documentation corpus.

A collection binds one `doc-shell/v2` artifact tree to an immutable source
revision. The descriptor carries the identity supplied by the checkout
owner; DocShell records it but does not invoke Git or fetch either URL.

`load/1` treats the manifest as the authority for the directory. It rejects
unlisted files, links, path escapes, mixed generations, and changed payloads
before returning documents. Source records and their metadata remain JSON
values, including kinds introduced by another producer.

## Example

    iex> {:ok, collection} =
    ...>   DocShell.Generate.Collection.new(%{
    ...>     id: "example_core",
    ...>     title: "Example Core",
    ...>     version: "1.4.0",
    ...>     revision: String.duplicate("a", 40),
    ...>     tree_digest: "sha256:" <> String.duplicate("b", 64),
    ...>     artifact_dir: "/tmp/example-core-docs",
    ...>     source_url: "https://example.invalid/example_core",
    ...>     edit_base_url: "https://example.invalid/example_core/edit/revision"
    ...>   })
    ...>
    ...> collection.id
    "example_core"

The `artifact_dir` is deliberately absent from `collection.json`: it names
the local import location and cannot affect portable bytes or provenance.
`source_root` is repository-relative and defaults to `"."`.

# `extracted`

```elixir
@type extracted() :: %{
  modules: [map()],
  guides: [map()],
  livebooks: [map()],
  changelog: [map()],
  openapi: map()
}
```

Complete extraction consumed by collection preparation.

# `loaded`

```elixir
@type loaded() :: %{
  descriptor: t(),
  generation_id: String.t(),
  content_digest: String.t(),
  artifacts: %{required(String.t()) =&gt; term()},
  sources: [map()],
  documents: [map()]
}
```

A validated corpus whose raw artifacts and qualified documents are in memory.

# `t`

```elixir
@type t() :: %DocShell.Generate.Collection{
  artifact_dir: Path.t(),
  audience: String.t() | [String.t()] | nil,
  default_locale: String.t() | nil,
  edit_base_url: String.t(),
  id: String.t(),
  license: String.t() | nil,
  package: String.t() | nil,
  revision: String.t(),
  source_root: Path.t() | nil,
  source_url: String.t(),
  status: String.t() | nil,
  title: String.t(),
  tree_digest: String.t(),
  version: String.t()
}
```

Immutable source and local artifact identity for one corpus.

# `digest`

```elixir
@spec digest(DocShell.Json.Canonical.encodable()) :: String.t()
```

Computes a lowercase SHA-256 digest over canonical JSON bytes.

This compatibility convenience function requires a JSON-encodable value and
raises `ArgumentError` for invalid values or duplicate encoded keys. Use
`DocShell.Json.Canonical.digest/1` for a checked input boundary.

# `load`

```elixir
@spec load(t() | map() | keyword(), keyword()) :: {:ok, loaded()} | {:error, term()}
```

Loads and validates the artifact tree named by a descriptor.

The returned `documents` have qualified IDs while retaining `document_id`.
Raw decoded payloads are available under `artifacts` without rewriting
unknown fields. Keyword options override the finite import budgets in
`DocShell.Generate.Collection.Limits`; total bytes include the manifest.

# `load_many`

```elixir
@spec load_many([t() | map() | keyword()], keyword()) ::
  {:ok, [loaded()]} | {:error, term()}
```

Loads an ordered set of collections and rejects duplicate collection identities.

Options are the same as `load/2`. `:max_collections` bounds this call's input;
file, byte, depth and source budgets apply independently to each collection.

# `new`

```elixir
@spec new(t() | map() | keyword()) :: {:ok, t()} | {:error, term()}
```

Normalizes and validates a collection descriptor.

# `payload`

```elixir
@spec payload(t(), [map()], [{String.t(), term()}]) :: map()
```

Builds the portable collection payload for an ordered artifact set.

# `payload_for_digests`

```elixir
@spec payload_for_digests(t(), [map()], %{required(String.t()) =&gt; String.t()}) ::
  map()
```

Builds the portable collection payload from precomputed artifact digests.

Each digest must be `DocShell.Json.Canonical.digest/1` of the artifact's
`data`. A build that has already encoded its artifacts passes their digests
here instead of having every payload encoded a second time.

# `portable_descriptor`

```elixir
@spec portable_descriptor(t()) :: map()
```

Returns the portable descriptor stored in `collection.json`.

# `prepare`

```elixir
@spec prepare(t(), extracted()) :: {:ok, extracted(), [map()]} | {:error, term()}
```

Normalizes extracted source paths and builds their provenance records.

# `schema_version`

```elixir
@spec schema_version() :: String.t()
```

Returns the collection payload schema identifier.

---

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