# Update a twin

Trial a candidate in a separate fresh World before replacing a working release. A new catalog recommendation does not change an existing World's pin.

## Keep the identities separate

| Identity | Selects |
|---|---|
| Catalog snapshot | Discovery and admission data, identified on the site |
| Installed package and lockfile | Exact implementation files available to run |
| Service `source.package` and `source.version` | What this World expects at boot |
| Booted identity | What the runtime actually mounted; shown separately by hosts that record it |

Inspect the release's README, support gaps and measured journeys in the [catalog](https://world.volter.ai/twins). Registry availability alone does not establish catalog admission. A version mismatch is a reason to reconcile the dependency and config, not erase the pin.

## Walk a concrete trial

Use Node 22.6 or newer in an empty directory. This teaching trial compares two published Stripe package versions; it does not assert that both are admitted in every catalog snapshot. The real SDK asserts the same customer round trip against each release.

```json file=package.json
{
  "name": "acme-web",
  "private": true,
  "type": "module",
  "dependencies": {
    "stripe": "17.7.0"
  },
  "devDependencies": {
    "@volter/world": "3.0.63",
    "@volter/twin-stripe": "3.0.0"
  }
}
```

```text file=.env.example
STRIPE_SECRET_KEY=
```

```js file=check.mjs
import assert from 'node:assert/strict';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { maxNetworkRetries: 0 });
const made = await stripe.customers.create({ email: 'ada@example.com', name: 'Ada' });
assert.equal((await stripe.customers.retrieve(made.id)).email, 'ada@example.com');
assert.equal(made.created, Date.parse('2026-01-15T12:00:00Z') / 1000);
console.log('create, read and frozen timestamp passed');
```

```bash
npm install
npx volter world init --name current-release --twins stripe
npx volter world up
npx volter world clock set 2026-01-15T12:00:00Z
npx volter world run -- node check.mjs
npx volter world down
```

```text
create, read and frozen timestamp passed
```

The current release is stopped with its state retained. Make a new directory containing only the app's project-owned source and a candidate manifest. Do not copy its running state or replace its dependencies:

```js file=prepare-candidate.mjs
import { mkdirSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs';
mkdirSync('candidate');
const pkg = JSON.parse(readFileSync('package.json', 'utf8'));
pkg.name = 'candidate-trial'; pkg.devDependencies['@volter/twin-stripe'] = '3.0.1';
writeFileSync('candidate/package.json', JSON.stringify(pkg, null, 2));
for (const file of ['.env.example', 'check.mjs']) copyFileSync(file, `candidate/${file}`);
```

```bash
node prepare-candidate.mjs
cd candidate
npm install
npx volter world init --name candidate-trial --twins stripe
npx volter world up
npx volter world clock set 2026-01-15T12:00:00Z
npx volter world run -- node check.mjs
npx volter world log
npx volter world down
cd ..
```

```text
create, read and frozen timestamp passed
```

Compare the two `.volter/world.json` files: the original source remains `@volter/twin-stripe@3.0.0`; the candidate source is `@volter/twin-stripe@3.0.1`. Each has its own lockfile and independent retained state. This walkthrough proves only the named customer assertions, not broad compatibility or state migration.

## Adopt deliberately or retain the current release

For your real application, run its own tests, seeded history, faults and callbacks in the candidate directory. Adoption updates the exact dependency, lockfile and service source together. Preserve intentional service definitions; do not overwrite an existing config merely to make startup pass. Stop the World and its consumers before changing packages.

Stored-state compatibility is separate from API compatibility. Ask the publisher before reusing old retained state across versions or publishers. Never run two runtime versions over the same instance. If the candidate is unsuitable, keep the original pin and World.

Revocation removes future catalog selection, without silently replacing historical pins. Read its reason and choose another selectable release deliberately. [Manual setup](./use-with-an-existing-app.md) explains explicit publisher selection; [coverage](../reference/coverage.md) explains what the release's measurements establish.

<!-- Fenced tutorial executor: packages/cli/src/journeys/tutorial.ts. Execution scope is recorded separately. -->
