carlos / platform Public

carlos ratelimit: show prints the plan ceiling; help lists the verb (#358)

show adds one "plan ceiling" line after the backstop line when the plan
sets any of share, rate or burst. The top-level usage gains the ratelimit
line after gate. The docs test now expects the cli.md usage fence to
declare its language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 5e12a3ee94120e3ce30a3152c65d83ff6ae1f639 parent 21dc5a1
3 files changed, +62 −1
  • main.go +1 −0
  • ratelimit_cli.go +31 −0
  • ratelimit_cli_test.go +30 −1
diff --git a/main.go b/main.go
index 1c70aa96e..5c7d8f197 100644
--- a/main.go
+++ b/main.go
@@ -275,6 +275,7 @@ Your apps — ship and run software on a CARLOS deployment
features an app's own feature flags, stored and served by the
platform, never interpreted: set | list
gate a shared password in front of an app's hostnames
+ ratelimit per-client limits on an app's instances
logs an app's merged log timeline: its own output plus platform
events (wakes, restarts, failures); -f follows live
errors error groups for an app: log lines at the set level plus
diff --git a/ratelimit_cli.go b/ratelimit_cli.go
index 3f14c6422..23fa73daa 100644
--- a/ratelimit_cli.go
+++ b/ratelimit_cli.go
@@ -55,6 +55,10 @@ The box picks a change up within seconds of the next converge tick.
ratelimitShowDefault = "(default)" // cli.ratelimit.show.default
ratelimitShowCapped = "(the plan caps this; you set {set})" // cli.ratelimit.show.capped
ratelimitShowCounts = "last 24 hours {shed} turned away while busy, {ceiling} over the backstop" // cli.ratelimit.show.counts
+ ratelimitShowCeiling = "plan ceiling {parts}" // cli.ratelimit.show.ceiling
+ ratelimitCeilShare = "share {n}" // cli.ratelimit.show.ceiling.share
+ ratelimitCeilRate = "{n} a second" // cli.ratelimit.show.ceiling.rate
+ ratelimitCeilBurst = "bursts of {n}" // cli.ratelimit.show.ceiling.burst
ratelimitSetOK = "rate limit set on" // cli.ratelimit.set.ok
ratelimitSetBad = "rate limit not set on" // cli.ratelimit.set.bad
ratelimitClearOK = "rate limit back to defaults on" // cli.ratelimit.clear.ok
@@ -224,6 +228,10 @@ func printRateLimit(w io.Writer, rl apiRateLimit) {
}
lines = append(lines, backstop)
+ if c := ratelimitCeilingLine(rl.Ceiling); c != "" {
+ lines = append(lines, c)
+ }
+
for _, p := range eff.Paths {
lines = append(lines, ratelimitCopy(ratelimitShowPath, "prefix", p.Prefix,
"share", n(p.ClientShare), "rate", n(p.Rate), "burst", n(p.Burst)))
@@ -237,6 +245,29 @@ func printRateLimit(w io.Writer, rl apiRateLimit) {
}
}
+// ratelimitCeilingLine is the plan ceiling's one line: the numbers the plan
+// sets, in the order share, rate, burst, and nothing when the plan sets none
+// (a null ceiling, or one whose fields are all zero).
+func ratelimitCeilingLine(c *ratelimit.Rule) string {
+ if c == nil {
+ return ""
+ }
+ var parts []string
+ if c.ClientShare != 0 {
+ parts = append(parts, ratelimitCopy(ratelimitCeilShare, "n", strconv.Itoa(c.ClientShare)))
+ }
+ if c.Rate != 0 {
+ parts = append(parts, ratelimitCopy(ratelimitCeilRate, "n", strconv.Itoa(c.Rate)))
+ }
+ if c.Burst != 0 {
+ parts = append(parts, ratelimitCopy(ratelimitCeilBurst, "n", strconv.Itoa(c.Burst)))
+ }
+ if len(parts) == 0 {
+ return ""
+ }
+ return ratelimitCopy(ratelimitShowCeiling, "parts", strings.Join(parts, ", "))
+}
+
// pathFlags collects the repeatable -path and -remove-path values.
type pathFlags []string
diff --git a/ratelimit_cli_test.go b/ratelimit_cli_test.go
index 3c41a1804..89532c1a8 100644
--- a/ratelimit_cli_test.go
+++ b/ratelimit_cli_test.go
@@ -251,6 +251,7 @@ func TestRatelimitShowMarksDefaultsAndCeiling(t *testing.T) {
" busy at 32 requests in flight\n" +
" per address, busy 20 requests in flight (the plan caps this; you set 40)\n" +
" backstop 1000 a second, bursts of 1000 (the plan caps this; you set 5000 a second, bursts of 1000)\n" +
+ " plan ceiling share 20, 1000 a second, bursts of 2000\n" +
" /api/ 8 in flight per address when busy, backstop 1000 a second, bursts of 1000\n" +
" last 24 hours 12 turned away while busy, 3 over the backstop\n"
if out.String() != want {
@@ -261,6 +262,34 @@ func TestRatelimitShowMarksDefaultsAndCeiling(t *testing.T) {
}
}
+// TestRatelimitShowPrintsPlanCeiling: the ceiling line names only the
+// numbers the plan actually sets, in the order share, rate, burst, and a
+// null or all-zero ceiling prints no line at all.
+func TestRatelimitShowPrintsPlanCeiling(t *testing.T) {
+ fc := newRLConsole(t)
+ show := func(ceiling string) string {
+ fc.current = strings.Replace(rlShowPlain, `"ceiling":null`, `"ceiling":`+ceiling, 1)
+ var out bytes.Buffer
+ if failed := ratelimitShow(newTestSession(fc.ts.URL), apiAcct{Seg: "bah"}, "app", []string{"b.example.com"}, &out); failed != 0 {
+ t.Fatalf("failed = %d", failed)
+ }
+ return out.String()
+ }
+ const line = " plan ceiling share 64, 5000 a second\n"
+ got := show(`{"client_share":64,"rate":5000}`)
+ if !strings.Contains(got, "backstop 300 a second, bursts of 1000 (default)\n"+line+" last 24 hours") {
+ t.Errorf("non-clamping ceiling:\n%s\nwant the line %q after the backstop line", got, line)
+ }
+ if got := show(`{"burst":2000}`); !strings.Contains(got, " plan ceiling bursts of 2000\n") {
+ t.Errorf("burst only:\n%s", got)
+ }
+ for _, c := range []string{`null`, `{}`, `{"client_share":0,"rate":0,"burst":0}`} {
+ if got := show(c); strings.Contains(got, "plan ceiling") {
+ t.Errorf("ceiling %s printed a line:\n%s", c, got)
+ }
+ }
+}
+
func TestRatelimitOldConsole(t *testing.T) {
fc := newRLConsole(t)
fc.patch = func(string) (int, string) {
@@ -290,7 +319,7 @@ func TestRatelimitDocsCarryTheUsage(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- if !strings.Contains(string(raw), "### carlos ratelimit\n\n```\n"+ratelimitUsage+"```\n") {
+ if !strings.Contains(string(raw), "### carlos ratelimit\n\n```text\n"+ratelimitUsage+"```\n") {
t.Error("docs/site/cli.md does not carry the ratelimit usage text verbatim")
}
}