# `MishkaCmsCore.Runtime.ExtensionProvision`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/runtime/extension_provision.ex#L5)

Gives a site everything an extension ships and it is missing: the permission names, the
components, the sample pages, the snippet. One implementation, driven by a `t:manifest/0` the
extension declares.

## Why it is here and not in each extension

It was written twice. A `diff` of the blog's machinery against the document's reported the alias,
the section name, the snippet name, the path constants and the prose — and not one line of logic.
Two copies of a run that must never half-finish is two places for it to learn to.

What an extension keeps is what only it knows: the pages it ships and what they are made of. What
it hands over is the order those things have to happen in, and every reason behind that order.

## The order is the whole design

PERMISSION NAMES FIRST, so the extension's areas can be granted from the moment anything of it is
on a site, and a run that stops halfway still leaves them grantable. They are not the site's: a
name is shared by every site, so the first site's run creates it and every later one finds it.
The SYSTEM writes them, whoever runs the provision, because creating a permission name is a
master's action (`MishkaCmsCore.Accounts.Permission`), and a site admin setting their own site up
has to be able to finish.

COMPONENTS BEFORE PAGES. A page names live components, and
`MishkaCmsCore.Runtime.Resources.Validations.StatefulIds` reads their rows to decide whether two
placements collide. It can only do that once the rows are there.

NOTHING IS EVER UPDATED. Every step writes only what the site has no row for, so a second run adds
whatever the extension has learned to ship since and leaves everything else — including anything
the admin has edited — exactly as it is. An admin pressing a button called "set up" has not asked
to lose an edit; `MishkaCmsCore.Runtime.Handlers.UiKitUpgrade` is where losing one is offered, by
name, one row at a time.

EVERY PAGE HANGS ON THE SITE'S FRAME. An extension's pages are drawn in the site's `site-main`
layout, between the header and the footer every page of the site shares
(`MishkaCmsCore.Runtime.SiteLayout`), which every site has from its first install
(`MishkaCmsCore.Install.StarterContent`). An extension writes that layout's id into each page it
ships (`MishkaCmsCore.Runtime.SiteLayout.layout_id/1`).

NO PAGE MAY RAISE THE RUN. A site is unique by more than a page's address, so a site already
holding a page called "Blog" refuses a sample page at a path nothing has taken — and left to
`Ash.create!` that is a raise in the MIDDLE of the run: the pages after it are never written, the
snippet is never reached, and every later run stops at the same row. `MishkaCmsCore.Runtime.PageProvision`
is what turns that into a rename or a skip, and a line in the report either way.

## What the report is for

Not the logs. The admin pressed a button and is owed the answer: what arrived, what was written
under another name because the one it wanted was taken, and what was left alone because something
of theirs holds it. `PageProvision.note/1` writes each of those as a sentence.

# `manifest`

```elixir
@type manifest() :: %{
  :app =&gt; atom(),
  :bundle_path =&gt; String.t(),
  :section =&gt; String.t(),
  :pages =&gt; (map() -&gt; [map()]),
  optional(:snippet) =&gt; %{name: String.t(), source: (String.t() -&gt; String.t())},
  optional(:permissions) =&gt; [{String.t(), String.t()}]
}
```

What an extension declares about itself. `pages` and `snippet` are functions of the SITE because a
stored `<.component>` call names the site whose component it draws, so the markup cannot be
written until the site is known.

`snippet` is optional — an extension with nothing to put in the Studio's list leaves it out.
`permissions` is optional too: the names the extension's own areas are gated by, each with its
description.

# `report`

```elixir
@type report() :: %{
  version: String.t() | nil,
  permissions: [String.t()],
  components: [String.t()],
  pages: [String.t()],
  snippets: [String.t()],
  renamed_pages: [MishkaCmsCore.Runtime.PageProvision.renamed()],
  skipped_pages: [MishkaCmsCore.Runtime.PageProvision.held()]
}
```

# `allowed?`

```elixir
@spec allowed?(manifest(), map() | nil) :: boolean()
```

Whether `actor` holds every name in `required/1`, so a provision it starts can finish.

# `bundle_version`

```elixir
@spec bundle_version(manifest()) :: String.t() | nil
```

The bundle version the pages are stamped with.

It is the KIT's version rather than the extension's: the pages and the components are one release
together — a page drawing a component the site has not got renders an error where the block should
be — and the kit is what `MishkaCmsCore.Runtime.Handlers.UiKitUpgrade` already versions.

# `component_names`

```elixir
@spec component_names(manifest()) :: [String.t()]
```

The component names the extension's bundle ships, read off the bundle itself.

# `ensure!`

```elixir
@spec ensure!(manifest(), map() | String.t(), map(), keyword()) :: report()
```

Gives `site` everything `manifest` ships and it is missing, and returns what happened.

Takes the site record or its id. Raises when the site is not there, when the actor lacks a name in
`required/1`, or when a write is refused for any reason but a page being taken: every caller
wants to know, and the screen says so. With no actor — the seeds, the install wizard's own run —
the system writes.

# `ensure_permissions`

```elixir
@spec ensure_permissions(manifest()) :: [String.t()]
```

Creates the names `manifest` lists under `permissions:` that are not stored yet, as the system, and
returns the ones it created. `ensure!/4` runs it first.

# `missing`

```elixir
@spec missing(manifest(), map() | nil) :: [String.t()]
```

The names in `required/1` that `actor` does not hold — what a refusal says to ask for.

# `moved`

```elixir
@spec moved([map()], map()) :: [map()]
```

The pages of a run with the addresses the admin asked for.

A page whose PATH something else holds is the one thing a run cannot answer on its own: the
address is taken, and only the admin knows whether to move ours or theirs. `answers` is what they
said, either way:

  * `%{"paths" => %{"/:lang/blog" => "/:lang/journal"}}` moves one page;
  * `%{"prefix" => "/mish"}` moves every page of the extension under one segment, which is the
    answer when a site has its own `/blog` and wants ours somewhere else entirely. The segment
    goes after a leading `:lang`, which only the first segment can be: `/:lang/blog` moves to
    `/:lang/mish/blog`.

A prefix is applied first and a named path wins over it, so an admin can move the lot and then
place one of them by hand. A page moved to a path that captures no `lang` answers in its own
language alone (`MishkaCmsCore.Runtime.PageProvision.serving_all_languages/1`).

# `page_paths`

```elixir
@spec page_paths(manifest(), map() | String.t(), map() | nil) :: [String.t()]
```

The paths of the sample pages, in the order they are created.

# `pages`

```elixir
@spec pages(manifest(), map() | String.t(), map() | nil) :: [map()]
```

The sample pages as this checkout ships them, as the attribute maps a page create takes.

# `required`

```elixir
@spec required(manifest()) :: [String.t()]
```

The names a person needs to run a provision of `manifest` — Set up, Doctor, Kit upgrade — one per
step, each asked on its own because a required list is any-of:

  * `admin:dashboard:runtime:component` — the kit's components are written;
  * `admin:dashboard:runtime:js_hook`, `admin:dashboard:runtime:stylesheet` — when the bundle
    ships JS hooks or stylesheets, the kit writes them too;
  * `admin:dashboard:runtime:page` — the sample pages are written, and the snippet is saved for the
    page builder, which this name opens;
  * `admin:dashboard:runtime:site` — the site is read, to name the pages after it.

---

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