settings-1 task 13: docs for resign and digests; the writers-before-readers roll note
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 files changed,
+26
−0
README.md+3 −0docs/site/cli.md+18 −0internal/console/apidigests.go+5 −0
diff --git a/README.md b/README.md| index eee29768b..28ead67b6 100644 |
| --- a/README.md |
| +++ b/README.md |
| @@ -278,6 +278,9 @@ A release-specific note goes here when a release changes something an |
| operator needs to check or act on before rolling it. Read the note for |
| the release you are about to roll. |
| +<!-- TODO-COPY digests.roll-note --> |
| +**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. |
| + |
| **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 8e1963123..4aaa6a086 100644 |
| --- a/docs/site/cli.md |
| +++ b/docs/site/cli.md |
| @@ -511,6 +511,24 @@ release earned one at stable. |
| carlos channels --app hello |
| ``` |
| +<!-- TODO-COPY digests.cli-heading-resign --> |
| +### carlos channels resign |
| + |
| +Re-signs a channel's current pointer with the sha256 of each build's manifest, so boxes that insist on digests accept it. It shows every digest first and signs only when you type yes. Use it on a channel nobody has promoted to since digests were turned on. It refuses until `carlos channels digests on` has been run for the account. |
| + |
| +```sh |
| +carlos channels resign --app hello stable |
| +``` |
| + |
| +<!-- TODO-COPY digests.cli-heading-digests --> |
| +### carlos channels digests |
| + |
| +Shows whether channel pointers carry manifest digests, and lists the channels that still do not. on and off are for platform operators: on tells upgraded writers to sign digests, and off stops them while a writer is being fixed. Turn it on only after every console, CLI and deploy script that promotes has been upgraded. |
| + |
| +```sh |
| +carlos channels digests |
| +``` |
| + |
| ### carlos pipeline |
| Shows an app's release channels and the rules for moving a version through |
diff --git a/internal/console/apidigests.go b/internal/console/apidigests.go| index dd356808f..c1c5ec307 100644 |
| --- a/internal/console/apidigests.go |
| +++ b/internal/console/apidigests.go |
| @@ -44,6 +44,7 @@ func (s *Server) handleAPIChannelResign(w http.ResponseWriter, r *http.Request) |
| return |
| } |
| if len(s.cfg.Key) == 0 { |
| + // TODO-COPY digests.console-no-key |
| jsonError(w, http.StatusServiceUnavailable, "this console has no release key") |
| return |
| } |
| @@ -72,9 +73,11 @@ func (s *Server) handleAPIChannelResign(w http.ResponseWriter, r *http.Request) |
| return |
| } |
| log.Printf("console: api resign %s/%s %s: %v", acct.Sqid, app, req.Channel, err) |
| + // TODO-COPY digests.console-pointer-write |
| jsonError(w, http.StatusBadGateway, "could not write the pointer") |
| return |
| } |
| + // TODO-COPY digests.activity-resigned |
| s.auditActivity(acct, app, addr, Activity{ |
| Action: "resign", TargetType: "channel", Target: req.Channel, |
| Detail: "re-signed " + req.Channel + " at " + plan.Version + " with manifest digests (cli)", |
| @@ -116,6 +119,7 @@ func (s *Server) handleAPISystemPointerDigests(w http.ResponseWriter, r *http.Re |
| return |
| } |
| if len(s.cfg.Key) == 0 { |
| + // TODO-COPY digests.console-no-key |
| jsonError(w, http.StatusServiceUnavailable, "this console has no release key") |
| return |
| } |
| @@ -137,6 +141,7 @@ func (s *Server) handleAPISystemPointerDigests(w http.ResponseWriter, r *http.Re |
| } |
| if err != nil { |
| log.Printf("console: pointer digests %s: %v", a.Sqid, err) |
| + // TODO-COPY digests.console-marker-write |
| jsonError(w, http.StatusBadGateway, "could not write the marker for "+a.Sqid) |
| return |
| } |