Files
sundynix-agentix/sundynix-gateway/internal/store/models.go
T
Blizzard addaa1b34f feat(space): 共享工作区增量3a —— Agent 编排按 Space 共享(纯 PG 打样)
引入 Space 中间容器(租户>Space>成员),资源作用域从 owner 改为 space_id,
让"个人私有/项目临时组队/整租户共享"出自同一模型(设计见 SPACE_DESIGN.md)。
先在纯 PG 的 Agent 上打样,零存储风险,验证协作+RBAC+切换 UX。

后端:
- 新表 Space{tenant_id,name,kind,creator,archived} + SpaceMember{space_id,user_id,role}
  (如 Tenant 般不 isTenantScoped);User.ActiveSpaceID;Agent 作用域 owner→space_id,
  owner 降级为创建人(供 UI 显示 / 删他人鉴权)
- store/space.go:个人空间幂等/活跃空间解析/切换/列表/建/成员CRUD/归档
- 迁移顺序坑:结构体只放非唯一 index,MigrateAgentSpaces 回填 space_id 后再建唯一
  索引 idx_agent_sn + DROP 旧 idx_agent_on(否则存量空 space_id 撞车);启动序4步幂等
- 中间件 SpaceContext(注入 space_id) + RequireSpaceRole(照 RequireTenantRole)
- handler/space.go 空间端点 + 路由;agent.go 改空间作用域(删/覆盖他人需 admin)
- 计费零改动(Space 与 ResolveBillingTenantID 正交)

桌面端:
- api.ts space 接口;顶栏 SpaceSwitcher(含新建项目空间);StudioView 随空间切换
  重拉编排 + viewer 禁保存;Agent 列表显示创建人 + 按 mine 控删除

验证:中间件6门控单测 + DB迁移(13个人空间/9 Agent全re-key/索引换新) + 后端HTTP全
场景(member见他人编排/删他人403、viewer存403、非成员切空间400+隔离、owner删他人200)
+ 浏览器实机(切换器3空间/Studio空间编排随切换隔离刷新/创建人显示/console无错)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 10:51:33 +08:00

108 lines
5.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package store
// 数据库映射模型。表名经 GORM NamingStrategy 统一加 sundynix_ 前缀 + 单数:
// User → sundynix_userTask → sundynix_task。
// 约定:均嵌入 BaseModel(雪花字符串 id + created_at/updated_at + 软删 deleted_at);
// 建表/改表一律走 AutoMigrate,不手写 DDL。
// User 是平台用户(Users)。
type User struct {
BaseModel
Email string `gorm:"uniqueIndex;size:255"`
Name string `gorm:"size:64"`
PasswordHash string `gorm:"size:255" json:"-"` // bcrypt;绝不出 JSON
ActiveTenantID string `gorm:"size:64" json:"-"` // 当前活跃租户(多租户切换;空=用默认)
ActiveSpaceID string `gorm:"size:64" json:"-"` // 当前活跃工作区(Space)(增量3;空/失效=用活跃租户的个人空间)
}
// Task 是一次提交的 Agent 编排任务(DSL)。
// 业务 idtask_xxx,用于 NATS subject/stream)单列 TaskID,主键统一雪花。
type Task struct {
BaseModel
TenantID string `gorm:"size:64;index"` // 多租户作用域(tenant 插件按 ctx 自动填/过滤)
Owner string `gorm:"size:64;index"` // 提交者 user.id(个人工作台按此过滤"我的运行")
TaskID string `gorm:"uniqueIndex;size:64"` // task_xxx
Graph string `gorm:"type:jsonb"` // React Flow 导出的 DSL 原文
Status string `gorm:"size:32"` // submitted / running / done / failed / timeout
Detail string `gorm:"type:text"` // 失败/超时原因等(状态机回写)
// 收尾持久化:供「运行历史复盘」永久回放(Redis 流仅 10min TTL,过期后历史任务靠这两列)。
Output string `gorm:"type:text"` // 最终模型输出(收尾时由网关从流快照落库)
Trace string `gorm:"type:text"` // 执行轨迹事件 JSON 数组(存为文本,容忍空串;不在库内查它)
}
func (Task) isTenantScoped() {}
// Eval 是一次任务的自动化评测结果(dispatcher 评完经 NATS 回写,每任务一条,按 task_id upsert)。
type Eval struct {
BaseModel
TenantID string `gorm:"size:64;index"` // 多租户作用域(SaveEval 从对应 task 复制)
Owner string `gorm:"size:64;index"` // 提交者 user.id(从对应 task 复制)
TaskID string `gorm:"uniqueIndex;size:64"`
Overall float64 // 综合分 [0,1]
Rule float64 // 规则分
LLM float64 // LLM 质量分
Faithful float64 // RAG 忠实度分(0=无来源未评)
Level string `gorm:"size:16"` // ok / warn / poor
Flags string `gorm:"type:text"` // 命中问题(JSON 数组字符串)
Reason string `gorm:"type:text"` // 评语
Sources int // 检索来源数
Corrected bool // 是否经低分自动纠偏重生成后采纳(恒温器闭环)
}
func (Eval) TableName() string { return "sundynix_eval" }
func (Eval) isTenantScoped() {}
// AuditLog 是一条敏感操作留痕(改模型/密钥/激活 prompt/审批 等)。
// 由 Audit 中间件在请求收尾时 best-effort 写入;只增不改,供运维溯源。
type AuditLog struct {
BaseModel
Actor string `gorm:"size:64;index"` // 操作者 uid(未登录/系统留空)
Action string `gorm:"size:8"` // HTTP 方法:POST / PUT / DELETE
Route string `gorm:"size:128"` // 路由模式,如 /api/v1/admin/models/:id
Path string `gorm:"size:256"` // 实际请求路径
Status int // HTTP 响应状态码
IP string `gorm:"size:64"`
Detail string `gorm:"type:text"` // 备注(可选,如目标名/关键参数)
}
func (AuditLog) TableName() string { return "sundynix_audit_log" }
// GuardrailEvent 是一次输入护栏命中(硬拦截 blocked / 灰区 suspect)。
// 由 Guardrail 中间件命中时 best-effort 写入;供安全溯源"谁触发多少次护栏"。
type GuardrailEvent struct {
BaseModel
Actor string `gorm:"size:64;index"` // 操作者 uid(未登录留空)
Kind string `gorm:"size:16;index"` // blocked(硬拦)/ suspect(灰区放行)
Reason string `gorm:"size:256"` // 拦截原因(blocked
Signals string `gorm:"type:text"` // 命中软信号 JSON 数组(suspect
Method string `gorm:"size:8"`
Path string `gorm:"size:256"`
IP string `gorm:"size:64"`
}
func (GuardrailEvent) TableName() string { return "sundynix_guardrail_event" }
// Tenant 是多租户的计费/隔离单位(组织/账户)。个人用户 = 一个单人默认租户;团队/企业 = 多成员。
type Tenant struct {
BaseModel
Name string `gorm:"size:128"`
Slug string `gorm:"size:64;uniqueIndex"` // 唯一短标识(默认租户用 default-<uid>
Plan string `gorm:"size:32;default:free"` // free / pro / enterprise
Status string `gorm:"size:16;default:active"` // active / suspended
CreditBalanceMicro int64 `gorm:"column:credit_balance_micro"` // 物化积分余额 ×10⁻⁶(= credit_ledger 之和;用量扣、充值增)
SharedBilling bool `gorm:"column:shared_billing"` // 共享计费:开=成员消耗计本租户池;关=成员计各自个人池(owner 恒计本租户)
}
func (Tenant) TableName() string { return "sundynix_tenant" }
// TenantMember 是用户与租户的成员关系(带角色)。一个用户可属多个租户。
type TenantMember struct {
BaseModel
TenantID string `gorm:"size:64;uniqueIndex:idx_tenant_user;index"`
UserID string `gorm:"size:64;uniqueIndex:idx_tenant_user;index"`
Role string `gorm:"size:16"` // owner / admin / member / viewer / billing_admin
Status string `gorm:"size:16;default:active"` // active / invited / removed
}
func (TenantMember) TableName() string { return "sundynix_tenant_member" }