carlos / platform Public

docs: the corpus, and the gates stop skipping

Two pages — the index and the CLI reference — which is a complete corpus
and enough to turn every gate on for real. The other thirteen pages come
next.

The CLI page is written from main.go's grouping but not its wording: the
usage text is terse because it has to fit a terminal, and a reference
page has room to say what a command is for. Every command's behaviour
was read out of its implementation rather than paraphrased from usage
strings, including the two the usage groups do not cover (`help`, and
`canary`, which is still a reserved no-op).

anchors.json also picks up "-app" and "--app", which both reduce to
"app": a real collision the existing combined case only hinted at.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 184baac5293afdf16e9ebab1b5882ffe03d651ef parent 5873654
9 files changed, +636 −26
  • AGENTS.md +6 −0
  • docs/site/anchors.json +3 −1
  • docs/site/cli.md +595 −0
  • docs/site/index.md +17 −0
  • docs/site/nav.json +14 −0
  • internal/docsite/cli_test.go +0 −4
  • internal/docsite/fences_test.go +0 −7
  • internal/docsite/links_test.go +0 −6
  • internal/docsite/nav_test.go +1 −8
diff --git a/AGENTS.md b/AGENTS.md
index ee741134a..ee0f7f395 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -103,6 +103,12 @@ read as a single piece rather than commit by commit.
- **When a storage shape changes, the consumer list is everything that PARSES
the shape** — not everything that calls the API. (Learned three times.)
+- **A new `carlos` command is not done until `docs/site/cli.md` names it.**
+ `internal/docsite` reads main.go's dispatch switch and fails the build over
+ a command with no docs, over a fenced example running a verb that no longer
+ exists, and over a `/docs` link whose page or `#fragment` does not resolve.
+ The corpus is rendered by `carlosframework/website`, which vendors a copy —
+ so a change here reaches the site only after that repo re-syncs and ships.
- **Wire-shape changes are additive**: live pointers/manifests exist in
production buckets; anything written before your change must still parse.
- **Production channel names are pinned in tests**
diff --git a/docs/site/anchors.json b/docs/site/anchors.json
index 4f680cecf..554e1c875 100644
--- a/docs/site/anchors.json
+++ b/docs/site/anchors.json
@@ -7,5 +7,7 @@
"Hyphen-joined words": "hyphen-joined-words",
"`-app` and `--app`": "app-and-app",
"canary/rehearsal": "canary-rehearsal",
- "X-Carlos-Version": "x-carlos-version"
+ "X-Carlos-Version": "x-carlos-version",
+ "-app": "app",
+ "--app": "app"
}
diff --git a/docs/site/cli.md b/docs/site/cli.md
new file mode 100644
index 000000000..c9a3f412f
--- /dev/null
+++ b/docs/site/cli.md
@@ -0,0 +1,595 @@
+# The CLI
+
+`carlos` is one binary that plays three roles, picked by the first word you
+type. Most days you are in the first role: you have an app, and you want it
+running somewhere. The second is for people who stand a deployment up and
+administer it. The third is what systemd starts on a box.
+
+Run `carlos help` for the same grouping at the terminal, and
+`carlos <command> -h` for a command's own flags. This page says what each
+command is for and when you would reach for it. It does not list every flag,
+because `-h` already does.
+
+Three flags turn up almost everywhere, and the sections below do not repeat
+them. `-app` names the app. `-account` names the account it belongs to, or
+`-account-id` when you want a sqid matched as a sqid and nothing else; leave
+both off if you belong to exactly one account. `-console` picks which
+logged-in console to act through, and you need it only when this terminal is
+logged in to more than one deployment.
+
+Most app commands work two ways. Signed in with `carlos auth login`, they go
+through a console's API, which is how someone with no box access and no cloud
+credentials gets their work done. With `CARLOS_DEPLOYMENT_BUCKET` or
+`CARLOS_DEPLOYMENT_DIR` set, they write to the bucket directly, which is the
+operator's path. Where a command only works one way, its section says so.
+
+## carlos help
+
+Prints the command list, grouped the way this page is. It goes to stdout and
+exits 0, so `carlos help | less` works. A bare `carlos` with no arguments
+prints the same text on stderr and exits 2, because that one is a mistake.
+
+## Your apps
+
+Everything from claiming a name to watching a deploy land. None of it needs
+access to a box, a cloud console, or the deployment's bucket; that is a rule
+the platform holds itself to, and a command that broke it would be a bug.
+
+## carlos auth
+
+Logs this terminal in to a console. `carlos auth login` prints a short code
+that you approve in a browser already signed in to that console, and the
+browser does not have to be on this machine, which is what makes this work
+over SSH. The token lands in `~/.carlos/credentials` at mode 0600, one entry
+per console, so a laptop can hold credentials for the flagship and your own
+deployment at once.
+
+```sh
+carlos auth login
+carlos auth whoami
+```
+
+Do not do this on a box. A box has no browser and no human, and the
+long-lived token you would leave behind is a standing credential nobody is
+watching; pass `-owner <address>` to
+[carlos accounts](/docs/cli#carlos-accounts) instead. `carlos auth default`
+shows the console this machine talks to by default, and sets it when you give
+it a URL. With `-project` it writes `./.carlos/config`, which you can commit
+so a checkout pins its own deployment.
+
+## carlos apps
+
+Claims an app name, moves an app onto a customer fleet, and deletes or
+restores one. Creating goes through the same door the console's New app form
+uses, so a name that form would refuse is refused here too.
+
+```sh
+carlos apps create -app hello
+```
+
+`carlos apps delete` moves an app to the Trash. Every route drops, custom
+domains included, the name stays reserved, and you have thirty days to
+`carlos apps restore` it. Restoring brings back releases, channels and the
+production flag exactly as they were, but no routes at all — not even the
+platform apex — so plan on adding those again. Delete is refused while the
+app is flagged production; clear the flag first.
+
+`-place fleet/<name>` at create, or `carlos apps place -target fleet/<name>`
+later, puts the app's routes and instances on a fleet your account owns.
+`-target ""` brings them back to platform-served. `-target` is mandatory even
+in that empty spelling: leaving it off is a usage error, never a silent
+clear.
+
+## carlos ship
+
+Publishes a release. The artifact is hashed, stored under its content
+address, and recorded in a manifest that never changes afterwards. Shipping
+does not change what anything serves — for that, see
+[carlos promote](/docs/cli#carlos-promote).
+
+```sh
+carlos ship -app hello -label "fixing modals" ./hello
+```
+
+`-version` is optional once the app has a version target set, and leaving it
+off is the point of having one: ship mints the next iteration under it. Use
+`-kind static` with a directory to ship a site instead of a binary. `-label`
+is one line for humans and shows up in `carlos releases` and in the console,
+so write it for the person doing a rollback at 2am.
+
+## carlos promote
+
+Points a channel at a version you have already shipped. The ladder is
+`canary → edge → beta → stable`: beta is entered from edge and stable from
+beta, so "it was signed off on beta" means something, and reaching stable
+cuts the release's semver tag. Channels named `canary/<something>` are never
+refused, because that is what the fleet itself runs on.
+
+```sh
+carlos promote -app hello a1b2c3d edge
+```
+
+Promoting the same version twice succeeds and does nothing. `-hotfix` skips
+the ladder and is an operator act: it works on the direct-bucket path only,
+and the console refuses it by name, so the flag never quietly does nothing.
+
+## carlos deploy
+
+Ship, promote, and then wait until the app's URL actually serves the new
+build. It watches the `X-Carlos-Version` header on the live host and does not
+report success until that header names what you shipped, over a 2xx. Reach
+for this one by default; ship and promote separately when you want the gap
+between them.
+
+```sh
+carlos deploy -app hello ./hello
+```
+
+Signed-in only. In a project with saved defaults you can run `carlos deploy`
+with no arguments at all, and the first run at a terminal asks what to deploy
+and offers to remember the answer. `-channel` overrides the channel it picks
+(normally the one the app's instances already follow), and `-host` scopes the
+watch to a single instance. A static site needs that second flag, having no
+instances to resolve a channel from.
+
+```sh
+carlos deploy -app website -kind static -host www.example.com ./dist
+```
+
+## carlos restart
+
+Cycles an app's processes with no new version and no config change. This is
+the answer to a wedged instance.
+
+```sh
+carlos restart -app hello
+```
+
+It reports the restart as *requested*, which is the honest word: the console
+touches one small object and every box serving the app notices within
+seconds. Long-running tenants come back within seconds. Exec-backed instances
+stop within seconds and respawn on their next request, which for an idle app
+may be a while. Nothing is stuck: the instance comes back with the next
+request that needs it. Hibernating instances are left asleep.
+
+## carlos rollback
+
+Points a channel back at the version it was serving before.
+
+```sh
+carlos rollback -app hello stable
+```
+
+## carlos channels
+
+What each of the app's channels is serving right now, with the tag if the
+release earned one at stable.
+
+```sh
+carlos channels -app hello
+```
+
+## carlos releases
+
+Every version shipped, newest first, with the label its author typed and the
+tag it earned. Channels answer "what is running"; this answers "what is there
+to run".
+
+```sh
+carlos releases -app hello
+```
+
+There is one sub-verb. `carlos releases retention -keep 20` sets an ambient
+prune policy, and `-dry-run` shows you what it would remove before you commit
+to it. The safety list always wins over the number: channel pointers,
+rollback history, tagged releases, anything inside the bake window, and each
+box's own adopted versions are never pruned, so nothing promoted or recent
+disappears by policy. `-off` goes back to keeping everything.
+
+## carlos version
+
+`carlos version` prints the build id of the binary in front of you. It is the
+first thing to check when a command behaves unlike this page describes.
+
+`carlos version target` is a separate thing wearing the same word: it sets
+the semver your ships count iterations under, so `carlos ship` can mint the
+next one for you.
+
+```sh
+carlos version target -app hello 0.5.0
+```
+
+## carlos env
+
+Plain per-app config vars: `set`, `unset`, `list`.
+
+```sh
+carlos env set -app hello LOG_LEVEL=debug
+```
+
+Values land in the app's default bundle. `-environment <name>` writes a named
+bundle instead, layered on top of the default when config is materialized,
+and `carlos route -environment` is what binds a route to one.
+`carlos env environments -app hello` lists the bundle names an app has.
+
+## carlos secrets
+
+The same shape as `carlos env`, sealed. `list` prints key names and never
+values.
+
+```sh
+carlos secrets set -app hello STRIPE_KEY=sk_live_example
+```
+
+Sealing uses a public key; the private half lives on the box that decrypts
+them and never leaves it, in either mode. `carlos secrets genkey` mints the
+pair, and it is always local to your terminal.
+
+## carlos instances
+
+An app's instances are the places it actually runs. `carlos instances enable`
+is the app's opt-in to provisioning, and until it is set the API refuses to
+create anything. After that, `create`, `list`, `delete` and `set-upstreams`
+work on records through the console, and the box's reconcile pass turns a
+record into a live route.
+
+```sh
+carlos instances enable -app hello
+carlos instances list -app hello
+```
+
+`-health <slug>` narrows the listing to one state: `running`, `asleep`,
+`not-responding` and the rest. That is usually what you want when something
+is wrong.
+
+Typed on a box with no sub-verb, `carlos instances` does something different:
+it lists that box's own registry routes, backing and owning unit included.
+Off the box, [carlos routes](/docs/cli#carlos-routes) answers the same
+question through the console.
+
+## carlos routes
+
+The app's routes on this deployment: where each one sends traffic, which
+channel it follows, and which config environment it is bound to. That last
+column is the one worth knowing about, because it decides which sealed
+secrets the route's process is delivered.
+
+```sh
+carlos routes -app hello
+```
+
+It reads the console's own box, so on a multi-box fleet a short answer is not
+necessarily a complete one. The command says so every time it prints.
+
+## carlos domains
+
+Attaches a customer's own hostname to one of the app's routes, detaches it,
+or lists what is claimed.
+
+```sh
+carlos domains attach -app hello www.example.com
+```
+
+`-route` picks which route to point at, and you can leave it off when the app
+has exactly one instance record. The hostname comes live on the box owning
+that route at its next domains pass, certificate and all. The `list` verb
+joins the claims to the fleet's own readings sweep, so DNS state, certificate
+expiry and delegation are readable without leaving the terminal.
+
+## carlos logs
+
+The app's own stdout and stderr, merged with the platform's events about it:
+wakes, restarts, and failures with the reason. A site served from files has
+no app stream, but its platform events still show up.
+
+```sh
+carlos logs -app hello -since 1h -f
+```
+
+`-f` follows, polling every two seconds until you interrupt it. `-grep` takes
+an RE2 pattern, `-stream app` drops the platform commentary, and `-host`
+narrows to one instance.
+
+## carlos store
+
+Object storage for an app. `create` declares that the app wants a bucket,
+which is a request and not a provision — nothing exists in any cloud account
+until an operator runs `grant`. `status` shows what is declared, granted and
+delivered, including a rotation that stopped half way.
+
+```sh
+carlos store create -app hello
+carlos store status -app hello
+```
+
+`carlos store rotate` mints a fresh credential and leaves the old one live
+until you run it again with `-finish`, so the app has a window to pick the
+new one up. Granting needs a deployment operator; the other three are member
+verbs.
+
+## carlos ledger
+
+Append-only hash-chained ledgers an app can publish: `append` adds an entry,
+`head` prints the chain's current head, `list` shows what the app has, and
+`publish` decides which of them it serves publicly.
+
+```sh
+carlos ledger append -app hello events entry.json
+```
+
+`carlos ledger verify` walks a published chain over plain HTTPS and re-hashes
+every entry. It needs no credentials at all, which is the whole point:
+anybody can check a ledger you publish, including you, from a machine that
+has never been logged in.
+
+## carlos vet
+
+Checks a shipped release against the platform contract before anyone promotes
+it. The manifest has to exist and parse, and every artifact's stored bytes
+have to match the sha256 and size it claims.
+
+```sh
+carlos vet -app hello -version a1b2c3d -boot
+```
+
+`-boot` goes further and runs the binary: it must accept `-socket <path>`,
+serve HTTP on that socket, and answer `GET /healthz` with a 200 within ten
+seconds. That is the entire app contract, and this is the cheapest place to
+find out you have broken it.
+
+## carlos update
+
+Replaces the `carlos` binary on your workstation with the latest published
+release, checking the signed checksums before it swaps anything. Where a
+package manager owns the install, it prints the `brew` or `apt` command
+instead of fighting it.
+
+```sh
+carlos update
+```
+
+It refuses to run as root, and refuses on a box: box binaries are rolled by
+the platform, and a self-updating box would fight that machinery. `-y` skips
+the confirmation, though it still wants a terminal.
+
+## Your deployment
+
+The verbs for the person who owns the boxes. Some of these run against a
+console like the app commands above; others open a box's registry directly
+and only make sense while you are standing on it.
+
+## carlos bootstrap
+
+Prepares a host to be a CARLOS box: the service user, the data directories,
+the systemd units. Run it once per host, as root.
+
+```sh
+carlos bootstrap
+```
+
+`-root <dir>` writes the files into a staging directory instead, creating no
+users and running no systemctl, which is how you read what it would do before
+it does it. `-offcloud` is for a box outside EC2, where credentials come from
+a staged 0600 file instead of an instance profile.
+
+## carlos accounts
+
+Accounts are the tenancy primitive: apps, fleets, credentials and bucket
+prefixes all hang off one. `create` mints an account and prints the sqid
+every object underneath it is keyed by.
+
+```sh
+carlos accounts create -name acme -owner someone@example.com
+```
+
+`-owner` is the path for a box or a CI job, where there is no browser to log
+in with. It needs no identity of its own and takes precedence over any
+logged-in one, which is why it exists.
+
+`carlos accounts migrate` copies an app's objects to an account-qualified
+prefix and re-stamps its routes. Run it with `-dry-run` first; it prints the
+plan and touches nothing.
+
+## carlos fleets
+
+A fleet is a group of remote boxes an account owns, dialing in to this
+console over a channel named `<fleet>/<label>`. Create one against the
+customer's own data bucket, then register each box.
+
+```sh
+carlos fleets create -bucket acme-data acme
+carlos fleets add-box acme pi-1
+```
+
+The box's bearer token prints exactly once, at `add-box`. The console keeps
+only its hash, so losing that output means `rotate-token`; a second `add-box`
+will only refuse the label. Put it straight into the box's credential store.
+
+Rotation is not revocation. A fresh token refuses the box's next dial-in with
+the old one, but it does not evict a channel the box is already holding. If a
+credential may be compromised, `detach-box` is the command that actually
+stops it.
+
+## carlos services
+
+Credentials a server holds rather than a person: a CI job that ships, a
+sidecar that reads instances. Reach is fixed at mint, so adding you to
+another account later does not widen it, and it has its own rate-limit budget.
+
+```sh
+carlos services create -role publish -app hello ci-shipper
+```
+
+Pick the narrowest role that works. `publish` ships releases, `instance` is
+what a box-side reader wants, `operate` administers, and `admin` includes
+`store grant`, which mints a path-scoped IAM user; treat that one as a real
+handover. `-app` binds the credential to one app, which also means it cannot
+create apps.
+
+The secret prints once, same as a fleet token, and the same distinction
+applies at the other end: `rotate` refuses the credential's next call with
+the old secret, `revoke` stops it whatever secret it holds.
+
+## carlos add
+
+Writes a route straight into a box's registry: this hostname, served by this
+app, following this channel. It is the box-local, operator-side counterpart
+to an instance record.
+
+```sh
+carlos add -app hello -socket /run/hello.sock hello.example.com
+```
+
+`-channel` sets what the route follows, `-kind` picks instance, service or
+static, and `-addr` takes a TCP upstream where the app is not a socket
+tenant. `-unit console.service` names the systemd unit that owns the route's
+process, which is what lets adoption cycle it; leave it off for an ordinary
+exec child.
+
+## carlos remove
+
+Drops one of this box's routes. It stops routing the hostname and never
+touches the instance's database.
+
+```sh
+carlos remove hello.example.com
+```
+
+## carlos route
+
+Changes one thing about a route that already exists. Each axis is its own
+operation and the command refuses to combine them, because a capability grant
+and a repoint are different acts with different safety rails, and a command
+that did both would commit them together.
+
+```sh
+carlos route -host hello.example.com -channel edge
+```
+
+`-channel` and `-environment` repoint, and those two may be given together.
+`-grant` and `-revoke` change a capability. `-hibernate` lets the route sleep
+when idle. `-backing` and `-unit` are two answers to one question — who owns
+this route's process — so the command will not let you write both.
+
+Repointing the channel is the half to be careful with. Adoption converges a
+route onto whatever its channel currently points at, so moving the channel
+changes what production serves on the next pass; the command resolves the
+target pointer first and refuses a version change unless you pass
+`-allow-version-change`; `-dry-run` runs the same rails and prints the plan
+without writing. And once a route is `Provisioned`, its channel and
+environment belong to its instance record: `carlos route` refuses both axes
+on such a row and names the record, because the reconciler would revert you
+within seconds anyway.
+
+## carlos release-keygen
+
+Mints the deployment's root signing key, or a scoped key that can sign only
+certain rungs of the ladder.
+
+```sh
+carlos release-keygen -scope canary,edge -name builder
+```
+
+With no flags it prints a fresh root pair: the key goes into your secret
+manager, the public half into host config. With `-scope` it prints a key and
+a grant. The grant carries no secret material but has to travel with the key,
+because a scoped key on its own signs pointers nothing will accept. Only the
+root key can mint a grant, which is what stops a scoped key widening itself.
+
+## carlos economics
+
+Records one month's AWS bill so the console's economics dashboard has a real
+number to divide by.
+
+```sh
+carlos economics bill -month 2026-08 -total 412.55 -line AmazonEC2=55.00
+```
+
+Operator only, and the console answers everyone else with the same bare 404
+it gives an unknown route. A "not found" here almost always means your
+account lacks the fleet-operator bit, not that you typed the path wrong.
+
+## carlos status
+
+`-live` prints what every box in the fleet last reported about itself:
+release, tunnel state, memory, load, free disk. A box that has stopped
+reporting shows as UNREACHABLE with how long it has been dark, and none of
+its stale readings.
+
+```sh
+carlos status -live
+```
+
+`-dry-run <dir>` is the self-hoster's leak check. It builds a throwaway fleet
+stuffed with fixture secrets, runs it through the real publish path into a
+scratch directory, reads every published object back, and fails if any
+fixture escaped. Run it before you ever point this binary at a real bucket.
+Neither mode publishes anything: the public status page is written by each
+box's own agent tick.
+
+## On the host
+
+These are what systemd starts. You will rarely type them, but knowing what
+they do explains most of what happens between a promote and a URL changing.
+
+## carlos edge
+
+The front door. TLS and ACME, and the proxy that maps a hostname to whatever
+is serving it. One per box, started by `carlos-edge.service`.
+
+```sh
+carlos edge -dev -http :8080
+```
+
+`-dev` serves plain HTTP with no TLS or ACME, which is how you run it on a
+laptop. `-fallback` redirects unknown hostnames somewhere (an apex marketing
+site, usually) instead of returning the plain 404.
+
+## carlos agent
+
+Edge, the hibernation activator, release adoption and the status tick, in one
+process per host. This is what a box actually runs; `carlos edge` alone is
+the proxy without any of the rest.
+
+## carlos adopt
+
+One release-adoption pass: read each route's channel, resolve what that
+channel points at, fetch it if the box does not have it, and swap the
+symlink. The hourly timer runs this verb.
+
+```sh
+carlos adopt
+```
+
+Adoption swaps a symlink, and on its own it does not restart anything. A
+long-running unit keeps serving the old build until something cycles it, and
+a hibernating tenant picks the new one up when it next wakes — so the order
+that works is promote, then adopt, then restart. A route that names its
+owning unit is the exception: adoption cycles that one itself, and rolls the
+adoption back if the restart fails, so the version header never claims a
+build the process is not serving.
+
+`-restart-only` and `-config-only` are the narrower passes the agent asks a
+root-side unit to run for it, since the agent can request a pass but cannot
+write to `/etc` or restart units itself.
+
+## carlos ops
+
+Box operations, run by a timer or by an operator standing on the box.
+`litestream` regenerates the replication config from the registry and the
+data directory, printing `changed` or `unchanged` so the caller knows whether
+to restart replication. `restore-verify` restores every configured replica
+from S3 and integrity-checks it. `hibernate-verify` does the same drill for
+the replicas the config does not name — hibernating instances, whose
+replication belongs to the activator — and publishes a per-host verdict to
+the deployment bucket so you can read it off-box.
+
+```sh
+carlos ops restore-verify
+```
+
+## carlos canary
+
+Reserved, and not implemented: typing it exits with `not implemented`.
+Canary releases today are ordinary channels named `canary/<something>`,
+promoted with [carlos promote](/docs/cli#carlos-promote) like any other rung.
diff --git a/docs/site/index.md b/docs/site/index.md
new file mode 100644
index 000000000..16a159564
--- /dev/null
+++ b/docs/site/index.md
@@ -0,0 +1,17 @@
+# CARLOS
+
+CARLOS runs your apps on hardware you control. A deployment is one
+object-store bucket, one or more Linux hosts running the `carlos` binary,
+and a DNS zone; you publish a release with `carlos ship`, and the platform
+places it on instances, routes to it through a shared edge, and keeps TLS,
+config, secrets and replication working underneath it.
+
+These pages are the reference for the platform itself — the commands you
+type, and what happens on a box after you type them. They assume you are
+either running a deployment or shipping onto one. They do not teach Go, and
+they are not a tutorial.
+
+If you are a Carloku customer, the docs you want are at
+[carloku.com/docs](https://carloku.com/docs). Carloku is CARLOS run for you,
+and its pages cover the parts you actually touch without repeating the
+box-side machinery below.
diff --git a/docs/site/nav.json b/docs/site/nav.json
new file mode 100644
index 000000000..616eb25f0
--- /dev/null
+++ b/docs/site/nav.json
@@ -0,0 +1,14 @@
+{
+ "sections": [
+ {
+ "title": "Reference",
+ "pages": [
+ {
+ "slug": "cli",
+ "label": "The CLI",
+ "blurb": "Every carlos command, grouped by whether you are shipping an app, running a deployment, or on a box."
+ }
+ ]
+ }
+ ]
+}
diff --git a/internal/docsite/cli_test.go b/internal/docsite/cli_test.go
index 758682c7b..18bbacaad 100644
--- a/internal/docsite/cli_test.go
+++ b/internal/docsite/cli_test.go
@@ -1,7 +1,6 @@
package docsite
import (
- "os"
"strings"
"testing"
)
@@ -49,9 +48,6 @@ func contains(xs []string, s string) bool {
// someone can forget to update, so gating against it would only prove
// the docs match a second copy of the docs. The switch is the CLI.
func TestCLICommandsAreDocumented(t *testing.T) {
- if _, err := os.Stat(root + "/nav.json"); os.IsNotExist(err) {
- t.Skip("no corpus yet — Task 8 creates docs/site/nav.json")
- }
site, err := Load(root)
if err != nil {
t.Fatalf("load: %v", err)
diff --git a/internal/docsite/fences_test.go b/internal/docsite/fences_test.go
index 95961381a..fceaf8b95 100644
--- a/internal/docsite/fences_test.go
+++ b/internal/docsite/fences_test.go
@@ -1,7 +1,6 @@
package docsite
import (
- "os"
"strings"
"testing"
)
@@ -17,9 +16,6 @@ var knownFenceLangs = map[string]bool{
}
func TestFenceLanguagesAreDeclared(t *testing.T) {
- if _, err := os.Stat(root + "/nav.json"); os.IsNotExist(err) {
- t.Skip("no corpus yet — Task 8 creates docs/site/nav.json")
- }
site, err := Load(root)
if err != nil {
t.Fatalf("load: %v", err)
@@ -59,9 +55,6 @@ func TestTestdataFencesDeclareLanguages(t *testing.T) {
// prose. A renamed verb leaves every fence containing it copy-pasteable
// and wrong, and nothing else in this package would notice.
func TestFencedCarlosCommandsAreReal(t *testing.T) {
- if _, err := os.Stat(root + "/nav.json"); os.IsNotExist(err) {
- t.Skip("no corpus yet — Task 8 creates docs/site/nav.json")
- }
site, err := Load(root)
if err != nil {
t.Fatalf("load: %v", err)
diff --git a/internal/docsite/links_test.go b/internal/docsite/links_test.go
index fa7bf99eb..28020b514 100644
--- a/internal/docsite/links_test.go
+++ b/internal/docsite/links_test.go
@@ -14,9 +14,6 @@ import (
// exists, the link still looks right in review, and the reader lands at
// the top of a long page wondering what they missed.
func TestInternalLinksResolve(t *testing.T) {
- if _, err := os.Stat(root + "/nav.json"); os.IsNotExist(err) {
- t.Skip("no corpus yet — Task 8 creates docs/site/nav.json")
- }
site, err := Load(root)
if err != nil {
t.Fatalf("load: %v", err)
@@ -72,9 +69,6 @@ func TestLinkCheckCatchesADeadFragment(t *testing.T) {
// the second one. The link then goes to the wrong section rather than
// nowhere, which is harder to notice.
func TestAnchorsAreUnique(t *testing.T) {
- if _, err := os.Stat(root + "/nav.json"); os.IsNotExist(err) {
- t.Skip("no corpus yet — Task 8 creates docs/site/nav.json")
- }
site, err := Load(root)
if err != nil {
t.Fatalf("load: %v", err)
diff --git a/internal/docsite/nav_test.go b/internal/docsite/nav_test.go
index 37681de47..8f2d5d9b3 100644
--- a/internal/docsite/nav_test.go
+++ b/internal/docsite/nav_test.go
@@ -1,9 +1,6 @@
package docsite
-import (
- "os"
- "testing"
-)
+import "testing"
// root is the real corpus, relative to this package. Every gate below
// runs against it.
@@ -15,10 +12,6 @@ const root = "../../docs/site"
// unreachable page is the worse failure: it looks written, and nobody
// ever reads it.
func TestNavAndFilesAgreeBothWays(t *testing.T) {
- if _, err := os.Stat(root + "/nav.json"); os.IsNotExist(err) {
- t.Skip("no corpus yet — Task 8 creates docs/site/nav.json")
- }
-
site, err := Load(root)
if err != nil {
t.Fatalf("load: %v", err)