carlos / platform Public

settings-1 task 14: apply the approved copy and pin it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 4c06d1e4595d51dc3a71bf46647c59892d0853bd parent 1bd6419
6 files changed, +106 −60
  • README.md +0 −1
  • approved_copy_digests_test.go +69 −0
  • channels_digests.go +19 −39
  • docs/site/cli.md +0 −2
  • internal/console/apidigests.go +15 −10
  • internal/release/digestwords.go +3 −8

CI passed — run details

diff --git a/README.md b/README.md
index 28ead67b6..66d035e46 100644
--- a/README.md
+++ b/README.md
@@ -278,7 +278,6 @@ 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
diff --git a/approved_copy_digests_test.go b/approved_copy_digests_test.go
new file mode 100644
index 000000000..7a40e6b7c
--- /dev/null
+++ b/approved_copy_digests_test.go
@@ -0,0 +1,69 @@
+package main
+
+import (
+ "encoding/json"
+ "os"
+ "path/filepath"
+ "sort"
+ "testing"
+)
+
+// The pointer digest copy (#253) Paul approved, by ID. The record must name
+// exactly these, and each text must be in its file byte for byte.
+var digestCopyIDs = []string{
+ "digests.rollback-resign", "digests.marker-unreadable", "digests.resign-changed", "digests.resign-unreadable",
+ "digests.resign-no-marker",
+ "digests.resign-intro", "digests.resign-row", "digests.resign-computed", "digests.resign-kept",
+ "digests.resign-missing", "digests.resign-prompt", "digests.resign-done", "digests.resign-aborted",
+ "digests.channels-header", "digests.state-yes", "digests.state-no", "digests.state-straggler",
+ "digests.status-on", "digests.status-off", "digests.status-header", "digests.status-none-left",
+ "digests.turned-on", "digests.turned-off", "digests.all-accounts",
+ "digests.cli-heading-resign", "digests.cli-heading-digests", "digests.roll-note",
+ "digests.console-no-key", "digests.console-pointer-write", "digests.console-marker-write",
+ "digests.activity-resigned", "digests.unknown-account", "digests.status-error",
+}
+
+func TestDigestCopyIsApplied(t *testing.T) {
+ raw, err := os.ReadFile("docs/superpowers/plans/2026-10-07-instance-settings-1-copy.json")
+ if err != nil {
+ t.Fatal(err)
+ }
+ var rec struct {
+ ApprovedBy string `json:"approved_by"`
+ ApprovedOn string `json:"approved_on"`
+ Entries []struct{ ID, File, Text string }
+ }
+ if err := json.Unmarshal(raw, &rec); err != nil {
+ t.Fatal(err)
+ }
+ if rec.ApprovedBy == "" || rec.ApprovedOn == "" {
+ t.Fatal("the record has no approval")
+ }
+ var got []string
+ for _, e := range rec.Entries {
+ got = append(got, e.ID)
+ if e.Text == "" {
+ t.Errorf("%s: the approved text is empty", e.ID)
+ continue
+ }
+ body, err := os.ReadFile(filepath.Join(".", e.File))
+ if err != nil {
+ t.Errorf("%s: %v", e.ID, err)
+ continue
+ }
+ if !copyApplied(e.ID, e.File, e.Text, body) {
+ t.Errorf("%s: %s does not contain the approved text %q", e.ID, e.File, e.Text)
+ }
+ }
+ want := append([]string(nil), digestCopyIDs...)
+ sort.Strings(got)
+ sort.Strings(want)
+ if len(got) != len(want) {
+ t.Fatalf("record IDs %v, want %v", got, want)
+ }
+ for i := range got {
+ if got[i] != want[i] {
+ t.Fatalf("record IDs %v, want %v", got, want)
+ }
+ }
+}
diff --git a/channels_digests.go b/channels_digests.go
index 43fe79dd0..683cf0759 100644
--- a/channels_digests.go
+++ b/channels_digests.go
@@ -14,50 +14,30 @@ import (
"github.com/carlosframework/platform/internal/release"
)
-// The strings below are DRAFTS from copy-review/strings.json until the copy
-// review (plan Task 1) approves them. Each carries a TODO-COPY marker.
+// The strings below are the copy Paul approved for pointer digests (#253),
+// recorded in docs/superpowers/plans/2026-10-07-instance-settings-1-copy.json
+// and pinned by approved_copy_digests_test.go.
const (
- // TODO-COPY digests.resign-intro
- textResignIntro = "Re-sign %s %s with these manifest digests:"
- // TODO-COPY digests.resign-row
- textResignRow = " %s %s %s"
- // TODO-COPY digests.resign-computed
+ textResignIntro = "Re-sign %s %s with these manifest digests:"
+ textResignRow = " %s %s %s"
textResignComputed = "from the bucket now: check it"
- // TODO-COPY digests.resign-kept
- textResignKept = "already signed"
- // TODO-COPY digests.resign-missing
- textResignMissing = "no manifest: left without a digest"
- // TODO-COPY digests.resign-prompt
- textResignPrompt = "Sign this pointer? Type yes to continue: "
- // TODO-COPY digests.resign-done
- textResignDone = "re-signed %s %s with digests"
- // TODO-COPY digests.resign-aborted
- textResignAborted = "nothing signed"
- // TODO-COPY digests.channels-header
+ textResignKept = "already signed"
+ textResignMissing = "no manifest: left without a digest"
+ textResignPrompt = "Sign this pointer? Type yes to continue: "
+ textResignDone = "re-signed %s %s with digests"
+ textResignAborted = "nothing signed"
textChannelsHeader = "CHANNEL\tVERSION\tPROMOTED\tTAG\tDIGESTS"
- // TODO-COPY digests.state-yes
- textStateYes = "yes"
- // TODO-COPY digests.state-no
- textStateNo = "no"
- // TODO-COPY digests.state-straggler
+ textStateYes = "yes"
+ textStateNo = "no"
textStateStraggler = "no (written after digests were turned on: an old writer may still be in use)"
- // TODO-COPY digests.status-on
- textStatusOn = "pointer digests: on for %s since %s"
- // TODO-COPY digests.status-off
- textStatusOff = "pointer digests: off for %s"
- // TODO-COPY digests.status-header
- textStatusHeader = "ACCOUNT\tAPP\tCHANNEL\tVERSION\tDIGESTS"
- // TODO-COPY digests.status-none-left
+ textStatusOn = "pointer digests: on for %s since %s"
+ textStatusOff = "pointer digests: off for %s"
+ textStatusHeader = "ACCOUNT\tAPP\tCHANNEL\tVERSION\tDIGESTS"
textStatusNoneLeft = "every channel's pointer carries digests: CARLOS_REQUIRE_POINTER_DIGEST can be turned on"
- // TODO-COPY digests.turned-on
- textTurnedOn = "pointer digests on for %s. Upgraded writers sign digests from now on"
- // TODO-COPY digests.turned-off
- textTurnedOff = "pointer digests off for %s. Boxes that have already seen digests keep refusing pointers without them"
- // TODO-COPY digests.all-accounts
- textAllAccounts = "every account"
- // TODO-COPY digests.status-error
- textStatusError = "pointer digests: unknown for %s, the report could not be read: %s"
- // TODO-COPY digests.unknown-account (NEW: not in strings.json)
+ textTurnedOn = "pointer digests on for %s. Upgraded writers sign digests from now on"
+ textTurnedOff = "pointer digests off for %s. Boxes that have already seen digests keep refusing pointers without them"
+ textAllAccounts = "every account"
+ textStatusError = "pointer digests: unknown for %s, the report could not be read: %s"
textUnknownAccount = "no account %s on this console, so nothing was changed for it"
)
diff --git a/docs/site/cli.md b/docs/site/cli.md
index 4aaa6a086..c35a9965d 100644
--- a/docs/site/cli.md
+++ b/docs/site/cli.md
@@ -511,7 +511,6 @@ 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.
@@ -520,7 +519,6 @@ Re-signs a channel's current pointer with the sha256 of each build's manifest, s
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.
diff --git a/internal/console/apidigests.go b/internal/console/apidigests.go
index 9ee3a1b26..05fa60c93 100644
--- a/internal/console/apidigests.go
+++ b/internal/console/apidigests.go
@@ -12,6 +12,16 @@ import (
"github.com/carlosframework/platform/internal/release"
)
+// Copy Paul approved for pointer digests (#253), recorded in
+// docs/superpowers/plans/2026-10-07-instance-settings-1-copy.json and pinned
+// by approved_copy_digests_test.go.
+const (
+ textNoReleaseKey = "this console has no release key"
+ textPointerWrite = "could not write the pointer"
+ textMarkerWrite = "could not write the marker for %s"
+ textActivityResigned = "re-signed %s at %s with manifest digests (cli)"
+)
+
// handleAPIChannelResign answers `carlos channels resign` (#253, spec §4.6).
// A body naming only the channel gets the plan; a body that also carries
// the digests the operator confirmed signs them, if a plan made now is
@@ -52,8 +62,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")
+ jsonError(w, http.StatusServiceUnavailable, textNoReleaseKey)
return
}
if req.Digest != "" && s.refuseStableOverAPI(w, p, acct, app, req.Channel) {
@@ -94,14 +103,12 @@ 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")
+ jsonError(w, http.StatusBadGateway, textPointerWrite)
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)",
+ Detail: fmt.Sprintf(textActivityResigned, req.Channel, plan.Version),
})
s.bumpSerial(ctx)
writeJSON(w, http.StatusOK, map[string]string{"result": "resigned"})
@@ -167,8 +174,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")
+ jsonError(w, http.StatusServiceUnavailable, textNoReleaseKey)
return
}
accts, err := s.store.Accounts()
@@ -189,8 +195,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)
+ jsonError(w, http.StatusBadGateway, fmt.Sprintf(textMarkerWrite, a.Sqid))
return
}
s.auditSystem(addr, action, "account "+a.Sqid+" (cli)")
diff --git a/internal/release/digestwords.go b/internal/release/digestwords.go
index a755d2d7b..5a8079514 100644
--- a/internal/release/digestwords.go
+++ b/internal/release/digestwords.go
@@ -1,23 +1,18 @@
package release
// The pointer digest strings a member or operator reads from this package
-// (#253). These are DRAFT texts from copy-review/strings.json until Task 1's
-// review approves them; each is marked TODO-COPY and is replaced by the
-// approved text, pinned by approved_copy_digests_test.go.
+// (#253). These are the texts Paul approved, recorded in
+// docs/superpowers/plans/2026-10-07-instance-settings-1-copy.json and pinned
+// by approved_copy_digests_test.go.
const (
- // TODO-COPY digests.rollback-resign
// digests.rollback-resign: app, channel, version, app, channel.
TextRollbackResign = "rollback: %s %s would go back to %s, which has no signed digest. Run carlos channels resign --app %s %s first, or promote a newer build"
- // TODO-COPY digests.marker-unreadable
// digests.marker-unreadable: verb, account, error.
TextMarkerUnreadable = "%s: the pointer digest marker for account %s could not be verified, so nothing was signed: %v"
- // TODO-COPY digests.resign-changed
// digests.resign-changed: channel.
TextResignChanged = "%s moved since these digests were shown. Nothing was signed. Run the command again"
- // TODO-COPY digests.resign-unreadable
// digests.resign-unreadable: version.
TextResignUnreadable = "%s has no readable manifest, so its digest cannot be confirmed. Promote a newer build instead"
- // TODO-COPY digests.resign-no-marker
// digests.resign-no-marker: account, account.
TextResignNoMarker = "resign: pointer digests are not turned on for account %s. Upgrade every writer, then run carlos channels digests on --account-id %s first"
)