Address Gadfly review on #19: batch fills + tighten Region
Build image / build-and-push (push) Successful in 15s
Build image / build-and-push (push) Successful in 15s
- FillRegion: insert the whole batch in one transaction (store.CreatePlantings) instead of one round-trip per plop, and refuse fills over maxFillPlops (5000) so a max-sized bed with tiny spacing can't generate ~10^5 sequential writes. Guards the computed radius is finite/positive and clamps the region to the object's bounds before packing (bounds hexCenters). - FillNamedRegion + FillRegion share a fillLoaded body, so the object is loaded and authorized once (no double objectForRole). - Region is now rect-only — dropped the speculative circle fields/branch that NamedRegion never produced and hexCenters didn't pack (circles are post-v1). - NamedRegion guards a nil object and no longer maps a blank name to "all" (blank → ErrInvalidInput, matching the doc). - ClearObject documents why it deliberately doesn't require plantable. Tests: empty/nil region name → ErrInvalidInput; an oversized fill → ErrInvalidInput (over the cap). Tagged demo tidied (checked errors, domain.RoleViewer, t.Helper). GOWORK=off go build/vet/test ./internal/... green; tagged agent test green against majordomo (go.mod stays majordomo-free). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"gitea.stevedudenhoeffer.com/steve/majordomo/llm"
|
||||
|
||||
"gitea.stevedudenhoeffer.com/steve/pansy/internal/config"
|
||||
"gitea.stevedudenhoeffer.com/steve/pansy/internal/domain"
|
||||
"gitea.stevedudenhoeffer.com/steve/pansy/internal/service"
|
||||
"gitea.stevedudenhoeffer.com/steve/pansy/internal/store"
|
||||
)
|
||||
@@ -51,9 +52,9 @@ func TestToolboxScenario(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("garden: %v", err)
|
||||
}
|
||||
garlic, _ := svc.CreatePlant(ctx, owner.ID, service.PlantInput{Name: "Garlic", Category: "vegetable", SpacingCM: 15, Color: "#d9d2c5", Icon: "🧄"})
|
||||
basil, _ := svc.CreatePlant(ctx, owner.ID, service.PlantInput{Name: "Basil", Category: "herb", SpacingCM: 25, Color: "#4a7c3f", Icon: "🌿"})
|
||||
beans, _ := svc.CreatePlant(ctx, owner.ID, service.PlantInput{Name: "Beans", Category: "vegetable", SpacingCM: 10, Color: "#6b8e23", Icon: "🫘"})
|
||||
garlic := mustPlant(t, svc, owner.ID, "Garlic", 15, "🧄")
|
||||
basil := mustPlant(t, svc, owner.ID, "Basil", 25, "🌿")
|
||||
beans := mustPlant(t, svc, owner.ID, "Beans", 10, "🫘")
|
||||
|
||||
// create_object → a 400×400 bed.
|
||||
res := call("create_object", map[string]any{
|
||||
@@ -114,12 +115,15 @@ func TestToolboxScenario(t *testing.T) {
|
||||
}
|
||||
|
||||
// ACL: a viewer's fill_region is refused (the toolbox runs as that actor).
|
||||
viewerUser, _ := svc.Register(ctx, service.RegisterInput{Email: "[email protected]", DisplayName: "V", Password: "password123"})
|
||||
if _, err := svc.AddShare(ctx, owner.ID, g.ID, "[email protected]", "viewer"); err != nil {
|
||||
viewerUser, err := svc.Register(ctx, service.RegisterInput{Email: "[email protected]", DisplayName: "V", Password: "password123"})
|
||||
if err != nil {
|
||||
t.Fatalf("register viewer: %v", err)
|
||||
}
|
||||
if _, err := svc.AddShare(ctx, owner.ID, g.ID, "[email protected]", domain.RoleViewer); err != nil {
|
||||
t.Fatalf("share: %v", err)
|
||||
}
|
||||
viewerBox := NewToolbox(svc, viewerUser.ID)
|
||||
vr := viewerBox.Execute(ctx, llm.ToolCall{ID: "2", Name: "fill_region", Arguments: mustJSON(map[string]any{
|
||||
vr := viewerBox.Execute(ctx, llm.ToolCall{ID: "2", Name: "fill_region", Arguments: mustJSON(t, map[string]any{
|
||||
"objectId": bed.ID, "region": "all", "plantId": garlic.ID,
|
||||
})})
|
||||
if !vr.IsError {
|
||||
@@ -127,7 +131,22 @@ func TestToolboxScenario(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func mustJSON(v any) json.RawMessage {
|
||||
b, _ := json.Marshal(v)
|
||||
func mustJSON(t *testing.T, v any) json.RawMessage {
|
||||
t.Helper()
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func mustPlant(t *testing.T, svc *service.Service, owner int64, name string, spacing float64, icon string) *domain.Plant {
|
||||
t.Helper()
|
||||
p, err := svc.CreatePlant(context.Background(), owner, service.PlantInput{
|
||||
Name: name, Category: domain.CategoryVegetable, SpacingCM: spacing, Color: "#4a7c3f", Icon: icon,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create plant %s: %v", name, err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user