# Read the vendor through a shared world

Keep a copy of the account in the team's world: refreshed on demand or on a schedule, read by
every clone without a vendor call, and rebased when the vendor moves.

This page supplies an executable walkthrough for `packages/cli/src/journeys/tutorials.test.ts`.

A root's log is the account's history as observed. A **refresh** asks the vendor what it holds
and appends to that log only what changed; the twin says how often the vendor can be asked, and
the world can say otherwise. Everything downstream reads the held copy: a clone answers from its
tree, so a rate-limited API is read as often as you like. When the vendor moves under a
changeset, `rebase` names the conflict by record and field.

## Three worlds

GitHub itself is a world here, so the whole chain runs on one machine; the commands are the same
when the root is `https://api.github.com`.

```json file=package.json
{ "name": "acme-web", "private": true, "type": "module", "dependencies": { "@octokit/rest": "^21" } }
```

The app declares the credential it reads. The World issues a throwaway token for its GitHub account,
`world`; that account is separate from the platform org `acme`. The seed creates `world/web` through
Octokit before any issue is written. It can run again without creating a second repository.

```text file=.env.example
GITHUB_TOKEN=
```

```js file=.volter/seed.ts
import { Octokit } from '@octokit/rest';
const github = new Octokit({ auth: process.env.GITHUB_TOKEN });
const { data: { login: owner } } = await github.users.getAuthenticated();
try {
  await github.repos.get({ owner, repo: 'web' });
} catch (error) {
  if (error.status !== 404) throw error;
  await github.repos.createForAuthenticatedUser({ name: 'web' });
}
```

```js file=list-issues.mjs
import { Octokit } from '@octokit/rest';
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
const { data: issues } = await octokit.issues.listForRepo({ owner: 'world', repo: 'web', state: 'all' });
for (const issue of issues) console.log(`#${issue.number} ${issue.title}`);
```

```js file=retitle.mjs
import { Octokit } from '@octokit/rest';
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
await octokit.issues.update({ owner: 'world', repo: 'web', issue_number: 1, title: process.argv[2] });
console.log('retitled #1');
```

```text file=../reality/.env.example
GITHUB_TOKEN=
```

```js file=../reality/.volter/seed.ts
import '../../acme-web/.volter/seed.ts';
```

```text file=../team/.env.example
GITHUB_TOKEN=
```

```js file=../team/.volter/seed.ts
import '../../acme-web/.volter/seed.ts';
```

```js file=root-credential.mjs
import { readFileSync } from 'node:fs';
const worldToken = readFileSync('../reality/.volter/token', 'utf8').trim();
const response = await fetch('http://127.0.0.1:4400/acme/reality/github/_twin/app-credentials', {
  method: 'POST', headers: { 'x-twins-key': worldToken, 'content-type': 'application/json' }, body: '{}',
});
if (!response.ok) throw new Error(`The local GitHub account did not issue a token (${response.status})`);
const { token } = await response.json();
if (!token) throw new Error('The local GitHub account returned no token');
console.log(process.argv.includes('--token') ? token : JSON.stringify({ headers: {
  'x-twins-key': worldToken, authorization: `Bearer ${token}`,
} }));
```

```bash
npm install
npm install -g @volter/world
npm install -D @volter/twin-github
cd ../reality && volter world init --bare acme/reality --twins github
volter world up
volter world down
volter world serve --port 4400 &
cd ../team && volter world init --bare acme/team --twins github
volter world up
volter world down
volter world serve --port 4300 &
cd ../acme-web
```

Wait for both worlds to announce that they are serving before continuing.

```text
serving  acme/reality  http://127.0.0.1:4400/acme/reality
serving  acme/team  http://127.0.0.1:4300/acme/team
```

```bash
for i in $(seq 1 60); do [ -f ../reality/.volter/token ] && [ -f ../team/.volter/token ] && break; sleep 1; done; sleep 3
cd ../reality
export ROOT_GITHUB_TOKEN=$(volter world run -- node ../acme-web/root-credential.mjs --token)
cd ../acme-web
curl -s -X POST http://127.0.0.1:4400/acme/reality/github/repos/world/web/issues -H "x-twins-key: $(cat ../reality/.volter/token)" -H "authorization: Bearer $ROOT_GITHUB_TOKEN" -H 'content-type: application/json' -d '{"title":"Launch checklist"}' | grep -o '"number":[0-9]*'
```

```text
"number":1
```

## Set the root, and how often it may be asked

`--deploy hold` keeps landed entries waiting until someone deploys; `--at-most 30s` is this
world's word on how often the twin may be refreshed on demand (the twin has its own default).

```bash
cd ../team
volter twin github root http://127.0.0.1:4400/acme/reality/github --scope repos/world/web --deploy hold --at-most 30s
volter world run -- node ../acme-web/root-credential.mjs | volter twin github credential
volter twin github refresh
volter twin github refresh
cd ../acme-web
```

```text
github  refreshed
github  not refreshed: refreshed at ISO; the github twin refreshes at most every 30s (--force to refresh now)
```

## A clone reads the copy

The app's world clones the team's, and its tree holds what the team observed. Stop the vendor,
and the app still reads the issue: nothing here calls the vendor.

```bash
volter world init
volter world up --no-seed
volter world clone http://127.0.0.1:4300/acme/team --token "$(cat ../team/.volter/token)"
kill %1
volter world run -- node list-issues.mjs
```

```text
#1 Launch checklist
```

## When the vendor moves

Bring the vendor back and change the issue there. The team's world observes it on its next
refresh. Meanwhile the app changed the same title, cut a changeset and pushes: the push refuses,
because the team's world moved under it, and says what to do.

```bash
cd ../reality
volter world serve --port 4400 &
```

Wait for the restarted world to announce that it is serving:

```text
serving  acme/reality  http://127.0.0.1:4400/acme/reality
```

```bash
cd ../acme-web
for i in $(seq 1 60); do curl -s http://127.0.0.1:4400/-/ping >/dev/null && break; sleep 1; done; sleep 2
curl -s -X PATCH http://127.0.0.1:4400/acme/reality/github/repos/world/web/issues/1 -H "x-twins-key: $(cat ../reality/.volter/token)" -H "authorization: Bearer $ROOT_GITHUB_TOKEN" -H 'content-type: application/json' -d '{"title":"Launch checklist, final"}' | grep -o '"title":"[^"]*"'
cd ../team
volter twin github refresh --force
cd ../acme-web
volter world run -- node retitle.mjs "Launch checklist, draft"
volter world changeset -m "Retitle the checklist"
volter world push
```

```text
"title":"Launch checklist, final"
github  refreshed  1 changed
retitled #1
changeset  retitle-the-checklist  1 change
refused  retitle-the-checklist  origin moved on github; fetch and rebase before pushing
```

`fetch` brings the moved base, and `rebase` replays the changeset over it, naming the record and
the field where the two disagree. The rebased changeset has a new hash; a reviewer sees the
conflict on it.

```bash
volter world fetch
volter world rebase retitle-the-checklist
volter world push
```

```text
conflicts:
github issue:1 title set
pushed  retitle-the-checklist  1 change → origin
```

The entry waits on the team's world: the root says `hold`. Someone deploys it, and the vendor
holds the app's title.

```bash
cd ../team
volter world deploy
volter world log --receipts
cd ../acme-web
curl -s http://127.0.0.1:4400/acme/reality/github/repos/world/web/issues/1 -H "x-twins-key: $(cat ../reality/.volter/token)" -H "authorization: Bearer $ROOT_GITHUB_TOKEN" | grep -o '"title":"[^"]*"'
```

```text
deployed  github  1 change
"title":"Launch checklist, draft"
```

## Clean up

```bash
volter world down
kill $(jobs -p)
```
