package main import ( "bufio" "context" "flag" "fmt" "io" "log" "os" "strings" "time" "github.com/carlosframework/platform/internal/release" ) // 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 ( textResignIntro = "Re-sign %s %s with these manifest digests:" textResignRow = " %s %s %s" textResignComputed = "from the bucket now: check it" 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" textStateYes = "yes" textStateNo = "no" textStateStraggler = "no (written after digests were turned on: an old writer may still be in use)" 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" 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" ) // resignInput is where resign reads its confirmation; tests swap it. var resignInput io.Reader = os.Stdin func digestCell(head, straggler bool) string { switch { case head: return textStateYes case straggler: return textStateStraggler } return textStateNo } // runChannelsResign is `carlos channels resign [--app ] ` (also // ` `, as the spec writes it). It shows every digest it would // sign and signs only on "yes" (or --yes). func runChannelsResign(args []string) { fs := flag.NewFlagSet("channels resign", flag.ExitOnError) app := appFlag(fs, "") acctRaw, acctIDRaw, acctOf := accountFlags(fs) yes := fs.Bool("yes", false, "sign without asking") consoleFlag := consoleOption(fs, "console origin to resign through (API mode only; default: the only logged-in one)") fs.Usage = flagUsage(fs, "") fs.Parse(args) usage := "usage: carlos channels resign [--app ] [--account ] [--yes] " pos := fs.Args() if len(pos) == 2 && *app == "" { *app, pos = pos[0], pos[1:] } if len(pos) != 1 { log.Fatal(usage) } channel := pos[0] confirm := func(version, digest string, history, hd []string, computed []bool) bool { fmt.Fprintf(stdout, textResignIntro+"\n", *app, channel) row := func(v, d string, c bool) { src := textResignKept switch { case d == "": src = textResignMissing case c: src = textResignComputed } fmt.Fprintf(stdout, textResignRow+"\n", v, d, src) } row(version, digest, computed[0]) for i, v := range history { row(v, hd[i], computed[i+1]) } if *yes { return true } fmt.Fprint(stdout, textResignPrompt) line, _ := bufio.NewReader(resignInput).ReadString('\n') if strings.TrimSpace(line) != "yes" { fmt.Fprintln(stdout, textResignAborted) return false } return true } sess, err := resolveMode(*consoleFlag) if err != nil { log.Fatal(err) } ctx := context.Background() if sess != nil { printUsing(stdout, sess) acct := resolvedAccount(sess, acctRaw, acctIDRaw) *app = resolvedApp(sess, acct, *app, usage) plan, err := sess.resignPlan(ctx, acct, *app, channel) if err != nil { log.Fatal(err) } if !confirm(plan.Version, plan.Digest, plan.History, plan.HistoryDigests, plan.Computed) { return } if err := sess.resignSign(ctx, acct, *app, channel, plan.Digest, plan.HistoryDigests); err != nil { log.Fatal(err) } fmt.Fprintf(stdout, textResignDone+"\n", *app, channel) return } if *app == "" { log.Fatal(usage) } sg := releaseSigner() s, acct := openStore(), acctOf() plan, err := sg.PlanResign(ctx, s, acct, *app, channel) if err != nil { log.Fatal(err) } if !confirm(plan.Version, plan.Digest, plan.History, plan.HistoryDigests, plan.Computed) { return } if err := sg.Resign(ctx, s, plan, promoteActor()); err != nil { log.Fatal(err) } fmt.Fprintf(stdout, textResignDone+"\n", *app, channel) } // runChannelsDigests is `carlos channels digests [on|off] [--account-id ]...`. // Bare, it reports; on and off write or remove the emission marker. Through a // console these are platform-operator doors; direct-store they need the root // key in CARLOS_RELEASE_KEY and name each account. func runChannelsDigests(args []string) { fs := flag.NewFlagSet("channels digests", flag.ExitOnError) var ids multiFlag fs.Var(&ids, "account-id", "an account sqid; repeat for several (direct-store: required)") consoleFlag := consoleOption(fs, "console origin (API mode only; default: the only logged-in one)") fs.Usage = flagUsage(fs, "") usage := "usage: carlos channels digests [on|off] [--account-id ]..." // The verb comes first (`digests on --account-id bab`, the form the // refusal texts print), and the flag package stops at the first // non-flag, so it is peeled off before the flags are parsed. verb := "" if len(args) > 0 && (args[0] == "on" || args[0] == "off") { verb, args = args[0], args[1:] } fs.Parse(args) if fs.NArg() != 0 { log.Fatal(usage) } ctx := context.Background() sess, err := resolveMode(*consoleFlag) if err != nil { log.Fatal(err) } var rep apiPointerDigests if sess != nil { printUsing(stdout, sess) rep, err = sess.pointerDigests(ctx, verb, ids) } else { if len(ids) == 0 { log.Fatal(usage) } rep, err = localPointerDigests(ctx, openStore(), verb, ids) } if err != nil { log.Fatal(err) } if sess != nil && verb != "" { if err := checkNamedAccounts(rep, ids); err != nil { log.Fatal(err) } } names := strings.Join(ids, ", ") if names == "" { names = textAllAccounts } switch verb { case "on": fmt.Fprintf(stdout, textTurnedOn+"\n", names) case "off": fmt.Fprintf(stdout, textTurnedOff+"\n", names) } // An account whose report could not be read is "unknown", never "off", // and an incomplete report never says the switch can be turned on. failed := false for _, a := range rep.Accounts { switch { case a.Error != "": failed = true fmt.Fprintf(stdout, textStatusError+"\n", a.Account, a.Error) case a.On: fmt.Fprintf(stdout, textStatusOn+"\n", a.Account, a.Since) default: fmt.Fprintf(stdout, textStatusOff+"\n", a.Account) } } if len(rep.Channels) == 0 { if failed { return } fmt.Fprintln(stdout, textStatusNoneLeft) return } w := newTable(stdout) fmt.Fprintln(w, textStatusHeader) for _, c := range rep.Channels { fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", c.Account, c.App, c.Channel, c.Version, digestCell(false, c.Straggler)) } w.Flush() } // checkNamedAccounts is the guard for an operator who named accounts: the // console's system door skips an account id it does not know and still // answers 200, which must not read as success. func checkNamedAccounts(rep apiPointerDigests, ids []string) error { for _, id := range ids { known := false for _, a := range rep.Accounts { known = known || a.Account == id } if !known { return fmt.Errorf(textUnknownAccount, id) } } return nil } // localPointerDigests is the direct-store form: the root key writes or // removes each named account's marker, and the bucket is listed for the // report. func localPointerDigests(ctx context.Context, s release.Store, verb string, ids []string) (apiPointerDigests, error) { pub := publicKey() var rep apiPointerDigests for _, acct := range ids { var err error switch verb { case "on": err = release.WriteDigestMarker(ctx, s, releaseSigner(), acct, promoteActor()) case "off": err = release.RemoveDigestMarker(ctx, s, acct) } if err != nil { return rep, err } row := apiDigestAccount{Account: acct} // A marker or listing that cannot be read marks this account's row, // as the console's report does; it does not hide the other accounts. marker, err := release.ReadDigestMarker(ctx, s, pub, acct) if err != nil { row.Error = err.Error() rep.Accounts = append(rep.Accounts, row) continue } if marker != nil { row.On, row.Since = true, marker.At.UTC().Format(time.RFC3339) } dirs, err := s.ListDirs(ctx, "apps/"+acct+"/") if err != nil { row.Error = err.Error() rep.Accounts = append(rep.Accounts, row) continue } for _, d := range dirs { app := strings.TrimSuffix(strings.TrimPrefix(d, "apps/"+acct+"/"), "/") if !release.ValidApp(app) { continue } states, _, err := release.DigestReport(ctx, s, pub, acct, app) if err != nil { row.Error = err.Error() break } for _, st := range states { switch { case st.Err != "": // A channel whose pointer could not be read leaves the // account unknown, as the console's report does. if row.Error == "" { row.Error = st.Err } case !st.HeadDigest: rep.Channels = append(rep.Channels, apiDigestChannel{Account: acct, App: app, Channel: st.Channel, Version: st.Version, Straggler: st.Straggler}) } } } rep.Accounts = append(rep.Accounts, row) } return rep, nil }