# `Rho.Dyn.Store`
[🔗](https://github.com/GenericJam/rho/blob/v0.1.0/lib/rho/dyn/store.ex#L1)

The Dyn layer on disk, under one root dir (`<data dir>/dyn` by default,
`Rho.Config.data_dir/0`):

    gens/<n>/src/**/*.ex     the generation's full source set, as the agent
                             wrote it (logical `Dyn.*` names)
    gens/<n>/ebin/*.beam     the compiled, selftested binaries
                             (versioned `Dyn.G<n>.*` names)
    gens/<n>/manifest.json   a `Rho.Dyn.Generation`
    gens/<n>/diff.patch      unified diff against the parent's sources
    current                  the active generation's number (0, or no
                             file: no Dyn layer)
    staging/src/**/*.ex      where edits go before a proposal
    boot.json                launch markers: `boot_attempts` (launches since
                             the last one that reached stable) and `stable`
    log.jsonl                crash, revert and safe-mode reports
    seed                     the generation the seed (the default sources
                             the host ships, `Rho.Dyn.Seed`) was last installed as
    seed.json                what that install was built from: each seed
                             file's sha256 as shipped (`files`), and the
                             digest of a newer seed that was rejected
                             (`rejected`), so it isn't retried every launch

Everything that decides what runs (`current`, manifests, `boot.json`) is
written to a temp file and renamed over the old one, so a crash mid-write
leaves the old file.

`Rho.Dyn`, `Rho.Dyn.Keeper`, `Rho.Dyn.Compiler` and `Rho.Dyn.Seed` read
and write it; hosts read Dyn state through `Rho.Dyn` instead. Every
function takes the root dir first.

# `boot_markers`

```elixir
@type boot_markers() :: %{
  boot_attempts: non_neg_integer(),
  stable: boolean(),
  core: String.t() | nil
}
```

`boot.json`: launches since the last stable one, whether the last reached
stable, and the delivered Core update (mob_deliver manifest id) the last
counted launch ran, `nil` for the build's own code.

# `allocate`

```elixir
@spec allocate(Path.t()) :: pos_integer()
```

Reserves the next generation number by creating its directory.

# `append_log`

```elixir
@spec append_log(Path.t(), map()) :: :ok
```

Appends a report (a JSON-encodable map) to the log; the file keeps the last 200.

# `beams`

```elixir
@spec beams(Path.t(), pos_integer()) :: [{module(), binary()}]
```

Generation `n`'s stored binaries (the module name comes from each binary).

# `boot_markers`

```elixir
@spec boot_markers(Path.t()) :: boot_markers()
```

The launch markers (`t:boot_markers/0`); a fresh, stable record when there are none.

# `current`

```elixir
@spec current(Path.t()) :: non_neg_integer()
```

The active generation's number; 0 when there is none (or the pointer is unreadable).

# `default_root`

```elixir
@spec default_root() :: Path.t()
```

The default root dir: `dyn` under `Rho.Config.data_dir/0`.

# `deps`

```elixir
@spec deps(Path.t(), non_neg_integer()) :: map() | nil
```

Generation `n`'s compile dependencies (`Rho.Dyn.Reuse`), or nil
if it has none recorded (built before they were, or unreadable): then
nothing of it is reused.

# `diff`

```elixir
@spec diff(Path.t(), non_neg_integer()) :: String.t()
```

Generation `n`'s unified diff against its parent; `""` when there is none.

# `generation`

```elixir
@spec generation(Path.t(), non_neg_integer()) ::
  {:ok, Rho.Dyn.Generation.t()} | {:error, :not_found}
```

Generation `n`'s manifest; generation 0 is `Rho.Dyn.Generation.empty/0`.

# `generations`

```elixir
@spec generations(Path.t()) :: [Rho.Dyn.Generation.t()]
```

Every generation with a readable manifest, newest first, then generation 0.

# `log`

```elixir
@spec log(Path.t(), pos_integer()) :: [map()]
```

The last `limit` reports, oldest first (string keys, as stored).

# `put_beams`

```elixir
@spec put_beams(Path.t(), pos_integer(), [{module(), binary()}]) :: :ok
```

Writes generation `n`'s compiled binaries.

# `put_boot_markers`

```elixir
@spec put_boot_markers(Path.t(), %{
  :boot_attempts =&gt; non_neg_integer(),
  :stable =&gt; boolean(),
  optional(:core) =&gt; String.t() | nil
}) :: :ok | {:error, term()}
```

Writes the launch markers (`t:boot_markers/0`).

# `put_current`

```elixir
@spec put_current(Path.t(), non_neg_integer()) :: :ok | {:error, term()}
```

Points `current` at generation `n` (0: no Dyn layer).

# `put_deps`

```elixir
@spec put_deps(Path.t(), pos_integer(), map()) :: :ok
```

Writes generation `n`'s compile dependencies (see `deps/2`).

# `put_diff`

```elixir
@spec put_diff(Path.t(), pos_integer(), String.t()) :: :ok
```

Writes generation `n`'s diff against its parent.

# `put_generation`

```elixir
@spec put_generation(Path.t(), Rho.Dyn.Generation.t()) :: :ok | {:error, term()}
```

Writes generation `gen.n`'s manifest.

# `put_seed`

```elixir
@spec put_seed(Path.t(), pos_integer()) :: :ok | {:error, term()}
```

Records that the seed was installed as generation `n`.

# `put_seed_files`

```elixir
@spec put_seed_files(
  Path.t(),
  %{required(String.t()) =&gt; String.t()} | nil,
  String.t() | nil
) ::
  :ok | {:error, term()}
```

Writes the seed's record (`seed.json`, see `seed_files/1`).

# `put_sources`

```elixir
@spec put_sources(Path.t(), pos_integer(), %{required(String.t()) =&gt; String.t()}) ::
  :ok
```

Writes generation `n`'s sources (`%{relative_path => source}`).

# `reset_staging`

```elixir
@spec reset_staging(Path.t(), %{required(String.t()) =&gt; String.t()}) :: :ok
```

Replaces the staging copy with `sources` (normally the current generation's).

# `seed`

```elixir
@spec seed(Path.t()) :: pos_integer() | nil
```

The generation the seed was installed as, or nil before it was.

# `seed_files`

```elixir
@spec seed_files(Path.t()) :: %{
  files: %{required(String.t()) =&gt; String.t()} | nil,
  rejected: String.t() | nil
}
```

The seed's record (`seed.json`): `files`, each seed file's sha256 as it
shipped in the install's seed (nil when an older app installed it and
didn't record them), and `rejected`, the digest of a seed whose install
failed its check, compile or selftests.

# `sources`

```elixir
@spec sources(Path.t(), non_neg_integer()) :: %{required(String.t()) =&gt; String.t()}
```

Generation `n`'s sources as `%{relative_path => source}`.

# `src_dir`

```elixir
@spec src_dir(Path.t(), pos_integer()) :: Path.t()
```

Generation `n`'s source dir (compile diagnostics point into it).

# `stage_create`

```elixir
@spec stage_create(Path.t(), String.t(), String.t()) ::
  :ok | {:error, :bad_path | :exists | File.posix()}
```

Creates a staging source only when `rel` does not already exist.

# `stage_delete`

```elixir
@spec stage_delete(Path.t(), String.t()) :: :ok | {:error, :bad_path}
```

Removes one staged source; `:ok` when it was already absent.

# `stage_put`

```elixir
@spec stage_put(Path.t(), String.t(), String.t()) :: :ok | {:error, :bad_path}
```

Writes (creates or replaces) one staged source; `rel` must pass `valid_rel/1`.

# `stage_read`

```elixir
@spec stage_read(Path.t(), String.t()) ::
  {:ok, String.t()} | {:error, :bad_path | :not_found}
```

One staged source; `rel` must pass `valid_rel/1`.

# `staged`

```elixir
@spec staged(Path.t()) :: %{required(String.t()) =&gt; String.t()}
```

The staged sources as `%{relative_path => source}`.

# `staging?`

```elixir
@spec staging?(Path.t()) :: boolean()
```

Has staging been set up (it is created from the current generation once)?

# `update_generation`

```elixir
@spec update_generation(Path.t(), pos_integer(), (Rho.Dyn.Generation.t() -&gt;
                                              Rho.Dyn.Generation.t())) ::
  {:ok, Rho.Dyn.Generation.t()} | {:error, term()}
```

Reads generation `n`, applies `fun`, writes the result back.

# `valid_rel`

```elixir
@spec valid_rel(String.t()) :: {:ok, String.t()} | {:error, :bad_path}
```

Source paths are relative, `.ex`, made of `[A-Za-z0-9_]` segments.

---

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