# Versions and identity

A published version is a folder that never changes. Identical bytes reuse it, a skill keeps one identity through renames, and receipts follow the bytes wherever they go.

## A version is a folder

Publishing appends `skills/<name>/v<N>/` to the team repository. Nothing edits a committed version: the write guard refuses any path inside a version folder that already holds files, whichever command asks.

The next version is the highest existing number plus one, not the count plus one, so a skill with `v1` and `v4` publishes next as `v5`. Each number has one spelling: `v1`, `v2`, `v10`, never `v0`, `v03` or `v1.0`.

There is no pinning. `install` refuses a ref with `@<version>` and copies `skills/<name>/v<max>/` as it stands in your clone, which is the newest version your last `sync` brought down.

::: figure version-stack
Each publish adds the next version to the end of the row, and a folder identical to an existing version adds nothing.
:::

## Identical bytes reuse the version

Publish digests your folder and compares it with every existing version of that name. If one matches, no version is minted:

```text
Nothing to publish: deploy-check is identical to Version 3 and already in the acme marketplace.
```

So a publish is safe to run twice, and an eval-only change does not bump the number. Eval assets, and local receipts of those exact bytes, still reach the team on every publish, minted or not.

### Which bytes count

One digest function serves the publish comparison, the eval receipt and the Library's join to the Marketplace, so the three never disagree. It covers every file in the skill folder except:

- `evals/` at the top level, so adding a case does not mint a version
- `.git` and `.skillhub` as the first path segment
- `.DS_Store` and `Thumbs.db` anywhere

`SKILL.md` is canonicalized first, dropping exactly three frontmatter fields: `license`, `metadata.id` and `metadata.author`. Those are the fields publish manages, so two people's copies of one skill have the same bytes. Everything else counts, including `metadata.terum-category`. File modes do not count, though the executable bit is carried into the repository.

## Lineage and the skill id

A skill's identity is a uuid in `metadata.id`. The folder name can change; the uuid cannot, and receipts, install records and profile entries are all keyed to it.

At publish, the uuid comes from the repository, not your file:

1. If the team already publishes a skill by that name, you append to its uuid.
2. Otherwise, if your `SKILL.md` declares a `metadata.id` no other published skill uses, it is kept.
3. Otherwise a fresh uuid is minted. That covers a first publish, and a folder copied from another skill, which would otherwise graft its receipts onto the original's history.

Inside the write, publish checks again that the name still carries that uuid, and refuses rather than append to someone else's lineage.

## What publish injects

Before it checks or digests anything, publish sets four frontmatter fields in `SKILL.md`:

| Field | Value |
| --- | --- |
| `license` | The team's `policy.skill_license` from `team.json` |
| `metadata.id` | The uuid resolved above |
| `metadata.author` | Your display name and email, as `Name <email>` |
| `metadata.terum-category` | Only when the file declares none |

The edited file is written back to your folder after publish's last question and before the push. A publish you cancel at any prompt leaves your folder byte-identical. One that fails after the write-back leaves the injected fields in place, and the next attempt derives them identically.

## Unpublish

```sh
npx -y terum-skills@latest unpublish <skill>
```

Anyone on the team may run it, after typing the skill's name to confirm. In one commit it removes every version folder, the `skills/<name>/evals/` assets, every receipt under `evals/<uuid>/`, and the uuid from every team project and every member's `profile[]`.

It does not rewrite git history, where the bytes stay reachable. It does not touch anyone's machine: installed copies keep working, `installed[]` is left alone, and each machine learns of the removal at its next sync.

Republishing starts again at `v1`, and the removed receipts and profile entries do not come back. The uuid does not restart with them: publish keeps your folder's declared `metadata.id`, and mints a fresh one only when another published skill is using it.

## The incumbent

An eval runs your local folder as the candidate. The incumbent is the committed version whose newest receipt is the most recent, not the highest number: a `v2` re-evaluated today is a more current comparison than a `v4` evaluated last month. Versions with the candidate's exact bytes are excluded first. With no receipted version left, there is no incumbent arm, and a new version nobody has evaluated is never the incumbent.

## Receipts follow the bytes

A receipt records the `content_digest` of the folder it evaluated, and the local store is keyed by that digest, not by name:

```text
~/.terum/skills/evals/local/<64 hex>/<runId>/receipt.json
```

- `install` copies a version's committed receipts into your local store, so an installed skill shows its team score offline.
- `eval` commits its own receipt when the bytes are already a published version, writing one file and minting nothing. `--no-commit` keeps it local.
- `publish` attaches every local receipt of exactly the bytes being published. Runs of earlier bytes stay behind.

Because the key is the digest, a receipt follows the bytes through a rename, a move to another project, and a copy to another machine.
