@@ -45,10 +45,10 @@ func TestParseFenceID_Shapes(t *testing.T) {
4545
4646func TestFenceLocs_FindsStartsOnly (t * testing.T ) {
4747 body := "intro\n " +
48- FenceStart ("svc[rule- ]" ) + "\n content\n " + FenceEnd ("svc[rule- ]" ) + "\n \n more prose\n " +
48+ FenceStart ("svc[rule]" ) + "\n content\n " + FenceEnd ("svc[rule]" ) + "\n \n more prose\n " +
4949 FenceStart ("svc" ) + "\n content\n " + FenceEnd ("svc" ) + "\n "
5050 locs := FenceLocs (body )
51- if len (locs ) != 2 || locs [0 ].ID != "svc[rule- ]" || locs [1 ].ID != "svc" {
51+ if len (locs ) != 2 || locs [0 ].ID != "svc[rule]" || locs [1 ].ID != "svc" {
5252 t .Fatalf ("FenceLocs = %+v, want the two start markers in order" , locs )
5353 }
5454 if locs [0 ].Offset >= locs [1 ].Offset {
@@ -67,11 +67,11 @@ func partitionDump() Dump {
6767
6868func TestRenderGroupFences_SubsetPlusCatchAll (t * testing.T ) {
6969 d := partitionDump ()
70- out , violations := RenderGroupFences (d , "svc" , []string {"svc[rule- ]" , "svc" })
70+ out , violations := RenderGroupFences (d , "svc" , []string {"svc[rule]" , "svc" })
7171 if len (violations ) != 0 {
7272 t .Fatalf ("unexpected violations: %v" , violations )
7373 }
74- subset , catchAll := out ["svc[rule- ]" ], out ["svc" ]
74+ subset , catchAll := out ["svc[rule]" ], out ["svc" ]
7575 if ! strings .Contains (subset , "### rule-create" ) || ! strings .Contains (subset , "### rule-delete" ) {
7676 t .Errorf ("subset fence should carry the rule verbs:\n %s" , subset )
7777 }
@@ -84,7 +84,7 @@ func TestRenderGroupFences_SubsetPlusCatchAll(t *testing.T) {
8484 if strings .Contains (catchAll , "### rule-create" ) {
8585 t .Errorf ("catch-all fence must not repeat claimed verbs:\n %s" , catchAll )
8686 }
87- if ! strings .HasPrefix (subset , FenceStart ("svc[rule- ]" )) || ! strings .HasSuffix (subset , FenceEnd ("svc[rule- ]" )) {
87+ if ! strings .HasPrefix (subset , FenceStart ("svc[rule]" )) || ! strings .HasSuffix (subset , FenceEnd ("svc[rule]" )) {
8888 t .Errorf ("subset fence markers must carry the full fence id:\n %s" , subset )
8989 }
9090}
@@ -96,9 +96,9 @@ func TestRenderGroupFences_Violations(t *testing.T) {
9696 ids []string
9797 want string // substring of some violation
9898 }{
99- {name : "double claim" , ids : []string {"svc[rule- ]" , "svc[rule-create]" , "svc" }, want : `verb "rule-create" claimed by` },
99+ {name : "double claim" , ids : []string {"svc[rule]" , "svc[rule-create]" , "svc" }, want : `verb "rule-create" claimed by` },
100100 {name : "dead prefix" , ids : []string {"svc[nope]" , "svc" }, want : `prefix "nope" claims no verb` },
101- {name : "no catch-all" , ids : []string {"svc[rule- ]" }, want : "no catch-all fence for unclaimed verbs: create, list" },
101+ {name : "no catch-all" , ids : []string {"svc[rule]" }, want : "no catch-all fence for unclaimed verbs: create, list" },
102102 {name : "duplicate id" , ids : []string {"svc" , "svc" }, want : `fence "svc" appears more than once` },
103103 {name : "foreign group" , ids : []string {"other" , "svc" }, want : `fence "other" does not belong to group "svc"` },
104104 }
@@ -118,21 +118,21 @@ func TestRenderGroupFences_Violations(t *testing.T) {
118118
119119func TestCheckFences_SplitAcrossDocs (t * testing.T ) {
120120 d := partitionDump ()
121- fresh , violations := RenderGroupFences (d , "svc" , []string {"svc[rule- ]" , "svc" })
121+ fresh , violations := RenderGroupFences (d , "svc" , []string {"svc[rule]" , "svc" })
122122 if len (violations ) != 0 {
123123 t .Fatalf ("unexpected violations: %v" , violations )
124124 }
125125
126126 docs := []Doc {
127- {Path : "rules" , Body : "# Rules\n \n " + fresh ["svc[rule- ]" ] + "\n " },
127+ {Path : "rules" , Body : "# Rules\n \n " + fresh ["svc[rule]" ] + "\n " },
128128 {Path : "svc" , Body : "# Svc\n \n " + fresh ["svc" ] + "\n " },
129129 }
130130 if issues := CheckFences (d , docs ); len (issues ) != 0 {
131131 t .Errorf ("fresh split fences: want 0 issues, got %+v" , issues )
132132 }
133133
134134 stale := []Doc {
135- {Path : "rules" , Body : "# Rules\n \n " + FenceStart ("svc[rule- ]" ) + "\n \n WRONG\n \n " + FenceEnd ("svc[rule- ]" ) + "\n " },
135+ {Path : "rules" , Body : "# Rules\n \n " + FenceStart ("svc[rule]" ) + "\n \n WRONG\n \n " + FenceEnd ("svc[rule]" ) + "\n " },
136136 {Path : "svc" , Body : "# Svc\n \n " + fresh ["svc" ] + "\n " },
137137 }
138138 issues := CheckFences (d , stale )
@@ -145,8 +145,8 @@ func TestCheckFences_TopologyIssues(t *testing.T) {
145145 d := partitionDump ()
146146 // A subset fence with no catch-all anywhere: the group's remaining verbs
147147 // have no home.
148- fresh , _ := RenderGroupFences (d , "svc" , []string {"svc[rule- ]" , "svc" })
149- docs := []Doc {{Path : "rules" , Body : "# Rules\n \n " + fresh ["svc[rule- ]" ] + "\n " }}
148+ fresh , _ := RenderGroupFences (d , "svc" , []string {"svc[rule]" , "svc" })
149+ docs := []Doc {{Path : "rules" , Body : "# Rules\n \n " + fresh ["svc[rule]" ] + "\n " }}
150150 issues := CheckFences (d , docs )
151151 if len (issues ) != 1 || issues [0 ].Kind != "fence-topology" {
152152 t .Fatalf ("want 1 fence-topology issue, got %+v" , issues )
@@ -162,3 +162,28 @@ func TestCheckFences_TopologyIssues(t *testing.T) {
162162 t .Errorf ("unknown group marker should be flagged: %+v" , issues )
163163 }
164164}
165+
166+ // TestRenderGroupFences_PrefixIsSegmentBounded pins the hyphen-boundary claim
167+ // semantics: "rule" claims "rule-create" but must NOT claim "rule2-list" — an
168+ // unbounded prefix match would misroute it with a clean single-owner
169+ // partition that no topology check could flag.
170+ func TestRenderGroupFences_PrefixIsSegmentBounded (t * testing.T ) {
171+ mk := func (verb string ) Command {
172+ return Command {Path : "svc " + verb , Group : "svc" , Short : "S " + verb , Use : verb }
173+ }
174+ d := Dump {Commands : []Command {mk ("rule-create" ), mk ("rule2-list" ), mk ("rule" )}}
175+ out , violations := RenderGroupFences (d , "svc" , []string {"svc[rule]" , "svc" })
176+ if len (violations ) != 0 {
177+ t .Fatalf ("unexpected violations: %v" , violations )
178+ }
179+ subset , catchAll := out ["svc[rule]" ], out ["svc" ]
180+ if ! strings .Contains (subset , "### rule-create" ) || ! strings .Contains (subset , "### rule\n " ) {
181+ t .Errorf ("subset must claim the exact verb and its hyphen extensions:\n %s" , subset )
182+ }
183+ if strings .Contains (subset , "### rule2-list" ) {
184+ t .Errorf ("subset must not claim the near-miss verb:\n %s" , subset )
185+ }
186+ if ! strings .Contains (catchAll , "### rule2-list" ) {
187+ t .Errorf ("near-miss verb must land in the catch-all:\n %s" , catchAll )
188+ }
189+ }
0 commit comments