diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..987f1c1
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,87 @@
+name: CI
+
+on:
+ pull_request:
+ push:
+ branches:
+ - main
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ci-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+env:
+ CI: true
+
+jobs:
+ lint:
+ name: Lint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: pnpm/action-setup@v6
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 24
+ cache: pnpm
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm lint
+
+ package-tests:
+ name: Package tests
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: pnpm/action-setup@v6
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 24
+ cache: pnpm
+ - run: pnpm install --frozen-lockfile
+ - name: Test workspace packages
+ env:
+ DECKD_URL: http://127.0.0.1:1
+ run: pnpm run --filter './packages/**' --if-present -r test
+
+ deckd-build:
+ name: Rust deckd build
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - name: Show Rust toolchain
+ run: rustc --version && cargo --version
+ - name: Build deckd
+ run: cargo build --locked --manifest-path packages/deckd/Cargo.toml
+ - name: Preserve deckd for collaboration replay
+ uses: actions/upload-artifact@v4
+ with:
+ name: deckd-linux
+ path: packages/deckd/target/debug/deckd
+ if-no-files-found: error
+ retention-days: 1
+
+ collaboration:
+ name: Collaboration replay
+ needs: deckd-build
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: pnpm/action-setup@v6
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 24
+ cache: pnpm
+ - run: pnpm install --frozen-lockfile
+ - uses: actions/download-artifact@v4
+ with:
+ name: deckd-linux
+ path: .ci/deckd
+ - name: Replay collaboration through the built deckd
+ env:
+ DECKD_BIN: .ci/deckd/deckd
+ run: |
+ chmod +x "$DECKD_BIN"
+ pnpm test:collaboration
diff --git a/.gitignore b/.gitignore
index 68bbe91..adb1308 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,15 +7,16 @@
.pnpm-store/
.deck/
# A depot carries its own machinery inside itself, and none of it is source:
-# `.deck/` is the store, `.jj/` is the seal log, and jj colocates a backing
-# `.git` beside it — which git otherwise mistakes for an embedded repository
-# and refuses to descend into.
+# `.deck/` contains the store, Repository refs, indexes, and History. deckd's
+# jj backend also colocates machine-generated `.jj/` and `.git/` directories,
+# which the outer git repository must ignore.
.jj/
examples/*/depot/.git/
examples/*/depot/app/viewer/build.json
examples/*/depot/app/viewer/states.json
examples/*/depot/app/library/shelf.json
# `deck fork` WRITES these into the depot: they are demo output, not source.
+examples/collaboration/depot/
examples/contribute-to-codelist/depot/ana/
examples/contribute-to-codelist/depot/kim/
examples/two-agents/depot/agent-a/
diff --git a/CLI.md b/CLI.md
index fc50392..582fe83 100644
--- a/CLI.md
+++ b/CLI.md
@@ -54,7 +54,7 @@ That scaffolds a depot, serves it, and prints a URL. Open it, edit `app.js`,
reload. Then the loop that runs for the rest of the day:
```bash
-deck serve --root ./work --history auto
+deck serve --root ./work --history deckd
```
`--root` is a directory *of* depots: every child directory becomes one,
@@ -66,7 +66,7 @@ every save:
seal, no noise. Touching a file is not a change.
3. **Auto-publish** `-dev.N`, tagged `dev`. Immutable from that
instant.
-4. **Seal it as a Step** (when `jj`, historyd, or **deckd** is attached) —
+4. **Seal it as a Step** (when **deckd** is attached) —
including the saves that did *not* publish, so the states between versions
exist too. With `DECKD_URL`, deckd’s FS watch owns seals; Hub still
publishes + HMR.
diff --git a/INSTALL.md b/INSTALL.md
index 8c992f2..7d631f4 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -16,8 +16,8 @@ from this repository instead.
| --- | --- | --- |
| **Node** | ≥ 22 | required — the CLI is TypeScript with no build step, and relies on Node's native type stripping |
| **pnpm** | 11 | required to install the workspace's dev dependencies (`packageManager` pins the exact version) |
-| **jj** | any recent | optional — Timeline via the jj CLI when no daemon is running |
-| **Rust / cargo** | stable | optional — only to build `packages/deckd`, the in-tree Timeline daemon |
+| **jj** | any recent | optional — History via the jj CLI when no daemon is running |
+| **Rust / cargo** | stable | optional — only to build `packages/deckd`, the in-tree History daemon |
Verified on Node v24.17.0, pnpm 11.0.9, jj 0.43.0.
@@ -98,16 +98,16 @@ pnpm test && pnpm lint
Expect 189 core, 22 history, 79 server, 4 conformance, 13 CLI — all
passing — and no lint or type errors.
-## Check whether you got a Timeline
+## Check whether every-save History is active
```bash
deck hello ./playground
```
The banner it prints includes a line naming the history backend it attached
-to, or the reason it could not. `jj` present means a Timeline and a Step per
-save; anything else means Versions only, which is still every settled save as an
-immutable version.
+to, or the reason it could not. An attached deckd means every-save History and
+a Step per save; anything else means Versions only, which still keeps every
+settled save as an immutable version.
## Environment
@@ -115,7 +115,7 @@ immutable version.
| --- | --- | --- |
| `DECK_KEYRING` | `~/.deck/keys` | where `deck keygen` writes private keys. They never leave the machine |
| `JJ_BIN` | `jj` | which `jj` binary to seal with when no daemon URL is set |
-| `DECKD_URL` | — | point the Timeline at in-tree **deckd** (`packages/deckd`, default listen `http://127.0.0.1:8787`). Preferred over `DECK_HISTORYD_URL` when both are set |
+| `DECKD_URL` | — | point the History at in-tree **deckd** (`packages/deckd`, default listen `http://127.0.0.1:8787`). Preferred over `DECK_HISTORYD_URL` when both are set |
| `DECK_HISTORYD_URL` | — | legacy alias for an external historyd; same HTTP contract as deckd |
To run Hub with deckd from this clone:
@@ -136,7 +136,7 @@ and [`docs/deck-daemon.md`](docs/deck-daemon.md).
| `Cannot find package '@cardstack/deck'` | `pnpm install` has not run, or you are running a copy of `packages/cli` from outside the workspace |
| `EADDRINUSE` from `deck serve` | the port is taken. `deck hello` walks forward up to ten ports on its own; `serve` does not, because a server you asked to be on 8788 should not quietly be somewhere else |
| `deck: --realm is now --depot` | an old flag. It still works and will keep working; the message is a note, not a failure |
-| history reports Versions only | `jj` is not on PATH, and `DECKD_URL` / `DECK_HISTORYD_URL` point at nothing. The live loop is unaffected |
+| history reports Versions only | deckd is not running (`DECKD_URL` / `DECK_HISTORYD_URL` point at nothing). The live loop is unaffected |
## Uninstall
diff --git a/README.md b/README.md
index 27af6dc..9651766 100644
--- a/README.md
+++ b/README.md
@@ -1,423 +1,292 @@
# Deck
-**Deck is version management without the commit, package management
-without the install, and cloud delivery without the deploy.**
-
-One thing makes all three true: there is only one kind of object here — a
-file, named by the hash of its own bytes, reachable at a URL. A tree is a
-list of those names. A Version, a Step, a dependency, a fork and a release
-are all pointers to trees. Commit, install and deploy all do the same job:
-they move a copy to a second place that names things differently. Here
-there is no second place.
-
-| | |
-| --- | --- |
-| A commit, a save, a snapshot are just | the hash of the tree, taken when you stopped typing. |
-| A version, a tag, a release, a staging environment are just | pointers to one of those hashes. Moving a pointer copies nothing. |
-| An import, an install, a lockfile, `node_modules` are just | an address in an import map, written into a file you can read. |
-| A branch, a fork, a pull request are just | the same package under your own publisher, with the tree you started from recorded. That record is the whole difference between a copy and a proposal. |
-| A code review, a preview environment are just | the page loaded with `?with=` pointing somewhere else, for one request. |
-| A merge is just | three trees: yours, theirs, and the one you both started from. |
-| A revert, a rollback, a checkout are just | reading a different address. Nothing on disk changes. |
-| A build, a bundle, a deploy, a CDN are just | absent — except the bundle, which is another version derived from the first, at its own address. |
-
-None of those is really a mechanism. They exist because three systems each
-keep their own address space, and something has to translate between them.
-With one address space, committing, installing and deploying are not three
-things you do to code — they are three names for pointing at it.
-
-```bash
-node packages/cli/bin/deck.js hello
+**Version management without the commit.
+Package management without the install.
+Code collaboration without the clone.
+Cloud delivery without the deploy.**
+
+Deck gives files, packages, dependencies, runnable applications, and collaborative
+work one content-addressed system. The browser can run the same immutable state
+that a reviewer inspected, a release named, and another package imported.
+
+Every settled save is safe without asking the author to make a commit. Intentional
+collaboration still has the familiar nouns—repository, branch, fork, Review, and
+merge—but those nouns coordinate immutable package state instead of copying a
+monorepo between machines.
+
+## The model
+
+```text
+bytes ──hash──▶ package tree ──name──▶ Version ──URL──▶ browser
+ │
+ └──────────────▶ History Step (every settled save)
+
+exact package Versions + exact RRI lock ──▶ Repository
+ │
+ authored as
+ ▼
+ Checkpoint
+ ▲
+ branch ──┘ mutable ref
+
+source branch ──exact Checkpoint──┐
+ ├──▶ Review ──preview──▶ Browse + Run
+target branch ──exact Checkpoint──┘ │
+ └──merge──▶ two-parent Checkpoint
+ │
+ └──CAS──▶ target branch
```
-That scaffolds a working app, serves it live, and prints the URL. Edit
-a file, reload, see it. That is the whole loop.
+The layers have deliberately different jobs:
-Nothing here is published to npm yet, and **`npx deck` is somebody else's
-package** — install from this repo. [INSTALL.md](INSTALL.md) covers the
-three ways; [CLI.md](CLI.md) is the CLI's own readme.
+| Layer | Meaning |
+| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **Version** | An immutable name for one package tree. Settled saves can create Versions automatically; releases add durable semver names without copying bytes. |
+| **History Step** | One ordered every-save state from `deckd`. History is daily safety and does not require commit ceremony. |
+| **Repository** | An immutable set of exact package Versions plus one exact Realm Resource Identifier (RRI) lock. A mutable package is already an implicit one-package Repository; `repo-init` names a multi-package Repository. |
+| **Checkpoint** | An authored collaboration point that binds a Repository, History head, completed index generation, message, and ordered parent Checkpoints. |
+| **Branch** | A mutable compare-and-swap ref inside one owner/Repository. Creating one clones the selected Repository state; unchanged content remains shared in CAS. |
+| **Fork** | The same storage operation across an ownership boundary. It creates a mutable package in another publisher scope and records ancestry. |
+| **Review** | Durable source-to-target metadata bound to exact Checkpoints, with an append-only event history and an exact runnable preview. |
+| **Merge** | A three-way reconciliation of base, target, and source Repositories, followed by one target-branch compare-and-swap and a new two-parent Checkpoint. |
+
+Imported packages are read-only. To change one, create a branch when you control
+its Repository, or fork it into your own publisher scope. A fork of Three.js can
+therefore become a Repository too; package and Repository are not competing
+containers in Deck.
## The layout is the address
-A depot is a directory. What `ls` shows and what the URL says are the
-same thing:
+A depot is a directory. Live source has the same shape on disk and over HTTP:
+```text
+/site/acme/palette/index.js on disk
+ /site/acme/palette/index.js live — every save, no-store
+ /site/acme/palette@1.1.0/index.js
+ exact Version, immutable forever
+ /site/_history//acme/palette/index.js
+ an earlier every-save state
```
-/site/acme/palette/palette.js on disk
- /site/acme/palette/palette.js live — every save, no-store
- /site/acme/palette@1.1.0/palette.js
- the sealed copy, immutable forever
- /site/_history//acme/palette/palette.js
- any state it was ever in, published or not
+
+Canonical identities are RRIs such as `@acme/palette/` and
+`@acme/palette@1.1.0/`. Realm URLs are transport projections. Authored locks,
+Repositories, Checkpoints, and indexes use RRI identity so moving a realm does
+not change what its content means.
+
+Each depot carries its own Deck resources:
+
+```text
+site/
+├── acme/… live authored packages
+└── .deck/
+ ├── store/ immutable blobs, trees, Versions, locks,
+ │ Repositories, and Checkpoints
+ ├── refs/heads/ mutable branch refs (conditional writes)
+ ├── reviews/ Review openings, heads, and event chains
+ ├── history/ deckd/jj every-save History
+ └── indexes/ immutable completed index generations
```
-`ls` and `cat` give you the LATEST of everything, and an agent can
-change any of it. Adding `@` to the package segment reads the
-same path out of a seal instead. A depot carries its own machinery
-inside itself — `.deck/store/` for sealed versions, `.jj/` for seals —
-both invisible to the tree hash, so copying the directory takes the
-live files, every published version, and the whole history with it.
+Agents can inspect this state as ordinary files. Mutations go through Deck APIs
+and conditional writes so two writers cannot silently replace one another. A
+desktop backend uses the same contracts on a regular filesystem; a hosted realm
+can place the realm-contained tree on S3 Files and use S3 conditional writes for
+durable refs.
-## Three levels of volatility, all just URLs
+## Run it locally
-| You want | You write | You get |
-| --- | --- | --- |
-| every save, instantly — 18 agents on one tree | `"acme/palette": "live"` | `/site/acme/palette/…` |
-| the freshest seal, for a shape still moving | `"acme/palette": "dev"` | `/site/acme/palette@1.2.0-dev.7/…` |
-| something dependable underneath you | `"acme/palette": "^1.0.0"` | `/site/acme/palette@1.1.0/…` |
+Prerequisites and install options are in [INSTALL.md](./INSTALL.md).
-`latest` is a dist-tag on a stable release. **`latest` is not live** —
-live is the bytes on disk, with no version segment at all.
+```bash
+pnpm install
+./scripts/run-hub-with-deckd.sh
+```
-Ranges never match prereleases, so a `^1.0.0` dependency cannot pick up
-somebody's auto-published dev line by accident. Opting into churn is
-always explicit.
+Open . One port serves the Hub chrome, Browse, Run,
+Track, Review, and every demo through `*.localhost:8788` virtual hosts.
-## The workflow
+The Hub has four working views over the same state:
+
+- **Browse** — files, packages, Versions, branches, exact diffs, and the
+ Repository underneath.
+- **Run** — a page or a generated harness using an exact Repository projection.
+- **Track** — automatic Versions and every-save History Steps.
+- **Review** — a queue of durable Reviews, exact package diffs, runnable
+ previews, activity, Checkpoints, and merge state.
+
+## The daily loop
```bash
-deck serve --root ./work # every depot under ./work, live + auto-publishing
-# …edit anything. Every settled save auto-publishes -dev.N (tag: dev)
-# and is sealed, whether or not it is published.
+deck serve --root ./work --history deckd
+# edit normally; every settled save is addressable and kept in History
deck release --depot ./work/site --publisher acme --name palette \
- --version 1.1.0 --from dev --tag latest # instantly a real semver, zero bytes copied
+ --version 1.1.0 --from dev --tag latest
+# the Version is a pointer to existing sealed state: zero bytes copied
-deck lock --depot ./work/site --publisher you --name app
-# resolves this deck's declared RANGES to exact versions and pins them in
-# its import map — what your users get, so they never inherit your
-# dependencies' working trees. Move them forward when you're ready.
+deck history ./work/site
+deck restore ./work/site
+# restore replays forward, so the state you leave remains recoverable
+```
-deck fork --depot ./work/site --from acme/palette --as you/palette --use-in you/app
-# copy someone's deck into YOUR publisher scope and change it at byte speed.
-# Separate version line, separate dist-tags. Their versions never move.
+Volatility is explicit:
-deck offers --depot ./work/site # every open proposal, and whether it still merges
-deck rebase --depot ./work/site # re-merge them all; conflicts are reported, not written
+| Dependency choice | Result |
+| ---------------------- | ----------------------------------------- |
+| `workspace:*` / `live` | Current bytes on disk, `no-store` |
+| `dev` | Latest automatically published prerelease |
+| `^1.0.0` | A stable Version satisfying the range |
+| `1.1.0` | One exact immutable Version |
-deck history ./work/site # every state the depot has been in
-deck restore ./work/site # go back to any of them; the state you
- # leave is sealed first, so this is undoable
-```
+`latest` is a dist-tag, not the working tree. Ranges never select prereleases.
+The conventional `package.json` suggests ranges; the exact import-map lock
+decides what executes.
-The publisher segment IS the fork boundary: `acme/palette` and
-`you/palette` are different names with different version lines in the
-same depot. An undiverged fork costs no bytes — the store is content-
-addressed, so identical content is stored once.
-
-How each promise is kept:
-
-| Promise | Mechanism |
-| --- | --- |
-| instant feedback | the version-less URL serves your working directory, `no-store` |
-| never lose work | every settled save auto-publishes an immutable version, and every save is sealed |
-| revert anytime | `deck restore ` — replayed forward, never rewinding |
-| ship any version | `deck release … --from dev` — names a sealed state, copies nothing |
-| share any milestone | `deck pack ` → one Deck Pack (`.pack.zip`) any zip tool opens |
-| use any library | `deck vendor https://esm.sh/three@0.160.0 …` — pulled in for good, hermetic |
-| fork freely | `deck fork` — your own publisher scope; theirs is untouched |
-| stability where you need it | `deck lock` — ranges in the tree, exact pins in the map |
-| whole web platform | plain ES modules and import maps; no bundler, no transpile |
-
-Deck is deliberately independent:
-
-- **No framework required.** The protocol serves ANY vanilla JS/HTML
- app. Nothing here depends on `.gts`, card-api, or Boxel — those
- are one consumer.
-- **Reference implementation here**: a CLI and a minimal Node
- webserver, developed and tested standalone.
-- **The Boxel realm server will IMPLEMENT Deck later**, when this is
- mature. A working at-rest implementation already exists inside the
- realm server (branches `deck/01`–`deck/07` on the
- `jj-history-sidecar` worktree of the boxel monorepo, L1–L7, fully
- tested) — it is prior art and a re-import target, not the home.
-
-## Contribution: the proposal is a version, not a diff
-
-A fork records its base — package, version, and treeHash — in its own import
-map, so a fork IS a proposal. Nothing registers it; there is no branch and no
-side table. That makes two things possible that a diff-based flow cannot do.
-
-**Review by running, not by reading.** Every proposal is already published at
-a URL, so trying one is a query parameter:
+## Branch, Review, and merge
-```
-/site/you/app/index.html?with=acme/palette=you/palette
-/site/you/app/index.html?with=acme/palette@1.1.0
-```
+The CLI exposes the same protocol that the Hub and agents use:
-The server resolves the app's import map differently for that one request —
-same rules `deck lock` uses, applied at serve time. No checkout, no build, no
-branch, and the app's own tree is untouched. Repeat `with` to try several at
-once. A trial is never cached as if it were the sealed thing.
+```bash
+deck repo-init --depot ./work/site --owner acme --name dashboard \
+ --root app/dashboard --member app/dashboard --member acme/editor \
+ --member acme/theme --history "$HISTORY_HEAD" --index "$INDEX_HASH"
+
+deck branch --depot ./work/site --repo acme/dashboard \
+ --name kim/carrier-routing --from main
+
+# edit the branch's packages, then create an intentional collaboration point
+deck checkpoint --depot ./work/site --repo acme/dashboard \
+ --branch kim/carrier-routing --message "Route recovery work" \
+ --history "$HISTORY_HEAD" --index "$INDEX_HASH"
-**Nobody rebases.** `deck serve --rebase` keeps every open proposal merged
-against upstream continuously: the base is exact and both sides are
-content-addressed, so re-merging is a pure function a machine can run. Clean
-merges are written into the proposal's own tree — which auto-publishes and
-seals like any other save — and the base pointer moves to what it absorbed.
-Conflicted ones are reported and **left strictly alone**: a machine never
-damages a contributor's work.
+deck review --depot ./work/site --repo acme/dashboard \
+ --source kim/carrier-routing --target main \
+ --title "Route recovery work to carrier desks"
-So a maintainer with 200 open proposals sees the three that stopped merging,
-not 197 that need a rebase.
+deck merge --depot ./work/site --repo acme/dashboard --number 2 \
+ --history "$HISTORY_HEAD" --index "$INDEX_HASH"
+```
+
+`History` and index values come from the realm services in an integrated host;
+the explicit CLI flags keep the protocol boundary visible. The executable
+[collaboration replay](./scripts/replay-collaboration.mjs) builds a realistic
+multi-party Repository from statements, including vendoring, automatic
+Versions, branches, Checkpoints, a realistic Review queue, an approved merge,
+and a rejected conflict. Run its assertions with:
-JSON is merged as data rather than as lines. In a pretty-printed object,
-adding a key makes the line above grow a comma, so a line merge would call
-that a conflict — and manifests and card instances are JSON. Merging by key
-means only the same key, changed differently on both sides, is a real
-disagreement.
+```bash
+pnpm test:collaboration
+```
-Accepting a proposal is a pointer move: upstream publishes the treeHash the
-proposal already holds. **Zero bytes are copied.**
+Temporary `?with=` substitutions remain useful for trying alternate package
+Versions. They are preview transport, not Review identity. A Review records the
+exact Repository and Checkpoints that were examined.
-## Bring a CDN library in-house
+## Bring a library in-house
```bash
deck vendor https://esm.sh/three@0.160.0 --name three --version 0.160.0 \
--depot ./work/site --publisher acme --tag latest
```
-That pulls three.js's whole module graph from esm.sh ONCE, rewrites
-every cross-module reference to stay inside the pack, and seals it.
-Your app's import map then points at your own server:
-
-```html
-
-
+Deck walks the ESM graph once, rewrites internal references to remain inside the
+vendored tree, verifies it, and seals it. Applications then import the reviewed
+Version from their own Deck server. Vendoring is hermetic by default: an
+unresolved external edge fails instead of hiding a runtime dependency on the
+public web.
+
+For npm packages with dependencies of their own, `vendor-tree` brings in the
+graph as independently addressable packages. Shared content is stored once.
+
+## Packs and delivery
+
+`deck pack` is transport, backup, cache fill, and interop—not a second package
+identity. A canonical `.pack.zip` round-trips to the same tree hash and can be
+opened by ordinary ZIP tooling.
+
+Exact content URLs are immutable and safe behind a CDN. Public catalog entries
+can use ordinary public caching. Private realms can use an authenticating edge
+or signed cookies/URLs while retaining immutable cache keys. The origin remains
+authoritative for authorization and mutable refs; the CDN delivers content.
+
+## CLI
+
+[CLI.md](./CLI.md) is the command reference. `deck help` prints the live command
+map, and `deck help ` explains one command from the same records used by
+the dispatcher.
+
+The groups are:
+
+```text
+start hello serve
+seal hash pack unpack list verify
+publish publish tag release prune store derive verify-derivation
+depend vendor vendor-tree lock verify-links
+repository repo-init branch checkpoint review merge
+propose fork offers rebase
+endorse keygen sign verify-sig
+recall history restore
+prove conform
+say claim note
+```
+
+The older package-level `fork` / `offers` / `rebase` flow remains a useful
+single-package proposal surface. Repository Reviews are the durable multi-package
+collaboration surface and are the basis of the Boxel backport.
+
+## Protocol documentation
+
+- **[Deck laws](./docs/deck-laws.md)** — the executable protocol contract.
+- **[At rest](./docs/deck-at-rest.md)** — depot layout, storage, addresses, and
+ History.
+- **[Collaboration protocol](./docs/deck-collaboration-protocol.md)** — RRI-native
+ Repository, Checkpoint, branch, Review, and merge objects.
+- **[Step, Version, and History](./docs/deck-step-version-vocabulary.md)** — the
+ every-save and authored-state vocabulary.
+- **[Pack equivalence](./docs/deck-pack-equivalence.md)** — canonical pack laws and
+ carriage modes.
+- **[Storage backends](./docs/deck-storage-backends.md)** — local filesystem and
+ realm-contained S3 Files architecture.
+- **[Efficient delivery](./docs/deck-efficient-delivery-spec.md)** — derived trees,
+ compression, preload closure, and CDN responsibilities.
+- **[Hub tour](./docs/deck-hub-tour.md)** and **[UI requirements](./docs/deck-ui-spec.md)**
+ — how to inspect the implementation and judge the product surface.
+
+## Implementation
+
+The packages layer so Deck Core can move into Boxel without importing the
+standalone server or tools:
+
+```text
+core ← server ← tools ← cli
+ └── history contract ← deckd (Rust + jj-lib)
+ └── conformance laws
```
-Verified end to end against the real esm.sh: three.js r160 loads and
-executes in a browser with every request going to the Deck server and
-none to any CDN. Vendoring is **hermetic by default** — if the graph
-reaches a host the walker will not follow, the command fails rather
-than sealing a pack with a live external reference inside it.
-`--allow-external` (keep it live) and `--allow-host` (follow it too)
-are the explicit opt-outs. The canonical source travels with the
-bytes as `vendoredFrom`, so a consumer can always tell what a
-vendored module IS — dedupe it, upgrade it, or check it upstream.
-
-Because a browser only honours an INLINE import map, the server
-injects each document's map when it serves HTML — including replacing
-an inert `
+
+
-