Versions

Pinning a release, `askr install` and `askr update`

# Versions

A project says which Askr release it builds against, and `askr` fetches
it. The pin lives in `askr.toml`; the exact commit lives in `askr.lock`,
which belongs in git.

```toml
[askr]
version = "0.6.0"
```

```sh
askr install     # fetch the pinned release
askr outdated    # what is published, and what you have
askr update      # move, after showing you what changes
askr version     # what this project is actually building against
```

Plugins are pinned the same way, each under `[plugins.<name>]` in both
files — see [Plugins](plugins.md).

## Where the source goes

`~/.askr/pkg/askrcode@0.6.0`, shared by every project on the machine.
Set `ASKR_CACHE` to move it — CI usually wants it somewhere cacheable.

Not `ASKR_HOME`: that one means *the framework checkout*, and the build
script tells you to point it at yours. If the cache read the same
variable, following that instruction would write packages into your own
checkout.

Nothing is distributed as a binary. A release is 2.2 MB of source that
compiles in about a second and a half, and `.ppu` files are tied to an
exact FPC version anyway, so sharing them between projects would be a
trap rather than an optimisation.

## One release, two ecosystems

This is the part that has no equivalent in Composer or Bundler.

An Askr release is the Pascal source **and** `@askrcode/lauf`. If those
drift apart you get a `DataGrid.svelte` whose server half is a different
version of `Askr.Urd.Grid`, and nothing says so until a column stops
sorting.

Lauf therefore ships **inside the release**, not on npm. `askr install`
points `frontend/package.json` at it through a gitignored symlink:

```json
"@askrcode/lauf": "file:./.askr/lauf"
```

That path is the same on every machine; the symlink under
`frontend/.askr/` is what differs, and it is not in git. Publishing to
npm would add a second place the version lives, which can lag behind the
tag or be built from the wrong commit — the exact failure this design
removes.

`askr.lock` records both halves anyway, so you can see what you have:

```toml
version = "0.6.0"
commit = "15eb4db6e8e4235f229b79d54101c41619934cca"
lauf = "0.6.0"
```

`askr version` warns if a checkout's two halves disagree. That check
exists because they *had* already drifted — the tool said 0.6.0 while
`frontend/lauf/package.json` said 0.1.0 — and nothing had noticed.

## Working on the framework itself

```toml
[askr]
version = "0.6.0"
path = "/Users/you/code/askrcode"
```

`path` wins over `version`, the same way `replace` wins in a `go.mod`.
`askr install` and `askr update` both refuse to do anything while it is
set, and say so rather than quietly working on the cache instead.

The top-level `askr = "/path"` from before versioning is read as
`[askr] path`, so older projects keep building untouched.

## The tool rebuilds itself when it has to

`askr` is compiled *from* the framework, so the list of unit directories
is baked into it. A 0.6.0 tool building a project pinned to 0.7.0 would
not put a newly added directory on the search path, and the error would
be `unit not found` — pointing nowhere near the cause.

So when the pinned version is not the tool you invoked, `askr` builds
that version's tool once into the cache and re-runs your command with
it. One line of output, the first time only. `bundle exec`, essentially.

`install`, `update`, `outdated` and `new` never delegate: they are the
commands that manage the pin, so they have to run as the tool you
started.

## Upgrading, step by step

**1. See what is published.**

```sh
askr outdated
```

```
installed   0.6.0
latest      0.7.0

published releases:
  0.7.0
  0.6.0   <- this project
```

**2. Read what changes before you take it.**

```sh
askr update
```

`askr update` prints the relevant sections of the framework's
[`UPGRADE.md`](../UPGRADE.md) **before** it touches anything in your
project, and stops there if you interrupt it. Only breaking changes are
listed — everything else is in the commit log.

If `askr.toml` pins an exact version, `askr update` will not move on its
own. That is correct, and on its own it contradicts what `askr outdated`
just told you, so it says what it found and what to type:

```
Already on Askr 0.6.0.

Askr 0.7.0 is published, but askr.toml pins 0.6.0,
which only allows 0.6.0. To move:

  askr update 0.7.0        take that release, and update the pin
  askr update ^0.6.0       or widen the pin in askr.toml by hand
```

`askr update 0.7.0` moves `askr.lock` **and** the pin in `askr.toml`, so
the two cannot disagree and drag you back on the next `askr install`.

**3. Pick up the frontend half.**

A release is one number across the Pascal source and `@askrcode/lauf`.
`askr update` rewrites the dependency in `frontend/package.json`; npm
still has to act on it:

```sh
(cd frontend && npm install)
```

`askr update` prints this line with your actual frontend path when the
file changed.

**4. Rebuild.**

```sh
askr build
```

The first build after an upgrade may print one extra line about building
the tool for the new version — see *The tool rebuilds itself* above.
That happens once per version.

**5. Migrate.**

```sh
askr migrate
```

A framework release can add columns to the tables Askr owns — the job
and failure tables, for instance. `askr update` reminds you; it does not
run it for you, because a migration against production is your decision
and not a side effect of an upgrade.

**6. Run your tests.**

```sh
askr test
```

This is the step that finds what `UPGRADE.md` warned you about. The
notes tell you the shape of what can break; your suite tells you whether
your code is that shape.

## Going back

```sh
askr update 0.6.0
```

The framework moves back, the lock and the pin follow, and Lauf goes
with it. Two things do **not** move: migrations you have already run,
and code you wrote against the newer API. Roll those back yourself.

## When it refuses

**`askr.lock pins commit … but the copy in the cache is …`**

A tag was moved after you locked it, or the cached copy was edited. The
message prints the path; delete it and run `askr install` again to
refetch.

**`Askr 0.7.0 is not installed`**

The lock names a version that is not in `~/.askr/pkg`. That is what a
fresh clone looks like:

```sh
askr install
```

**`could not fetch v0.7.0`**

The tag does not exist at the source, or there is no network. `askr
outdated` lists what is actually published.

**`the tag v0.7.0 contains Askr 0.6.1`**

The tag and the version constant inside it disagree, so the download is
thrown away rather than installed under the wrong name. That is a
problem at the source, not in your project.

## What is not here

**No registry, and no dependency graph.** `askr` resolves exactly one
thing — the framework — from git tags. There is no place to publish a
third-party Askr package, and nothing resolves transitive dependencies,
because nothing has any. If that changes, this is where it changes.

**No checksums beyond the commit.** The lock records the commit a tag
pointed at, and `install` refuses when the cache disagrees with it. That
catches a moved tag and a edited cache. It is not a supply-chain
guarantee: it does not verify signatures, and a compromised repository
would serve a commit that matches itself perfectly.

**No offline install.** A version you have never fetched needs the
network. Once it is in `~/.askr/pkg` it never needs it again, and
`ASKR_CACHE` is how CI keeps it between runs.

**No rollback of your own code.** `askr update 0.6.0` moves the
framework back. It does nothing about migrations you have already run,
or code you wrote against the newer API.