fix: Z39 isolate module data by slug

Keep agent-bound modules in per-app schemas and prevent stale frontend routes from displaying another module's blueprint or rows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
whm
2026-08-07 17:31:12 +08:00
parent 43dc708f1e
commit 473113682f
13 changed files with 402 additions and 207 deletions

View File

@@ -1,6 +1,7 @@
package blueprint
import (
"crypto/sha256"
"encoding/json"
"fmt"
"regexp"
@@ -48,12 +49,12 @@ type Storage struct {
}
type Entity struct {
Name string `json:"name"`
Table string `json:"table"`
Label string `json:"label"`
PrimaryKey string `json:"primary_key"`
Fields []Field `json:"fields"`
Indexes []Index `json:"indexes,omitempty"`
Name string `json:"name"`
Table string `json:"table"`
Label string `json:"label"`
PrimaryKey string `json:"primary_key"`
Fields []Field `json:"fields"`
Indexes []Index `json:"indexes,omitempty"`
}
type Field struct {
@@ -271,10 +272,7 @@ func (bp *Blueprint) AssignSchemaName(tenantID int64) string {
bp.Storage.SchemaName = "public"
return "public"
}
name := fmt.Sprintf("app_t%d_%s", tenantID, bp.Meta.Slug)
if len(name) > 48 {
name = name[:48]
}
name := scopedIdent(fmt.Sprintf("app_t%d_%s", tenantID, bp.Meta.Slug))
bp.Storage.SchemaName = name
return name
}
@@ -284,11 +282,30 @@ func (bp *Blueprint) AssignDatabaseName(tenantID int64) string {
if bp.Storage.Mode != "database_per_app" {
return ""
}
name := fmt.Sprintf("appdb_t%d_%s", tenantID, bp.Meta.Slug)
if len(name) > 48 {
name = name[:48]
return scopedIdent(fmt.Sprintf("appdb_t%d_%s", tenantID, bp.Meta.Slug))
}
// scopedIdent 保留可读前缀,并为超长标识符附加内容哈希,避免不同 slug 截断后碰撞。
func scopedIdent(raw string) string {
if len(raw) <= 48 {
return raw
}
return name
sum := sha256.Sum256([]byte(raw))
suffix := fmt.Sprintf("_%x", sum[:6])
return raw[:48-len(suffix)] + suffix
}
// Revision 返回蓝图内容的稳定短哈希,用于发布回执、缓存隔离与诊断日志。
func (bp *Blueprint) Revision() string {
if bp == nil {
return ""
}
raw, err := json.Marshal(bp)
if err != nil {
return ""
}
sum := sha256.Sum256(raw)
return fmt.Sprintf("%x", sum[:8])
}
func checkIdent(field, v string) error {

View File

@@ -1,4 +1,4 @@
package blueprint
package blueprint
import "testing"
@@ -18,6 +18,32 @@ func TestNormalizeIdent(t *testing.T) {
}
}
func TestAssignSchemaNameKeepsLongSlugsDistinct(t *testing.T) {
prefix := "module_with_a_very_long_shared_slug_prefix_that_used_to_collide_"
a := &Blueprint{Meta: Meta{Slug: prefix + "a"}, Storage: Storage{Mode: "schema_per_app"}}
b := &Blueprint{Meta: Meta{Slug: prefix + "b"}, Storage: Storage{Mode: "schema_per_app"}}
gotA := a.AssignSchemaName(12)
gotB := b.AssignSchemaName(12)
if gotA == gotB {
t.Fatalf("different slugs resolved to the same schema: %q", gotA)
}
if len(gotA) > 48 || len(gotB) > 48 {
t.Fatalf("schema names exceed identifier limit: %q / %q", gotA, gotB)
}
if gotA != a.AssignSchemaName(12) {
t.Fatalf("schema name is not deterministic: %q", gotA)
}
}
func TestRevisionChangesWithSlug(t *testing.T) {
a := &Blueprint{Version: "1.0", Meta: Meta{Slug: "whm1"}}
b := &Blueprint{Version: "1.0", Meta: Meta{Slug: "whm11"}}
if a.Revision() == "" || a.Revision() == b.Revision() {
t.Fatalf("revision must include blueprint slug: %q / %q", a.Revision(), b.Revision())
}
}
func TestSanitizeFields(t *testing.T) {
bp := &Blueprint{
Version: "1.0",