carlos / platform Public

settings-5 task 6: docs for the update settings commands; roll notes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 561c7dfb6f93d4bfc9f39c64f2a10f100fc42be9 parent f0f070d
3 files changed, +66 −0
  • README.md +2 −0
  • docs/site/cli.md +24 −0
  • internal/updatewords/words_docs_test.go +40 −0
diff --git a/README.md b/README.md
index 62462f178..7130feee7 100644
--- a/README.md
+++ b/README.md
@@ -280,6 +280,8 @@ the release you are about to roll.
**Pointer digests (#253): writers before readers.** Once a box has verified a channel pointer that carries manifest digests, it refuses pointers for that channel without them. So digests are turned on in order. First upgrade every writer: every console, and every CLI or deploy script that promotes, rolls back or fast-tracks. Then run `carlos channels digests on`. Then watch `carlos channels digests`: a channel marked as written by an old writer means one is still in use. Rolling a writer back below this release stops it writing pointers boxes accept; `carlos channels digests off` suspends digests while it is fixed, but does not clear the boxes that have already seen them. When `carlos channels digests` lists nothing, set `CARLOS_REQUIRE_POINTER_DIGEST=on` in every box's host.env, so a new box refuses old pointers too.
+**Instance update settings: the roll order.** Roll every box to the release that applies update settings before anyone turns on manual updates, a major hold or a pin: the console refuses those until every box reports it can protect the builds they name from pruning, and lists the boxes it is waiting on. Before rolling any box back below that release, turn retention off for the deployment (`carlos releases retention --off` for each app that has one), because an older housekeeper prunes builds a pin or a held instance still needs. After such a rollback the console shows *settings not enforced* for instances on the older boxes: they follow their channel until the box is rolled forward again.
+
**v0.25.0: instances move to the nested layout on their own.** This is
the first release on which an isolated (nested) instance wakes, and nested
is the default. On each box, migration starts unattended on the first
diff --git a/docs/site/cli.md b/docs/site/cli.md
index 1ac818f6d..d4bd04051 100644
--- a/docs/site/cli.md
+++ b/docs/site/cli.md
@@ -199,6 +199,14 @@ box, after the first one has stayed up for ten seconds; it starts at 1. Any
member of the app can change both, here or on the app's settings page in the
console.
+`carlos apps set` also controls the update settings an instance may change:
+
+```sh
+carlos apps set shop --instance-control updates,pin --default-updates manual
+```
+
+--instance-control names the settings an instance may change itself once instances can act for themselves; --default-updates, --default-channel and --default-major apply wherever an instance has not chosen.
+
### carlos ship
Publishes a release. The artifact is hashed, stored under its content
@@ -859,6 +867,22 @@ 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 instances set
+
+Sets how one instance takes new builds: auto or manual updates, the channel, a major version to stay on (stable only), or an exact build to pin. An empty value clears a setting, so the app's default applies again.
+
+```sh
+carlos instances set a.shop.example.com --updates manual
+```
+
+### carlos instances update
+
+Asks the instance's box to install the newest build it considers ready, even with manual updates. The box chooses the build, and it runs on the next start. --fast-track skips the safety delay; only account owners can use it.
+
+```sh
+carlos instances update a.shop.example.com
+```
+
### carlos sidecars
A sidecar is a member that nothing can dial: a process the platform runs for
diff --git a/internal/updatewords/words_docs_test.go b/internal/updatewords/words_docs_test.go
new file mode 100644
index 000000000..bf5752966
--- /dev/null
+++ b/internal/updatewords/words_docs_test.go
@@ -0,0 +1,40 @@
+package updatewords
+
+import (
+ "os"
+ "strings"
+ "testing"
+)
+
+// TestDocsContainDrafts verifies that the CLI docs and README contain the
+// drafts from words_surfaces.go byte for byte, so the docs and the drafts
+// cannot drift before or after Paul's review.
+func TestDocsContainDrafts(t *testing.T) {
+ tests := []struct {
+ id string
+ path string
+ }{
+ {"settings.cli.docs-instances-set", "../../docs/site/cli.md"},
+ {"settings.cli.docs-instances-update", "../../docs/site/cli.md"},
+ {"settings.cli.docs-apps-set", "../../docs/site/cli.md"},
+ {"settings.cli.roll-note", "../../README.md"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.id, func(t *testing.T) {
+ txt := Text(tt.id)
+ if txt == "" {
+ t.Fatalf("no draft found for %s", tt.id)
+ }
+
+ content, err := os.ReadFile(tt.path)
+ if err != nil {
+ t.Fatalf("reading %s: %v", tt.path, err)
+ }
+
+ if !strings.Contains(string(content), txt) {
+ t.Errorf("%s does not contain the draft text for %s: %q", tt.path, tt.id, txt)
+ }
+ })
+ }
+}