# What code names, config declares; what the CLI names, discovery finds

A Machine names things outside itself by string, and until now the first sign of a wrong string was an invoke-time
failure mid-run. ADR-0049 moved Agents and Sandbox Images INTO the Machine — actor slots and a `workspace()` option — so
those names are typed by xstate's own `src` typing and by the Machine's own parts. ADR-0051 moved the last one, the
**Repo**, the same way: a slot on the `workspace()`, bound by url. What this decision fixes is the rule that says where
a definition lives, and the gate that enforces it.

## Decision

- **One rule decides where a definition lives.** A thing that **code refers to by name** and that a Machine cannot carry
  is declared in `jr2.config.ts`, so the type system sees the name. A thing that **only the CLI or HTTP API refers to by
  name** is discovered from files. Workflows are the latter — `jr2 run` and `POST /workflows/:name/runs` name them, a
  nested Machine is reached by `import` (ADR-0049) — so `workflows/` stays filename-discovered. Agents, images, and
  Repos are neither: they ride the Machine (ADR-0049, ADR-0051), and the `agents/` folder, `config.agents`,
  `config.images`, and `config.repos` all retire. One thing code names and a Machine cannot carry is a secret's value:
  an Effect names it with `secret("name")`, and the Instance declares it in the top-level `secrets` list
  ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)). The name is a string, not a type: the
  walk after the typecheck refuses an Effect that names a secret the list does not hold, before anything is built.
  `jr2.config.ts` holds reach and credentials (`harness`, `secrets`, `git.credentials`, `registry`).
- **The slot key types a Repo.** A `workspace()` declares its Repos as slots —
  `repos: { target: open, docs: { url, ref } }` — and the keys are a phantom on the wrapper type, read through
  `pool()`'s `worker` and `workspace()`'s `body` as the image is (ADR-0049). A
  `customize(machine, { repos: { <slot>: url } })` of a slot the Machine does not declare is a compile error, never a
  silent widening to `string`, and the body's `workspace.repos.<slot>` handle is typed by the same keys. There is no
  `Register`, no `RepoName`, and no `repoNames()`: the url a Machine writes is the thing, not a reference into someone
  else's file (ADR-0051). A door that offers a menu of Repos is the Instance's own `z.enum` of urls.
- **`jr2 up` typechecks the Instance before it builds anything**, and refuses on errors, naming them. The scaffold's
  `tsc --noEmit` and pinned compiler already exist (ADR-0043's rule for the checker); this makes them a gate instead of
  a script the user may run. A wrong slot name, a `customize()` of an Agent or a Repo Slot the Machine does not carry:
  all stop here. One Repo check is converge-time and not compile-time: a registered Machine with an open slot nobody
  bound. A `workflows/` export has no type to hang it on, so the walk that follows the typecheck refuses it, before it
  builds anything, naming the Machine, the slot, and the `customize` line that fixes it (ADR-0051).
- **`jr2 check` runs the gate and the walk with no cluster.** The typecheck and the walk are one function of the CLI,
  `checkInstance`. `jr2 up` and `jr2 check` both call it, so the two give the same refusals with the same text. The walk
  loads each registered module. Thus `jr2 check` also gives each refusal that a module throws when it loads. These are
  refusals of `createMachine`, `defineEvent` and `workspace()`, and `npm run typecheck` cannot give them. `jr2 check`
  reads no kube context. Thus an Assistant that may not run `jr2 up` can run it before it gives a change to the user. It
  runs in a kit checkout and in an installed Instance. The name is the verb that a user types for "is this folder
  correct". The glossary's _check_ of a Check pod is a different noun in a different place: one model call in the
  provider preflight. Thus the name stays, and the glossary entry tells the two apart.

## Considered options

- **A generated declaration** (`jr2-env.d.ts` written by `init`/`up` from directory discovery), to keep `agents/` and
  `images/` as folders and still type their names. Rejected: a generated-file class jr2 does not have, an editor
  staleness window, and — decisive — ADR-0049 removed the need by making the names part of the Machine.
- **Agents and images as config entries** (`agents: { scribe }`, `images: { default: "./images/default" }`), the shape
  this ADR first took. Rejected the same day: it typed the names but kept them Instance-scoped, so a packaged Machine
  still depended on someone else's config holding the right strings, and one run could not hold two Machines each
  carrying a `coder`.
- **A Repo catalog in config, typed back through a `Register` interface** (`repos: [{ name, url }]`, a `RepoName`
  derived from `typeof config`, `z.enum(repoNames(config))` on a door), the shape this ADR took for the one dependency
  it left in config. Rejected in ADR-0051 by the argument above: a packaged Machine still could not say which repository
  it works on, and a consumer could bind one only by holding the right NAME. The slot key is the type now.

- **A flag on `jr2 up`** (`jr2 up --check`, or `--dry-run`) in place of a verb. Rejected: `up` reads the kube context
  first and its flags are converge flags; a flag that stops it after the walk still asks an Assistant to run the
  converging verb, which a permission rule that forbids `jr2 up` also forbids. A verb is the line a permission rule can
  allow.
- **A copy of the walk in `jr2 check`.** Rejected: two walks drift, and then the check passes a folder that `jr2 up`
  refuses — the failure that `jr2 check` exists to remove.
- **Stage the Assembly in `jr2 check` too.** Rejected: staging runs a frozen install from the lockfile (ADR-0043), which
  needs the registry and takes minutes, and its refusals are about the install, not the code an Assistant writes.

## Consequences

- Only Machines the Instance's program typechecks get the compile-time guarantee — those under `workflows/` and what
  they import. A packaged Machine compiled elsewhere carries its own parts, so its internal names are checked where it
  was built; the Repo it works on is an open slot the consumer binds with `customize`, typed by the package's own slot
  keys, or a per-run slot typed by the consumer's mapper over the door.
- Runtime validation stays: an open slot nobody bound is a `jr2 up` walk refusal, and the attach still refuses a per-run
  url no `git.credentials` entry matches (ADR-0051). The type error is the earlier check, not the only one.
