// Command tmplatformctl is the admin surface (P-8): credit an account, read a balance, look at // sign-ins, end sessions. // // A CLI rather than a protected HTTP route, deliberately. An admin endpoint needs a second // authorisation model — roles, an escalation path, a way to lose the admin cookie — for four // operations. The trust boundary for these is already "can open a shell on the box and read the // DSN", and that boundary is enforced by the machine rather than by code we would have to write // and get right. If a browser-facing admin panel is ever wanted, it wraps these same store calls. package main import ( "context" "crypto/rand" "encoding/hex" "errors" "flag" "fmt" "io" "os" "os/signal" "syscall" "text/tabwriter" "time" "textmachine/platform/internal/config" "textmachine/platform/internal/money" "textmachine/platform/internal/pgstore" ) func main() { if err := run(os.Args[1:], os.Stdout); err != nil { if errors.Is(err, errUsage) { _, _ = fmt.Fprintln(os.Stderr, usage) os.Exit(2) } _, _ = fmt.Fprintln(os.Stderr, "tmplatformctl:", err) os.Exit(1) } } // errUsage asks main to print the usage text; every other error is a message on its own. var errUsage = errors.New("usage") const usage = `usage: tmplatformctl [flags] grant --user --usd [--note ] [--key ] adjust --user --usd --note [--key ] balance --user logins --user [--limit ] revoke --user The DSN comes from TM_PLATFORM_DSN or TM_PLATFORM_DSN_FILE.` func run(args []string, out io.Writer) error { if len(args) == 0 { return errUsage } dsn, err := config.Secret("TM_PLATFORM_DSN") if err != nil { return err } if dsn == "" { return errors.New("TM_PLATFORM_DSN (or TM_PLATFORM_DSN_FILE) is not set") } ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() store, err := pgstore.Open(ctx, dsn) if err != nil { return err } defer store.Close() cmd, rest := args[0], args[1:] switch cmd { case "grant": return grant(ctx, store, rest, out) case "adjust": return adjust(ctx, store, rest, out) case "balance": return balance(ctx, store, rest, out) case "logins": return logins(ctx, store, rest, out) case "revoke": return revoke(ctx, store, rest, out) default: return fmt.Errorf("unknown command %q: %w", cmd, errUsage) } } // grant writes one ledger row: that is the whole of the free tier. func grant(ctx context.Context, store *pgstore.Store, args []string, out io.Writer) error { fs := flag.NewFlagSet("grant", flag.ContinueOnError) user := fs.String("user", "", "account id") amount := fs.String("usd", "", "amount in dollars, e.g. 5 or 2.50") note := fs.String("note", "", "why") // Idempotency is OPT-IN. A default key derived from the account and the day looked safe and was // not: two legitimate grants on one day collapse into the first, and the second reports success // while crediting nothing. Each invocation is its own intent unless the operator says otherwise. key := fs.String("key", "", "idempotency key: repeating the command with the same one is a no-op") if err := fs.Parse(args); err != nil { return err } if *user == "" || *amount == "" { return errors.New("grant needs --user and --usd") } micro, err := money.ParseUSD(*amount) if err != nil { return err } return write(ctx, store, out, *user, *key, func(id string, now time.Time) (bool, error) { return store.Grant(ctx, *user, micro, "admin", id, *note, now) }, "granted "+micro.USD()+" to "+*user) } // adjust corrects a balance with a second row: ledger rows are never edited. func adjust(ctx context.Context, store *pgstore.Store, args []string, out io.Writer) error { fs := flag.NewFlagSet("adjust", flag.ContinueOnError) user := fs.String("user", "", "account id") amount := fs.String("usd", "", "signed amount in dollars, e.g. -2.50") note := fs.String("note", "", "why (required: an unexplained correction is unauditable)") key := fs.String("key", "", "idempotency key") if err := fs.Parse(args); err != nil { return err } if *user == "" || *amount == "" || *note == "" { return errors.New("adjust needs --user, --usd and --note") } micro, err := money.ParseUSD(*amount) if err != nil { return err } return write(ctx, store, out, *user, *key, func(id string, now time.Time) (bool, error) { return store.Adjust(ctx, *user, micro, "admin", id, *note, now) }, "adjusted "+*user+" by "+micro.USD()) } // write runs one ledger operation and reports what actually happened. "Applied" and "the key was // already spent" are different outcomes and the operator is told which one they got. func write(ctx context.Context, store *pgstore.Store, out io.Writer, user, key string, op func(id string, now time.Time) (bool, error), what string) error { now := time.Now().UTC() id := key if id == "" { id = newKey() } applied, err := op(id, now) if err != nil { return err } // Past this point the write is committed and NOTHING may report failure. An operator who reads // an error retries, and a retry without --key mints a fresh idempotency key, so the second run // credits again — a failed BALANCE READ would have bought a double credit. The balance is a // courtesy; its failure is a note on the same line. Found by review. shown := "balance unavailable: " + user if after, err := store.Balance(ctx, user); err == nil { shown = "balance is " + after.USD() } else { _, _ = fmt.Fprintf(out, "warning: could not read the balance back: %v\n", err) } if !applied { _, _ = fmt.Fprintf(out, "no-op: key %s was already used on %s; %s\n", id, user, shown) return nil } _, _ = fmt.Fprintf(out, "%s (key %s); %s\n", what, id, shown) return nil } // newKey mints a key for a one-off command, so that two deliberate grants on the same day are two // grants. func newKey() string { var b [8]byte rand.Read(b[:]) // never fails return "cli-" + hex.EncodeToString(b[:]) } func balance(ctx context.Context, store *pgstore.Store, args []string, out io.Writer) error { fs := flag.NewFlagSet("balance", flag.ContinueOnError) user := fs.String("user", "", "account id") if err := fs.Parse(args); err != nil { return err } if *user == "" { return errors.New("balance needs --user") } // One snapshot: reading the cache and the ledger in two queries reports drift that a concurrent // grant caused between them. a, err := store.ReadAccount(ctx, *user) if err != nil { return err } _, _ = fmt.Fprintf(out, "balance %s\n", a.Balance.USD()) if a.Reserved != 0 { _, _ = fmt.Fprintf(out, "reserved %s (open holds, already deducted)\n", a.Reserved.USD()) } if a.Balance != a.LedgerSum { _, _ = fmt.Fprintf(out, "⚠ ledger sums to %s: the cached balance has drifted\n", a.LedgerSum.USD()) } open, err := store.OpenReservations(ctx, *user) if err != nil { return err } for _, r := range open { _, _ = fmt.Fprintf(out, " hold %s on book %s since %s (run %s)\n", r.Amount.USD(), r.BookID, r.OpenedAt.UTC().Format(time.RFC3339), r.EngineRunID) } return nil } func logins(ctx context.Context, store *pgstore.Store, args []string, out io.Writer) error { fs := flag.NewFlagSet("logins", flag.ContinueOnError) user := fs.String("user", "", "account id") limit := fs.Int("limit", 20, "how many") if err := fs.Parse(args); err != nil { return err } if *user == "" { return errors.New("logins needs --user") } entries, err := store.RecentLogins(ctx, *user, *limit) if err != nil { return err } w := tabwriter.NewWriter(out, 0, 0, 2, ' ', 0) _, _ = fmt.Fprintln(w, "WHEN\tPROVIDER\tOUTCOME\tCLIENT\tFROM\tREASON") for _, e := range entries { _, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", e.At.UTC().Format(time.RFC3339), e.Provider, e.Outcome, e.Client, e.IPPrefix, e.Reason) } return w.Flush() } func revoke(ctx context.Context, store *pgstore.Store, args []string, out io.Writer) error { fs := flag.NewFlagSet("revoke", flag.ContinueOnError) user := fs.String("user", "", "account id") if err := fs.Parse(args); err != nil { return err } if *user == "" { return errors.New("revoke needs --user") } n, err := store.RevokeUserSessions(ctx, *user, time.Now().UTC()) if err != nil { return err } _, _ = fmt.Fprintf(out, "revoked %d sessions of %s\n", n, *user) return nil }