# verify - is this install the thing that was published

The install is a COPY. `install.js` writes the pipeline tree into `~/.claude`,
`~/.copilot` and `~/.codex`, and from that moment the two halves drift
independently. Both directions produce bugs that are hard to name:

- an edit made in the installed copy is a behaviour with no source, and the next
  update silently reverts it;
- a file the installer failed to write is a script the docs describe and nobody
  has, which reads as a documentation error.

`multi-agent-pipeline verify` answers both mechanically.

```bash
npx @mmerterden/multi-agent-pipeline verify              # package + install
npx @mmerterden/multi-agent-pipeline verify --package    # package integrity only
npx @mmerterden/multi-agent-pipeline verify --install    # install drift only
npx @mmerterden/multi-agent-pipeline verify --json
```

| Code | Meaning |
|---|---|
| 0 | everything matches |
| 1 | a difference was found, named file by file |
| 2 | nothing to verify - a source checkout, or a version published before manifests existed |

Exit 2 is not a pass and not a failure. A dev checkout has no manifest by
design, and reporting that as either would be a lie in one direction or the
other.

## The manifest

`manifest.json` is written at pack time by `prepack`, never committed. A
manifest in git is stale one commit after it is written, and a stale manifest
reports honest edits as tampering - which is worse than having none, because
people learn to ignore it.

The file list is not guessed. It comes from `npm pack --dry-run --json`, so by
construction it is the same set npm publishes, `files` globs and all. The gate
asserts the two counts agree, which is what catches a `files` entry and a
manifest that have stopped describing the same package.

Two things it cannot cover, said here rather than discovered later: it cannot
hash itself, and a signature over it does not authenticate the tarball.

## What a green result proves, and what it does not

It proves the bytes match what the publisher recorded. It is not proof of WHO
published them. The manifest, the signature and the verifier all travel inside
the same tarball, so anyone able to rewrite one can rewrite the others.
Provenance belongs to npm's own integrity field.

What this does catch is the set of failures that actually happen: a damaged or
partial install, a file edited after install, and an update that did not land.

Signing is optional. `make-manifest.mjs --sign` reads an ed25519 private key
from the credential store (or `MULTI_AGENT_SIGNING_KEY` on a build host with no
store) and writes `manifest.sig`; `verify` checks it against
`MULTI_AGENT_SIGNING_PUBKEY` when one is pinned. Without a key it says "signed,
no public key to check it against" rather than claiming valid - a signature
nobody can check is not a signature that passed.

## How each tree is compared

| Tree | Mode | Why |
|---|---|---|
| `scripts` | bytes | verbatim copy, minus the dev-only set |
| `lib` | bytes | verbatim copy |
| `multi-agent-refs` | bytes | verbatim copy |
| `agents` | bytes | verbatim copy |
| `commands/multi-agent` | presence | `install.js` rewrites each SKILL.md `description` into the user's `outputLanguage` |

Byte-comparing `commands/` reports every command as drift on a perfectly
healthy machine. Measured here: all 57 command files differ, and 56 of them
differ by nothing except the translated description. A report that is wrong by
default is a report nobody reads.

The dev-only filter matters just as much: smokes, linters and fixtures ship in
the package and are deliberately NOT installed. Without excluding them, `verify`
would report 252 files as "the installer skipped this".

`~/.copilot` and `~/.codex` are reported as present, not compared: the installer
rewrites paths for both on purpose, so a byte difference there is the design.
