carlos / platform Public

region HA 2a: the copy record and its gate

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev 28fa37958b935d91c3ed6debd999c685d3fe8c06 parent 5d95ec3
2 files changed, +130 −0
  • approved_region_ha_copy_test.go +68 −0
  • docs/superpowers/plans/2026-10-16-region-ha-2a-copy.md +62 −0
diff --git a/approved_region_ha_copy_test.go b/approved_region_ha_copy_test.go
new file mode 100644
index 000000000..744d17db7
--- /dev/null
+++ b/approved_region_ha_copy_test.go
@@ -0,0 +1,68 @@
+// approved_region_ha_copy_test.go
+package main
+
+import (
+ "os"
+ "regexp"
+ "strconv"
+ "testing"
+)
+
+// The copy gate of region HA plan 2a (Task 1). The record is a markdown
+// table in the moves record's format, read with the moves gate's parser
+// and checked with the run gate's helpers, so "approved" and "applied"
+// mean what they mean there:
+//
+// - TestRegionHACopyRecordIsComplete: the record names exactly RHA-C1 to
+// RHA-C19;
+// - TestRegionHACopyHasNoEmDash;
+// - TestRegionHABuiltCopyIsApproved: an id in regionHACopyBuilt is an
+// approved row. No pending string reaches code, CLI help or docs;
+// - TestRegionHABuiltCopyIsApplied: a built row's text is in the file it
+// names.
+const regionHACopyRecordPath = "docs/superpowers/plans/2026-10-16-region-ha-2a-copy.md"
+
+var regionHACopyIDPattern = regexp.MustCompile(`^RHA-C[0-9]+$`)
+
+// regionHACopyIDs is the record's inventory: RHA-C1 to RHA-C19, pinned.
+var regionHACopyIDs = func() []string {
+ ids := make([]string, 0, 19)
+ for i := 1; i <= 19; i++ {
+ ids = append(ids, "RHA-C"+strconv.Itoa(i))
+ }
+ return ids
+}()
+
+// regionHACopyBuilt is every id whose text the code or docs carry. The
+// task that writes a row's text adds its id here in the same commit, once
+// Paul has approved the row.
+var regionHACopyBuilt = []string{}
+
+func mustParseRegionHARecord(t *testing.T) []copyRow {
+ t.Helper()
+ raw, err := os.ReadFile(regionHACopyRecordPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ rows, err := parseCopyTable(string(raw), regionHACopyRecordPath, regionHACopyIDPattern, "an RHA-C<n>")
+ if err != nil {
+ t.Fatal(err)
+ }
+ return rows
+}
+
+func TestRegionHACopyRecordIsComplete(t *testing.T) {
+ reportAll(t, checkMovesInventory(mustParseRegionHARecord(t), regionHACopyIDs))
+}
+
+func TestRegionHACopyHasNoEmDash(t *testing.T) {
+ reportAll(t, checkMovesNoEmDash(mustParseRegionHARecord(t)))
+}
+
+func TestRegionHABuiltCopyIsApproved(t *testing.T) {
+ reportAll(t, checkRunBuilt(mustParseRegionHARecord(t), regionHACopyBuilt))
+}
+
+func TestRegionHABuiltCopyIsApplied(t *testing.T) {
+ reportAll(t, checkRunApplied(builtRows(mustParseRegionHARecord(t), regionHACopyBuilt), "."))
+}
diff --git a/docs/superpowers/plans/2026-10-16-region-ha-2a-copy.md b/docs/superpowers/plans/2026-10-16-region-ha-2a-copy.md
new file mode 100644
index 000000000..cb94e5c3c
--- /dev/null
+++ b/docs/superpowers/plans/2026-10-16-region-ha-2a-copy.md
@@ -0,0 +1,62 @@
+# Region HA plan 2a: copy record (pending Paul's review)
+
+Every user-facing string plan 2a (`2026-10-16-region-ha-2a-core.md`)
+writes: console refusals, CLI output and usage lines, the operator alert,
+and the docs prose. The gate is `approved_region_ha_copy_test.go` at the
+repo root, which reuses the moves gate's parser, so the format is the
+moves record's: one table per heading, columns id, file, text, status.
+
+The rules:
+
+- A row is `pending` until Paul approves it in the copy review (the
+ `copy-review` skill). His edits are applied to the text here verbatim
+ and the row becomes `approved`.
+- No pending string reaches code, CLI help or docs. The task that writes
+ a row's text adds its id to `regionHACopyBuilt` in the gate in the same
+ commit, and from then on the gate fails unless the row is `approved`
+ and its text is in the file it names. Tasks 10 and 11 therefore wait
+ for their rows' approval.
+- In a Go file the text is one string literal, or one `fmt.Sprintf`
+ format, equal to the row with `%s` where the row has a `<placeholder>`.
+ A row whose file cell says "a fragment" is a piece of a larger literal
+ (the `carlos system` usage text).
+- No em dashes. A full stop and a new sentence instead. The role doors'
+ refusals start lower case, as the console's other CLI refusals do; the
+ no-capacity refusal is shared with the browser's domain door, so it is
+ a capitalised sentence, as `claimResponseUnavailable` is.
+
+Placeholders: `<box>` a box's roster reference or label; `<role>` a role
+name (default, legacy or platform); `<region>` a placement code or the
+home region's name.
+
+## Box and group roles (Task 10)
+
+| id | file | text | status |
+|---|---|---|---|
+| RHA-C1 | internal/console/apiboxroles.go | role must be default, legacy or platform | pending |
+| RHA-C2 | internal/console/apiboxroles.go | a group's role must be default or legacy | pending |
+| RHA-C3 | internal/console/apiboxroles.go | could not read the box roster. Try again. | pending |
+| RHA-C4 | internal/console/apiboxroles.go | the box roster changed while the role was being saved, and nothing was changed. Try again. | pending |
+| RHA-C5 | internal/console/apiboxroles.go | could not save the role. Try again. | pending |
+| RHA-C6 | internal/console/apiboxroles.go | no enrolled box is named <box> | pending |
+| RHA-C7 | internal/console/apiboxroles.go | <region> has no group | pending |
+| RHA-C8 | internal/console/apiboxroles.go | <box> is retired, so it takes no role | pending |
+| RHA-C9 | system_roles.go | <box> now has the <role> role | pending |
+| RHA-C10 | system_roles.go | the group in <region> now has the <role> role | pending |
+| RHA-C11 | system.go (a fragment of the usage) | whether new instances may be placed on a box. A legacy or | pending |
+| RHA-C12 | system.go (a fragment of the usage) | platform box keeps the instances it has and takes no new ones | pending |
+| RHA-C13 | system.go (a fragment of the usage) | the same for every box a region's group has enrolled, and for | pending |
+| RHA-C14 | system.go (a fragment of the usage) | every box it enrolls later | pending |
+| RHA-C15 | docs/site/cli.md | Box and group roles | pending |
+| RHA-C16 | docs/site/cli.md | carlos system box role says whether new instances may be placed on a box. A box with the default role can take them. A legacy or platform box keeps the instances it has and takes no new ones: no new instance, relocation or move lands on it. carlos system group role sets the role of every box a region's group has enrolled, and of every box it enrolls later. Both work only on a console with region HA on. | pending |
+
+## No box has room (Task 11)
+
+One refusal for every door that chooses a box (H27): instance create,
+domain attach, standalone sidecar create and the move door's destination.
+
+| id | file | text | status |
+|---|---|---|---|
+| RHA-C17 | internal/console/nocapacity.go | No box in <region> has room right now. Try again in a few minutes. | pending |
+| RHA-C18 | internal/console/nocapacity.go (the operator alert's subject) | No box in <region> has room | pending |
+| RHA-C19 | internal/console/nocapacity.go (the operator alert's body) | A request in <region> was refused because no box there can take it. Every box is full, stale or excluded by its role. Add a box, or free memory or disk on one, and check the eligible-capacity alarm for <region>. | pending |