P7: approved copy for the door-key CLI and doors; the edge-local door in the CLI reference (#175)
Task 8 of the P7 plan. Every string as approved in copy review on 2026-09-29, verbatim: the door-key usage prose, the one-time warning, the flag help, the console's door-key errors, the door's refusal, and a new passage on the edge-local door in docs/site/cli.md's carlos instances section. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Human
Prompt
Approved (P7 copy review, 19 strings)
From this commit's own message. The prompt ledger has no entries for it — which is what a squashed, rebased or amended commit looks like, and what every commit looks like in a repo without a ledger.
Summary
P7 Task 8: approved copy and the docs passage.
3 files changed,
+20
−10
docs/site/cli.md+14 −0doorkeys_cli.go+5 −9internal/console/apidoorkeys.go+1 −1
CI passed — run details
diff --git a/docs/site/cli.md b/docs/site/cli.md| index cedeb32f0..c173c7929 100644 |
| --- a/docs/site/cli.md |
| +++ b/docs/site/cli.md |
| @@ -641,6 +641,20 @@ would reach that quota between them. |
| `--private-storage=false` turns the policy off. Instances created before it |
| keep the bindings and buckets they already have. |
| +**The edge-local door.** Some apps run their own processes on a box beside their instances: a router that serves many customers under one domain, a relay, a sync loop. Dialling each instance's socket directly stops working when an instance sleeps, moves to another box, or changes where its socket lives. The edge-local door is one socket those processes dial instead, wherever the instance is. |
| + |
| +```sh |
| +carlos instances door-key create --app hello --label router |
| +curl --unix-socket /run/carlos/edge-local.sock \ |
| + -H 'X-Carlos-Door-Key: cdk1.…' https://acme.hello.bab.oncarlos.com/ |
| +``` |
| + |
| +The door handles a request the way the edge handles one from the internet. It wakes a sleeping instance, forwards to the box that holds a moved one, and applies the password gate. It only reaches the key's own app. Any other host answers as unknown. |
| + |
| +The door believes no visitor address the caller sends. To your instance, a door request looks like one the box itself made to the public URL: it carries the box's public address, over `https`. If your app needs the visitor's address, have your process send it in a header your app signs and checks itself. |
| + |
| +The door is off until a box sets `CARLOS_EDGE_DOOR=/run/carlos/edge-local.sock` and its agent restarts, and the calling process's user must be enrolled with `carlos bootstrap --socket-consumer <user>`. A revoked key stops working within 30 seconds, or within 10 minutes on a box that cannot reach the bucket. A key's record the console has not re-signed for 72 hours stops working too, so a console that stays down that long closes the door. |
| + |
| 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 |
diff --git a/doorkeys_cli.go b/doorkeys_cli.go| index 40aafd064..2311dba97 100644 |
| --- a/doorkeys_cli.go |
| +++ b/doorkeys_cli.go |
| @@ -33,17 +33,13 @@ const doorKeyUsage = `usage: carlos instances door-key create --app <a> [--label |
| --console <origin> console to act through (default: the only |
| logged-in one) |
| -Signed-in only (carlos auth login): a door key is a console-authorized |
| -write — the console's own database is the authority, and there is no |
| -direct-bucket equivalent. |
| +Signed in only (carlos auth login). A door key is written through a console, which keeps it in its own database. There is no direct-bucket form. |
| -create mints a fresh key for the app and prints it once. store it now, |
| -it cannot be shown again — a new "carlos instances door-key list" only |
| -ever shows the id and label, never the key itself. |
| +create mints a new key for the app and prints it once. Store it now: it cannot be shown again, and list only ever shows its id and label. |
| list shows the app's live keys: id, label and when each was created. |
| -revoke takes the id from list and stops that key working. |
| +revoke takes an id from list and stops that key working. |
| ` |
| // ---- wire types ------------------------------------------------------------- |
| @@ -148,7 +144,7 @@ func runInstancesDoorKey(args []string) { |
| func runInstancesDoorKeyCreate(args []string) { |
| fs := flag.NewFlagSet("instances door-key create", flag.ExitOnError) |
| app := appFlag(fs, "app to mint a door key for") |
| - label := fs.String("label", "", "short note stored alongside the key, shown by list (default: none)") |
| + label := fs.String("label", "", "short note stored with the key and shown by list (default: none)") |
| consoleFlag := consoleOption(fs, "console to create through (default: the only logged-in one)") |
| acctRaw := fs.String("account", "", "account (sqid or name) the app belongs to (default: your only account, or the configured/chosen one)") |
| acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes --account") |
| @@ -172,7 +168,7 @@ func runInstancesDoorKeyCreate(args []string) { |
| // create follows for a standalone credential's password. |
| func printDoorKeyCreate(w io.Writer, res apiDoorKeyMint) { |
| fmt.Fprintln(w, res.Key) |
| - warnf(w, "shown once. store it now, it cannot be shown again.") |
| + warnf(w, "shown once. store it now: it cannot be shown again") |
| } |
| func runInstancesDoorKeyList(args []string) { |
diff --git a/internal/console/apidoorkeys.go b/internal/console/apidoorkeys.go| index 919ab5366..12f9a706a 100644 |
| --- a/internal/console/apidoorkeys.go |
| +++ b/internal/console/apidoorkeys.go |
| @@ -38,7 +38,7 @@ const ( |
| doorKeyPublishBudget = 20 * time.Second |
| ) |
| -// Draft copy, pending review (plan Task 8). |
| +// Copy approved in review (plan Task 8), 2026-09-29. |
| const ( |
| doorKeyReadOnlyMsg = "no signing key on this host. the console is running read-only" |
| doorKeyNoBucketMsg = "no deployment bucket on this host. the console is running without one" |