# The authoring SDK

The installed editor type-checks TypeScript behaviours and describes a project's screens. It has
no checkout, no system Node, no pnpm and no network, so everything both need ships with it, in one
directory: the **authoring SDK**.

This is the contract between three things that are built and installed separately — the packaging
script, the workspace service that uses an SDK, and the installer that puts one on a machine. The
MSI (item 2 of the production-readiness plan) replaces the PowerShell installer, not this layout.

A shipped **game** needs none of this. The SDK is for authoring.

## What is in one

```text
<install>/sdk/
  sdk.json                                     the manifest: id, platform, versions, every file
  node/node          | node/node.exe           the pinned private runtime
  node/node.exe.zst + node.exe.sha256          ...or packed (node/node.zst on Linux), as a release ships it (below)
  esbuild/esbuild    | esbuild/esbuild.exe     the transpiler, the same one a checkout builds with
  esbuild/esbuild.exe.zst + esbuild.exe.sha256 ...or packed, as a release ships it (below)
  checker/pnpm-workspace.yaml                  the root marker the harness's paths.ts walks up to
  checker/packages/harness/src/typecheck.mjs   the checker (imports the compiler from beside itself)
  checker/packages/harness/src/typescript.cjs  the TypeScript compiler, once: the checker and the install's bundled engine CLI (`cli/`, built with `bundle.mjs --sdk-beside`) both import this file
  checker/packages/harness/src/typescript.cjs.zst  ...in a release, the compiler packed, and typescript.cjs a loader for it (below)
  checker/packages/harness/src/lib.*.d.ts      TypeScript's standard-library declarations (the ES2020 closure only: no DOM, no web worker)
  checker/packages/api/src/index.ts            the @argus/api declaration closure
  checker/packages/api/src/*.ts                ...and every file that closure reaches
  packages/ui/package.json                     @argus/ui: the exports map the aliases come from
  packages/ui/src/**.ts, **.tsx, **.css        ...its sources, bundled into every screen
  packages/ui/theme/*.css, engine-tokens.json  ...and the theme layer (docs/UI.md §10)
  packages/gameplay/dist/entries.json          @argus/gameplay's build: the built-in behaviours
  packages/gameplay/dist/modules/*.js          ...a scene names by module,
  packages/gameplay/dist/lib/gameplay.js       ...the library a behaviour's `import … from
                                               "@argus/gameplay"` is bundled against,
  packages/gameplay/dist/lib/types/**.d.ts     ...and its declarations for the typecheck. Packing
                                               refuses without them: build the package first
  web-runtime/argus_web.js, argus_web_bg.wasm  the browser runtime `engine export --target web` uses in an
  web-runtime/build-report.json                ...install, stamped with the engine version it was built at
                                               (scripts/web/build.mjs); packed with --web-runtime, which
                                               refuses another version's. A release's form; absent otherwise
  fixtures/render/fonts/*                      the engine's default font, fallback chain and three starter
                                               fonts to copy, with
                                               their licences: what an export packs for a project
                                               that ships no fonts (docs/EXPORT.md, "A font")
  fixtures/recipes/make-your-own-sounds/tools/synth.mjs
                                               the one script a recipe tells an agent to run (the
                                               sound synthesiser): recipes are `argus://` resources
                                               a sub-agent cannot read, so this one is a file
  resources/**                                 every `argus://` resource as a plain file (below)
  NOTICES.md                                   third-party notices
```

`fixtures/` in an SDK is runtime content despite its name: the fonts, the building kit
(`fixtures/kits/fantasy-town`) and the recipe script above, kept at their checkout paths so the engine
finds them by one relative path in a checkout and in an install.

### Resources as files

A sub-agent has no tool for reading an MCP resource, so everything the server serves as `argus://`
is also carried here, built from the same sources (`scripts/sdk/resources.mjs`, which
`scripts/sdk/pack.mjs` calls). The path is one rule: `argus://X` is `<sdk>/resources/X`, with the
extension its kind has.

```text
argus://docs/<slug>             resources/docs/<slug>.md           the whole document (a #heading is a heading in it)
argus://recipes/<name>          resources/recipes/<name>/          README.md first, then every file, whole
argus://recipes/<name>/<path>   resources/recipes/<name>/<path>
argus://contract/scene|project  resources/contract/scene|project.json
argus://ui/schema[/<section>]   resources/ui/schema.json          the whole table
argus://ui/components[/<name>]  resources/ui/components.json
argus://examples/<id>           resources/examples/<id>.ts
argus://rules|components|api    resources/rules|components|api.json   the pack's whole index (a piece is a key in it)
argus://project/...             none: live state of one project; use the project tools
```

`<sdk>` is where an agent already finds the guides: `docsDir` in the `notes.read` result is
`<sdk>/docs`, `resourcesDir` is `<sdk>/resources`, and a new project's `AGENTS.md` names the
absolute `<sdk>` of the machine that created it. `scripts/agent-pack/tests/resource-files.test.mjs`
holds every `argus://` address the guides, the server and the templates mention to a carried file.
The engine ships no starter assets except three small starter fonts (Inter Regular and SemiBold,
JetBrains Mono Regular, OFL) in `fixtures/render/fonts/`, to copy into a project's `ui/fonts/`; the
faces a project brings beyond them are its own (docs/UI.md, "Fonts and fallback").

The private runtime is not a Node installation: nothing is added to `PATH`, no other program can
find it, and removing the SDK removes it.

`checker/` is laid out exactly as a checkout is, because the checker finds the declarations the way
it does at home: it walks up from its own file to `pnpm-workspace.yaml` and reads
`packages/api/src/index.ts` from there.

### Where the gameplay build is found

The engine (`crates/app/src/modules.rs`), the editor's in-process world (the same code) and the
workspace service (`crates/workspace-service/src/build/tools.rs`) look for `packages/gameplay/dist`
in one order, taking the first that has an `entries.json`:

1. `ARGUS_GAMEPLAY`, taken as given (and, for the service, a named toolchain root or the SDK it
   checks with);
2. the nearest `packages/gameplay/dist` at or above the scene or project: a checkout;
3. `$ARGUS_SDK/packages/gameplay/dist`;
4. `<exe dir>/sdk/packages/gameplay/dist`, then `<exe dir>/../sdk/packages/gameplay/dist`.

A project far from any checkout, with the editor and engine run from a build folder rather than
beside an SDK, therefore needs only `ARGUS_SDK`. An editor started for its control endpoint
(`--control-port`, as `editor.start` starts one) whose project will not open exits at once with
the reason, rather than leaving its caller waiting on an endpoint that only an open project has.

### `@argus/ui`, and why it sits beside `checker/` rather than in it

A project's `ui/screens/**.tsx` import `@argus/ui`, and describing one is esbuild plus one
evaluation (`docs/EDITOR.md`, "Describing a screen"): the screen is bundled against the package and
the bundle is evaluated. That is a different tool from the checker — the engine's own esbuild, not
the TypeScript compiler — so the package travels beside `checker/`, not inside the workspace the
checker walks.

It is at `packages/ui`, the path it has in a checkout, because that is where
`argus_host::screen::find_ui_package` already looked: under `<exe dir>` and `<exe dir>/sdk`,
walking up, so both install layouts — `bin/` and `sdk/` as siblings, or an `sdk/` folder beside the
binaries — find it with no path either side had to invent. Keeping the checkout's path is also what
keeps the package-relative style paths in `src/components/registry.ts` true in an install.

What ships is **source**, not a build: the package's `exports` map names `./src/index.ts` and
`./src/components/index.ts`, and esbuild compiles the TypeScript on the way into the bundle. The
`exports` map is the contract — `ui_package_aliases` turns each entry into one `--alias` flag, so
`@argus/ui/components` resolves to the file the package says rather than to a guess at its layout —
and the packer refuses to write a pack whose `exports` names a file it does not carry.

`test/`, `fixtures/`, `strings/` and the `dist/` a local `tsc` leaves do not ship. Nothing a screen
can reach is in them, and a pack that swept up whatever was in the working tree would not have the
same id twice.

Before this, an installed editor had to be told where the package was with `ARGUS_UI_PACKAGE`, and
without it every screen became `ui.screen.unknown` at run time. It now needs no such variable; the
variable remains for a layout discovery cannot see.

### The declaration closure, and why it is a closure

`packages/api/src/index.ts` is not one file. It re-exports the generated component declarations
beside it, and it may grow more neighbours. An SDK that carried only `index.ts` would produce, for
every behaviour a person wrote:

```text
build.typecheck | scenes/jump.ts:0:0 | (api).ts:1:49: TS2307: Cannot find module
  './components.generated.js' or its corresponding type declarations.
```

— a tooling failure, reported against the author's file, while their real mistakes went undiagnosed
because the component types had collapsed to an error type. So the packaging follows the imports
from `index.ts` and carries the whole closure, and the service's own checkout-mode discovery walks
it by the same rules (`crates/workspace-service/src/build/tools.rs`, `closure_of`):

- relative specifiers only — a package specifier is not part of the API's own closure;
- `./x.js` resolves to `./x.ts`, which is what `moduleResolution: "bundler"` does with the
  specifier TypeScript emits;
- nothing outside the checkout, and at most 1024 files.

A declaration that resolves to no file stops the packaging, and on a checkout it fails the build
before any check runs, naming the file.

The checker itself is walked the same way. A checkout's fingerprint used to hash `assemble.ts` and
`paths.ts` by name, which held only while those were the two files the checker imported; one added
import would have left every cached answer valid against a checker that had changed. It now hashes
whatever the checker reaches — five small files today — so the fingerprint keeps up by itself. A
packaged SDK has no such question: the bundle is one file and its bytes are in the id.

## Identity

`sdkId` is the SHA-256 of the SDK's own canonical file listing — every path with its digest and its
length, sorted. Nothing else: no build time, no machine name, no version string of its own.

The listing carries every file's path, content hash and length, so the id is a digest over the
**bytes of the whole SDK** — but only because the listing is required to be complete. On its own,
a digest of a listing says nothing about a file the listing leaves out, and an emptied listing
given a consistent id would have verified against nothing at all: the checker itself could have
been replaced and every build would still have reported `typecheck: "checked"`. So the service
refuses an SDK holding a file its manifest does not name, and refuses a listing that names nothing
for a role the editor cannot work without — the checker bundle, `checker/pnpm-workspace.yaml`,
`node`, `esbuild`, at least one `checker/packages/api/**.ts` declaration, `packages/ui/package.json`
and at least one `packages/ui/src/**.ts` source.

**A packed esbuild.** A release ships esbuild zstd-compressed (about 10.6 MB becomes about 3.8 MB of
the Windows install): `esbuild/esbuild.exe.zst` and `esbuild/esbuild.exe.sha256`, the plain bytes' hash.
The `esbuild` role is filled by the plain file or by both of those, named by the listing, so the id
attests to what is run either way. It is unpacked on first use into `tools/<sha256>/` under the
user's cache (`%LOCALAPPDATA%\Argus\cache\tools\`, or `$XDG_CACHE_HOME/argus/tools`), checked against the recorded hash
on every use, and the cache keeps the four most recently used copies
(`crates/export/src/engine_pack.rs`, `unpack_tool`, the deploy engines' unpacker). `argus.exe`
unpacks it at start-up and names it in `ARGUS_ESBUILD` for everything it starts; opening the SDK
finds the same copy. The install folder itself is never written, so its files stay exactly the
ones the engine manifest lists. First use costs about 0.15 s, later ones a hash of the copy.

**A packed compiler.** A release also ships the shared TypeScript compiler zstd-compressed
(about 3.6 MB becomes about 0.8 MB): `checker/packages/harness/src/typescript.cjs.zst`, and in place of the
compiler a `typescript.cjs` of a dozen lines that unpacks it in memory, with the runtime's own zstd
(node 22.15 or later; the packer refuses an older one), and runs it as itself. The checker, the
install's engine CLI and the `typescript` role load the same path either way, and nothing is
written to disk. Both files are in the listing, so the id attests to the compiler. Unpacking costs
20-30 ms on Linux and nothing measurable on Windows; the warm checker pays it once per start.

**A stripped runtime.** A Linux release ships `node/node` without its symbol and debug tables
(`--strip-node`, GNU `strip --strip-all`): about 120 MB becomes about 105 MB, and nothing node runs uses
them. The upstream binary is checked against nodejs.org's `SHASUMS256.txt` when it is downloaded;
what an install verifies is the listing's hash of the stripped bytes, so the packer strips twice and
refuses a strip that does not give the same bytes both times, and refuses a stripped runtime that
does not answer `--version` as the upstream one does or cannot write a diagnostic report. The
bytes are the same for the same `strip`; another binutils version may write others, so
`NOTICES.md` names the strip used, with the upstream binary's size and hash. Dynamic symbols, which
native addons link against, are kept. A Windows `node.exe` is not stripped: it carries no symbols (the strip saves 15 KB) and is
signed by its publisher, which rewriting it would undo; the packer refuses it.

**A packed runtime.** A release also ships the runtime zstd-compressed (`--pack-node`), the way
it ships esbuild: `node/node.zst` (`node/node.exe.zst` on Windows) and a `.sha256` of the plain
bytes in place of the binary. Most processes never run Node, so it is not unpacked at start-up:
the first tool that asks for it (the agent's critic, the editor's asset tools; `installed_node`)
unpacks it into the user's tool cache, checks the copy against the `.sha256`, and keeps the answer
for the rest of the process. The cache holds the 8 most recently used tools (`TOOL_CACHE_ENTRIES`).
`ARGUS_NODE` names another runtime and skips all of this, and is the way out where the cache cannot
hold a program that runs (a `noexec` home, a locked-down Windows). Measured on Ares's release round
(16 Oct 2026, node v22.17.1): the Linux runtime packs to 28.6 MB, and the SDK is 40.95 MB; Windows
`node.exe` goes from 85.19 MB to 24.25 MB, and the installed payload from 165.98 MB to 112.47 MB.
On Linux (Triton's A/B for the 16-Oct merge), `node` goes from 108.51 MB to a 29.79 MB `node.zst`,
78.72 MB saved, and the packed install measured 122.50 MB. The installed-size baselines
(`payload-size-baseline*.txt`) are the larger of the 0.7.0 baseline less that saving and the
measured install: Windows 120,358,712 B, Linux 122,497,694 B.
A fresh user's first `critic.check` (which unpacks it) took 0.73 s on Linux and 2.33 s on Windows,
and the next one 0.45 s and 0.43 s.

No unkeyed digest inside a pack can defend that pack against whoever can rewrite it; this needs
write access to the SDK directory or control of `ARGUS_SDK`. What the two rules buy is that such a
rewrite has to replace the whole SDK rather than one file, and the id — which every build records
as `toolchain.apiSha256` — changes when it does.

That has three consequences worth stating:

- **Two packing runs over the same inputs give the same id.** `scripts/sdk/pack.mjs` writes no
  timestamps and no absolute paths, and it runs esbuild in the checkout root with relative paths so
  the bundled checker's own bytes do not depend on where it was built.
- **One changed declaration is a different SDK.** The id is the typecheck fingerprint, so a
  regenerated component field invalidates the checker image and every cached answer. Source that
  was accepted against the old declarations is checked again against the new ones.
- **A manifest cannot borrow another SDK's identity.** The service recomputes the id from the
  listing and refuses a manifest whose `sdkId` is not the digest of its own files.
- **A manifest cannot attest to less than the SDK.** `sdk.json` is the one file not in its own
  listing; every other file in the tree must be named by it, and the roles above must each be
  named, or the SDK is refused before anything is hashed.

## How the service finds and trusts one

In order (`crates/workspace-service/src/build/sdk.rs`):

1. `ARGUS_SDK`, or `--sdk` on `argus-workspace-service serve`. An SDK named by hand is the one that
   was asked for: if it does not verify, that is the answer, not a reason to look elsewhere.
2. `ARGUS_TOOLCHAIN_ROOT` or `--toolchain-root`, the developer override, which selects
   checkout-based tools instead. Same manifest and fingerprint rules apply to what it finds.
3. `<exe dir>/sdk`, then `<exe dir>/../sdk` — the installed layout, `bin/` and `sdk/` as siblings.
4. A checkout above the project.

`@argus/ui` is looked for under the same two places (`ARGUS_UI_PACKAGE` first, then the project's
own `tsconfig.json` paths, then `node_modules/@argus/ui` or `packages/ui` walking up from the
project, this executable and the working directory, with `sdk/` searched under the last two). One
SDK therefore answers for both: the checker the service runs, and the package the describer
bundles with.

A project's own files are never searched, and `PATH` is never searched.

Before it is used, the tree is walked to check that the manifest names all of it, the listing is
checked for each role, and then every file the manifest names is hashed and compared. The walk is
outside the stamp fast path below, because a file ADDED to the SDK changes no listed file's size
or modification time and the stamp would not see it. A full pass over about 143 MB
costs about a tenth of a second, and the verdict is stamped in the user's cache directory
(`%LOCALAPPDATA%\Argus\cache`, or `$XDG_CACHE_HOME/argus`) against each file's size and modification
time, so later opens skip the hashing. **Nothing is ever written inside the SDK**: it may be
read-only, and on a shared machine it should be.

Five things are refused, each with its own repair, and never with "turn checking off":

| What | The message names | The repair |
|---|---|---|
| Nothing at the path | the path | install the SDK, or set `ARGUS_SDK` |
| A manifest that is not one, or an id that is not the digest of its listing | what is wrong with it | re-run the installer |
| A file in the tree the listing does not name, or a listing naming nothing for a role | up to eight unlisted paths, or the roles it names nothing for | re-run the installer |
| A file that is absent, truncated or altered, the runtime included | up to eight paths that disagree | re-run the installer |
| An SDK for another Talos release, or another `<os>-<arch>` | both strings | install the SDK from this release, packed for this machine |

A build that cannot check does not reach Ready. It fails with `build.toolchain-unavailable`, and no
line of the author's code is blamed for it. What was already Ready stays Ready: breaking the SDK
under a running editor fails the next build and takes nothing away from what is running.

A project with no TypeScript in it records `"typecheck": "not-needed"`. It is never reported as
having passed a check it did not need, and its JavaScript still gets the sandbox, import and
module-load checks.

## Packing one

```sh
node scripts/sdk/pack.mjs --out dist/sdk
```

It takes the checker from `packages/harness/src/assemble.ts`, the compiler and its declarations from
the checkout's `typescript`, the API closure from `packages/api/src/index.ts`, the authoring front
end from `packages/ui` (its `package.json`, `README.md`, `src/**` and `theme/**`), esbuild from the
version this workspace resolved, and — unless `--node` says otherwise — the Node runtime that is
running the script. Then it writes the manifest and runs the **offline self-test**, which is both
halves of what an installed editor does, with `PATH` emptied and the SDK's own binaries:

- the SDK's own node runs the SDK's own checker on one behaviour that must pass and one that must
  not. An SDK that answers "fine" to everything is worse than one that will not start;
- the SDK's own esbuild bundles a two-scenario `.tsx` screen against the packed `@argus/ui` —
  through the aliases its `exports` map produces, importing a component from
  `@argus/ui/components` — and the SDK's own node evaluates the bundle and reads the description
  back. A pack short one source file fails here, rather than on the machine it was installed on.

The packing fails if any answer is wrong.

| Option | What it is for |
|---|---|
| `--node`, `--esbuild` | the native binaries for the platform being packed; **required** when it is not this machine |
| `--platform` | the `<os>-<arch>` the manifest records; defaults to this machine's |
| `--self-test-only` | re-run the self-test against an SDK already packed |
| `--link` | hard-link the two large binaries instead of copying them — tests and local runs only |
| `--pack-esbuild <argus-export>` | ship esbuild packed (above), made by `argus-export --pack-engine`; the release does. `--self-test-only` then takes `--esbuild` naming the unpacked copy |
| `--pack-node <argus-export>` | ship the runtime packed the same way (`node/node[.exe].zst` + `.sha256`, after `--strip-node`, so the hash is the stripped bytes'), unpacked into the user's tool cache the first time a tool runs Node (`installed_node`); the release does. `--self-test-only` then takes `--node` naming the unpacked copy |
| `--pack-typescript <argus-export>` | ship the shared compiler packed (above), made by `argus-export --pack-engine`; the release does |
| `--strip-node <strip>` | ship the runtime stripped (above) by this GNU strip; Linux only, the Linux release does |

### One name for a platform

An SDK is built for one platform at a time, because the runtime and esbuild in it are native
binaries. That platform is written, compared and printed as **`<os>-<arch>`** — `windows-x64`,
`linux-x64`, `macos-arm64` — by the packer, the manifest, the service and the installer alike.

The arch half uses Node's and esbuild's spelling (`x64`, `arm64`, `ia32`) rather than Rust's,
because the packer is a Node script reading esbuild's per-platform packages. The service maps its
own `x86_64` onto `x64`, so both sides of the comparison are produced by one rule.

This is not decoration. The two sides used to disagree: the packer wrote the platform as an
`{os, arch}` object and the service compared the operating system alone, so an SDK packed as
`windows-x64` was refused on Windows with *"it was built for windows-x64; this is windows"* and a
real install ended with no SDK at all. `--platform` now refuses anything outside the nine tokens,
naming them, before a byte is packed, and a manifest whose platform is not such a string is a
manifest error rather than something to wave through. That is why the schema is 2.

### The two binaries, and why they are never guessed

Packing for this machine takes the runtime the packer is already running on and the esbuild this
workspace resolved. Packing for any **other** machine must name both, and the packer refuses
rather than substitute:

```sh
node scripts/sdk/pack.mjs --out dist/sdk-win --platform windows-x64 \
  --node C:\path\to\node.exe --esbuild path\to\@esbuild\win32-x64\esbuild.exe
```

Leave out `--node` and it stops: *"packing for windows-x64 on linux-x64 needs `--node` with that
platform's node binary: this script's own runtime is a linux-x64 one and would be copied into the
pack."* That sentence exists because the opposite happened. `process.execPath` is always *a* node,
so nothing failed, and a Windows SDK was written holding a Linux `node` under the name `node`.

Naming a path is not enough either, because a file name proves nothing about a binary. Both files
are read before anything is packed and refused unless the header matches the target:

| Target | Header | Architecture read from |
|---|---|---|
| `windows-*` | `MZ` then `PE\0\0` | `IMAGE_FILE_HEADER.Machine` |
| `linux-*` | `\x7fELF` | `e_machine` |
| `macos-*` | Mach-O, or a universal binary | `cputype` |

So a Linux `node` handed to a Windows pack is refused with *"is not a PE (Windows) executable …
It looks like ELF (Linux)"*, and an arm64 binary in an x64 pack is refused by architecture. Nothing
is written when either check fails.

**Getting a Windows runtime on another machine.** Take it from an installed Node (`node.exe` from
`C:\Program Files\nodejs`), or download the pinned release and check it against the published
`SHASUMS256.txt` before pointing `--node` at it:

```sh
curl -fSLO https://nodejs.org/dist/v22.17.1/win-x64/node.exe
curl -fSL  https://nodejs.org/dist/v22.17.1/SHASUMS256.txt | grep 'win-x64/node.exe' | sed 's|win-x64/||' | sha256sum -c -
```

The version that ends up in the pack is recorded in `sdk.json` either way, so what was shipped is
never in question.

Cross-packing runs the self-test nowhere: it prints `self-test NOT RUN` and says to run
`--self-test-only` on the target machine. It is never reported as passed.

Size, for a recent release: about 190 files, about 143 MiB on `linux-x64` and about 104 MiB on
`windows-x64`, nearly all of it the Node runtime. `@argus/ui` is a small part of it, the same on every
platform. These figures move from release to release.

## Checking a machine

```sh
argus-workspace-service sdk --check
argus-workspace-service sdk --sdk "C:\Users\me\AppData\Local\Argus\sdk" --check
```

It prints what this machine checks with — SDK or checkout, its identity, its versions, the
declaration files it carries — and with `--check` it runs a good and a bad behaviour through the
real checker and times them. It exits non-zero when nothing here can check, so an installer or a
support script can read the exit code while a person reads the JSON.

```json
{
  "mode": "sdk",
  "ok": true,
  "sdk": { "id": "43aeebb0…", "versions": { "argus": "0.1.0", "typescript": "5.9.3" } },
  "check": { "coldMs": 523, "warmMs": 534, "good": [], "bad": ["bad.ts:3:7: TS2322: …"] }
}
```

## Installing one

```powershell
scripts\windows\install-editor.ps1 -Exe target\release\argus-editor.exe -Sdk dist\sdk
```

The SDK is copied to `<Install>\sdk.incoming`, verified there with the service that was just
installed, and only then swapped into `<Install>\sdk`. An upgrade that is interrupted, refused or
unverifiable leaves the SDK that was already installed in place and usable; a swap that fails puts
the old one back rather than leaving an install with no SDK.

Without `-Sdk` and with no SDK already installed, the installer warns, and the editor will not
check TypeScript: a build containing TypeScript **fails**, naming the missing SDK. It does not run
unchecked.

## The check being off

The editor never turns the check off. It used to add `--no-typecheck` whenever it could not see a
checkout, which on an installed editor was always.

The developer bypass is `ARGUS_EDITOR_NO_TYPECHECK=1`, which the editor passes on as
`--no-typecheck` and announces in its log. A build made that way records `"typecheck": "skipped"`
and a limitation saying how many modules were not checked. It is visibly unchecked output and
cannot stand in for a checked build.

Every Ready build's `build.json` records what checked it:

```json
"toolchain": {
  "apiSha256": "43aeebb0…",
  "sdk": { "root": "…/sdk", "id": "43aeebb0…", "versions": { … },
           "apiFiles": ["checker/packages/api/src/index.ts"] }
}
```

so a diagnostic can be traced to the exact declarations it was made against.

