Blizzard
632057f29b
docs: 状态机 & agent loop 三层嵌套图(独立 SVG)
...
任务状态机(NATS→PG) → Eino compose.Graph DAG 调度 → react.Agent ReAct 循环。
自包含 SVG(内联样式),任意浏览器/编辑器可开。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-09 17:12:55 +08:00
Blizzard
1ee1e87371
fix: 状态/用量回写升级 JetStream 持久 —— 堵住漏账(core NATS fire-and-forget)
...
问题:usage(计费)/status 回写走 core NATS,网关离线/慢消费者/NATS 抖动期间
dispatcher 发的事件直接丢——任务照跑照烧 token,但这次计费凭空消失(漏账),且零重试零对账。
(对比:提交/审批/入库本就 JetStream 持久,唯独回写是 best-effort。)
修复(照 tasks/approvals 套路):
- 新增 JetStream 流 SUNDYNIX_USAGE(MaxAge 72h) / SUNDYNIX_STATUS(24h),捕获 usage.task/status.task。
- PublishUsage/PublishTaskStatus 改 js.Publish(同步等 stream ack);dispatcher+gateway 启动各自 ensure 流。
- ConsumeUsage/ConsumeTaskStatus 持久消费者 + 显式 ack:落库成功 Ack、失败 Nak 重投自愈、脏数据 Term。
- 幂等保证 at-least-once 安全:usage_event.task_id 唯一 + 门控;SaveUsageEvent 返回 inserted,
仅新插入才累计 Redis 日计数(非幂等旁路,防重投重复累加);UpdateTaskStatus 按 task_id 覆盖幂等。
live 验证(复现原漏账场景):提交任务→立刻杀网关→dispatcher 跑完把 usage 发进持久流
(网关离线,usage_event=0 但流积压 1 条=钱没丢)→重启网关→自动补消费:任务 done、
usage_event 补上、公司A 余额扣 0.098、消费者 num_pending/ack_pending 归零。旧设计下这笔会永久丢失。
eval 回写仍 core NATS(仅观测,低价值,暂不改)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-09 17:07:31 +08:00
Blizzard
248267984c
feat(desktop): 顶栏租户切换器(多租户)—— 切换活跃租户即换工作区+计费
...
用户属多个租户时,顶栏显示租户下拉(Building2 图标):切换即调 POST /me/tenant
换活跃租户,随后刷新余额芯片(显示新计费租户的可花余额)。仅 >1 租户时出现。
api 加 myTenants/switchTenant。
live 验证(preview):demoB(属公司A+公司B) 切换器显两租户;从公司A(共享,余额15.84)
切到公司B(个人,余额6.38)→ 后端 active 持久化、芯片随之更新为 6.4(橙,偏低)。tsc+48 测试全过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 11:02:36 +08:00
Blizzard
b388915fff
feat(admin): 租户页加「共享计费」开关(每租户)
...
TenantRow 带 shared_billing;选定租户成员区上方加共享计费开关(PUT
/admin/tenants/:id/shared-billing):开=成员消耗计本租户池、关=各计个人池。
live 验证:公司A 开关随真实状态回显为勾选。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 10:56:21 +08:00
Blizzard
b98cf3c718
feat(gateway): 多租户增量2 —— 租户切换 + 每租户共享计费开关
...
用户可切活跃租户,消耗按"计费租户"扣(数据仍落活跃租户/工作区):
- User.active_tenant_id(切换持久化)+ Tenant.shared_billing(每租户开关,默认关)。
- 中间件 ActiveTenantForUser:active_tenant_id 若有效(仍是成员)则用之,否则默认租户。
- 计费目标解析 ResolveBillingTenantID:本人是活跃租户 owner 或该租户 shared_billing 开
→ 记活跃租户;否则记本人个人租户(各付各的)。SubmitTask 的硬拦截 + 用量都按它走。
- 接口:GET /me/tenants、POST /me/tenant(切换)、PUT /admin/tenants/:id/shared-billing;
/tenants/current 改显"可花余额"(计费租户) + billing_shared 标记。
live 验证(demoB 是公司A 成员、个人租户=公司B):切到公司A 后——
shared OFF:任务落公司A 工作区、用量扣公司B(个人),公司A 不变;
shared ON:用量扣公司A(共享),公司B 不变;/tenants/current 余额随之在 6.48↔15.92 切换。全过。
前端切换器 + admin 开关见后续。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 10:52:17 +08:00
Blizzard
aa10037511
feat(admin): 「租户 & 用户」页接真实成员管理(删掉 mock)
...
此前整页是硬编码 mock(假租户/假用户/假 API key/假日预算)。重写为真数据:
- 左:租户目录(listTenants:名称/slug/plan/成员数/积分余额)+ 建租户表单
(名称/slug/可选 owner 邮箱)。
- 右:选定租户成员表(listMembers)——按邮箱加成员+选角色、行内改角色、移除;
owner 受保护(不可改角色/移除)。api 加 listTenants/createTenant/listMembers/
addMember/setMemberRole/removeMember。
live 验证(preview):14 个真实租户带成员数/余额;UI 加 demoB→Dexter「2 成员」、
成员表 owner 保护 + 成员可改角色/移除、移除后回 1 成员,全过;tsc + 41 测试全过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 10:20:11 +08:00
Blizzard
0e47a827c0
feat(gateway): 多成员租户·成员管理后端 —— 租户目录 + 成员增改删
...
真·多租户地基(此前只有单人默认租户 + admin 页是 mock):
- store:ListTenants(含成员数+余额) / MyTenants / ListMembers(join user) /
AddMemberByEmail(邮箱归一化小写、幂等 upsert、复活已移除、未注册报友好错) /
SetMemberRole(禁改 owner、禁直接授 owner) / RemoveMember(软移除、禁移除 owner) / ValidRole。
- admin 接口:GET/POST /admin/tenants、GET/POST /admin/tenants/:id/members、
PUT/DELETE /admin/tenants/:id/members/:uid。
live 验证:加 demoB 入公司A→member、列表、改角色→admin、移除 owner 被拒、移除 member,全过。
admin 前端接线 + 租户切换 见后续。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 10:13:58 +08:00
Blizzard
a878e2dc89
feat(desktop): 「用量」视图 —— 我的积分余额/消耗趋势/最近消耗(P3 续)
...
给终端用户一个自己的计量观测面(守边界:只读自己租户):
- 新增 UsageView(左导航 MANAGE 组「用量」,Coins 图标):余额 hero 卡
(硬拦截+余额≤0→红/偏低→橙)、区间 KPI(积分/Token/成本/运行数)、
积分消耗按天趋势条形图(近7/近30天可切)、最近消耗明细表(任务/模型/token/积分/成本)。
读 /api/v1/me/usage。
- 顶栏积分余额芯片改为可点,进「用量」页(onOpenUsage → setView)。
live 验证(preview):视图显示 公司A 余额 15.92 + KPI + 趋势bar(落在实际消耗日) +
最近消耗 3 行真实数据;tsc + 48 测试全过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 09:31:12 +08:00
Blizzard
f6ea03b34b
feat(gateway): GET /me/usage —— 用户自己租户的用量明细(余额+趋势+最近消耗)
...
面向用户口径(非 admin,受插件按请求 ctx 租户自动隔离):返回积分余额 +
credit_enforce + 按天消耗趋势 + 区间合计 + 最近 10 条消耗明细。复用
UsageTrend/TenantBalance,新增 RecentUsage(按 tenant 取近 N 条 usage_event)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 09:31:12 +08:00
Blizzard
511ae199a4
feat(desktop): 顶栏积分余额芯片 + 提交遇余额不足透出友好文案(P3 租户感知)
...
让计费对终端用户可见(守 desktop/admin 边界:只读自己租户,不碰 admin 配置):
- TopBar 加积分余额芯片(Coins 图标):常态显余额;硬拦截开+余额≤0 → 红「余额不足」;
偏低 → 橙。tooltip 显租户名 + 是否拦截。api.tenantCurrent() 读 /tenants/current。
- App 登录后拉租户上下文,每 20s 轮询 + 运行结束即刷新(余额跟手)。
- submitTask 遇 402 解析后端 error 文案透出(如「租户积分余额不足,请充值后再试」),
在运行面板报错处显示,用户知道为何被拦。
live 验证(preview):芯片显示 公司A 余额 15.9 ✓;enforce 开+余额0 时 /tenants/current
标 enforce+余额0(芯片转「余额不足」)、提交 402 带友好文案 ✓;tsc + 48 测试全过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 09:18:30 +08:00
Blizzard
d5a4670c2f
feat(gateway): /tenants/current 带上积分余额 + 硬拦截开关(桌面端租户感知用)
...
面向用户自己的租户(非 admin 口径):返回 credit_balance_micro + credit_enforce,
供桌面端顶栏显余额、并据 enforce 判断是否会因余额不足被拦。复用已有 GetTenant/CreditEnforceEnabled。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 09:18:30 +08:00
Blizzard
b46382817c
feat(admin): 计费页加「余额硬拦截」开关 + 各租户「充值」按钮
...
闭合积分环的配置/操作端,接到 P4 后端:
- 计费规则区:加「余额硬拦截」开关(credit_enforce),与汇率合成一个「保存规则」。
- 用量观测·租户排行:每行加「充值」按钮(正=充值、负=校正),调 /admin/credits/grant,
充值后即时刷新余额。
- api:getBillingConfig/saveBillingConfig 带 credit_enforce;新增 grantCredits。
live 验证(preview):开关渲染+可存;点公司A「充值 10」→余额 5.92→15.92、账本落对租户;
tsc + 41 vitest 全过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 13:53:52 +08:00
Blizzard
93741a5504
feat(gateway): 闭合积分环 —— 充值/发放 + 提交前余额硬拦截(默认关)
...
让预付费积分成为闭环(发放→消耗→见底拦住):
- POST /admin/credits/grant:给租户充值/发放(正=grant,负=adjust 校正),
复用 store.GrantCredits(账本分录 + 物化余额,一事务)。
- 提交门控:credit_enforce 开启且租户余额≤0 → 拒绝新任务 402;默认关=软扣不拦。
开关入 billing-config(sundynix_setting KV,后台可切)。
- 修 bug:GrantCredits 是跨租户管理操作,须 store.WithoutTenant——否则 tenant 插件
会把账本分录的 tenant_id 覆盖成 admin 自己的租户(余额记目标、分录记 admin,破坏对账)。
live 验证:enforce 关→余额0可提交;开→余额0拒 402;充值后可提交、余额递减;
修复后 grant 分录落到目标租户、balance==SUM(ledger) 不变量成立。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 13:53:43 +08:00
Blizzard
11d321f218
feat(admin): 计费 & 用量合成一页 —— 顶部配规则、下方看结果(闭环)
...
按"配置和观测放一个页面"收口:删掉重复的「计价 & 预算」页(其预算区块是 mock),
把真·计价配置并入「计费 & 用量」,形成 规则→扣费→观测 一页闭环。
- 新组件 BillingRules:全局 token→积分汇率 + 每模型 单价/币种/积分权重(读写
/admin/pricing + /admin/billing-config),改完对后续任务实时生效。
- UsagePage:顶部「计费规则」(配置端) + 下方「用量观测」(按规则折算后的实际消耗)。
- 删 PricingPage + /pricing 路由;api 加 credit_weight / getBillingConfig / saveBillingConfig。
live 验证(preview):改汇率保存→持久化→新任务按新汇率扣→用量观测反映;
tsc + 41 vitest 全过;无 PricingPage 残引。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 13:26:33 +08:00
Blizzard
e2fc2d366c
feat(gateway): 计费规则可后台配置 —— token→积分汇率(DB) + 每模型积分权重
...
让「扣费按规则扣」的规则真正可后台热调(此前汇率是 env、积分权重无接口):
- sundynix_setting KV 表 + GetSetting/SetSetting;TokensPerCredit 改为
DB 设置优先 → env 回退 → 1000,SaveUsageEvent 按 ctx 读,改完即对后续任务生效。
- Pricing.credit_weight 纳入 UpsertPricing + ListPricing/SavePricing API。
- GET/PUT /admin/billing-config(token→积分汇率)。
live 验证:UI 存汇率=500 → billing-config/DB=500 → 新任务 credits_micro=214000
=107tok/500×1e6,规则→扣费联动精确成立。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 13:26:20 +08:00
Blizzard
54de95aa53
feat(admin): 用量 & 计费页 —— SaaS P2 计量观测面(读 /admin/usage)
...
管理端「分析」组新增页:全平台 / 单租户的 token · 积分 · 成本口径。
- api.adminUsage():拉 /admin/usage(含 trend / totals / tenants 排行 / 单租户余额);
微单位(×10⁻⁶)前端 ÷1e6 展示。
- UsagePage:租户下拉 + 近7/30天切换;KPI 卡(积分/Token/成本/余额或活跃租户);
积分消耗按天条形趋势(补零连续);全平台口径下各租户排行表(点名下钻单租户)。
- routes 注册 /usage(HashRouter)。
live 验证(preview):全平台两租户排行 + KPI + 趋势条渲染正确;点租户下钻→
单租户口径 + 当前余额卡;数值与后端自洽;无 console 错误;tsc + 41 vitest 全过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 12:38:33 +08:00
Blizzard
bf75bb8a62
feat(gateway): SaaS P2 计量·增量3 —— GET /admin/usage 用量/积分/成本口径
...
系统级(store.WithoutTenant,跨租户)用量观测:
- 无 tenant:全平台按天 SUM 趋势 + 各租户用量排行(含当前余额)。
- 有 tenant:该租户按天趋势 + 当前积分余额。
- from/to(YYYYMMDD)缺省近 30 天。UsageTrend/UsageByTenant/TenantBalance store 方法。
live 验证:两口径数值自洽(balance=seed−credits、credits=tok×汇率、totals=trend之和)。
至此 P2 计量后端(事实源)齐:明细/账本/余额/rollup/查询。前端页 + P4 硬拦截/充值待需求拉动。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 11:42:21 +08:00
Blizzard
d058a63434
feat(gateway): SaaS P2 计量·增量2 —— 积分账本 + 物化余额 + 用量 rollup
...
- credit_ledger(append-only):grant/usage/adjust 分录,扣量/充值的事实源。
- tenant.credit_balance_micro:物化余额(= 账本之和),用量扣、充值增。
- usage_rollup:租户/天 upsert 累加(配额/账单读一行,不扫明细)。
- 消费 usage_event 一个事务内:落明细 → applyUsageCredit(账本负分录+扣余额)
→ upsertRollup(累加)。幂等锚在 usage_event.task_id 唯一:RowsAffected==0(重投)
则跳过账本/余额/rollup,绝不重复计费。软扣:余额可为负(不拦,硬闸留 P4)。
- GrantCredits 充值/发放 API(记 grant 分录+增余额;增量3 admin 接)。
live 验证:ledger 负分录(ref=task_id) / 余额递减 / rollup 两任务累加(count=2,tok/credits求和)
/ **balance==seed+SUM(ledger) 不变量成立**(物化余额 == 账本真值)。
增量3(GET /admin/usage 趋势+余额)待做。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 11:26:27 +08:00
Blizzard
d032c198c8
feat: SaaS P2 计量·增量1 —— 用量按租户落持久明细 + 积分/成本折算
...
链路打通 tenant → 计费事实源:
- 契约:Task.Meta 加 MetaTenantID;UsageEvent 加 TenantID。
- 提交:网关 task.Meta[MetaTenantID]=tenantID(c);dispatcher emitUsage 带租户。
- 明细表 sundynix_usage_event(追加式,task_id 唯一→幂等防重投重复计费):
tenant/owner/model/tokens + credits_micro + cost_micros。
- 折算:credits=total_tok/TOKENS_PER_CREDIT×credit_weight(token 基准,设 1 即 token 直计);
cost 按 Pricing 折算;Pricing 加 credit_weight 列(每模型积分权重,缺省 1)。
模型名空则回退激活 chat 模型(近似,忽略 failover 备用模型,已在设计标注)。
- 网关 SubscribeUsage 折算落明细(保留 Redis 日计数作快速配额校验)。
live 验证:提交任务→一行 usage_event,tenant 匹配用户租户、
credits=89tok/1000×2×1e6=178000 微积分、cost=45/1000×1+44/1000×2=133000 微元 CNY,
折算数学与幂等键均正确。
设计见 SAAS_P2_DESIGN.md。增量2(credit_ledger 余额软扣 + rollup)待做。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 10:54:29 +08:00
Blizzard
22183cac51
docs: DEPTH_ROADMAP 增量2b 打勾(PG 核心表租户隔离闭合)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 10:18:55 +08:00
Blizzard
766eaab7a3
feat(gateway): 多租户2b-B —— Eval/Doc/DocLink 补 tenant_id(异步路径按源复制)
...
闭合 PG 核心表 tenant_id 全链路。这些表走 background ctx(NATS 回写 / 异步入库),
插件无请求租户可读,故在写入时按关联源显式补:
- Eval:SaveEval 从对应 task 复制 owner+tenant(+isTenantScoped)。
- Doc:SaveDoc 按 owner 查默认租户补 tenant(tenantIDForOwner 助手);覆盖路径不抹空。
- DocLink:ReplaceDocLinks 同法按 owner 补。
- BackfillRowTenants 扩到 doc/doc_link/task(owner→租户)+ eval(task join)。
live 验证:新任务→eval 复制 owner/tenant 与提交者一致 ✓;C 读己方 eval 有数据、
D 读同一 task eval 被租户过滤为空 ✓;异步入库的 note→doc 自动补对 tenant ✓;
vault C 见己方、D 空 ✓。存量回填:doc 47/51(4 条 owner 无租户=孤儿)。
至此 PG 核心表(KB/Agent/Task/Eval/Doc/DocLink)租户隔离闭合;
Redis/MinIO/Milvus/Neo4j 前缀留待 P1 后续。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 10:17:45 +08:00
Blizzard
ee1e9cfdca
feat(gateway): 多租户2b-A —— Task 加 owner/tenant 隔离 + 系统旁路修 admin 全局口径
...
- store.WithoutTenant(ctx):显式跨租户旁路,插件即使 ctx 带租户也不过滤。
修复增量2 引入的回归:admin SystemCounts 数 KB 时被 admin 自己租户误过滤。
AdminOverview 改用旁路 ctx → 任务/KB/Doc/Eval 恢复全平台口径。
- Task 加 TenantID(插件自动填) + Owner(提交者 user.id) + isTenantScoped()。
SaveTask 记录 owner;RecentTasks/RecentRuns 按 owner+租户过滤"我的运行"。
RecentRuns 是 raw Table 查询绕过插件,手动补 owner+tenant WHERE。
live 验证:A 提交任务 → 行 owner/tenant 自动填对 ✓;A 的 /runs 只见己方、
B 空、legacy 无 owner 行被排除 ✓;admin/overview tasks_total=39/kb_count=21
= DB 全局真值(旁路生效,回归已修)✓。
Eval/Doc/DocLink 的 tenant 传播见 2b-B。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 09:18:15 +08:00
Blizzard
30d667954b
feat(gateway): 多租户增量2 —— gorm 租户插件按上下文自动隔离查询(SaaS P1)
...
统一强制、别靠人肉:受租户模型标记 isTenantScoped() 后,store/tenant_scope.go
的 gorm 回调按请求 ctx 自动给查询加 WHERE tenant_id、创建自动填 tenant_id。
- KB / Agent 加 TenantID 字段 + isTenantScoped() 标记
- middleware.TenantContext 把 tenant 注入 request context 供 store 插件读取
- ctx 无租户(系统/回填/未登录)不过滤,保留跨租户操作能力
- 启动 BackfillRowTenants 回填存量行 tenant_id=owner 默认租户(幂等)
live 验证:创建自动写 tenant_id ✓;同 owner 不同 tenant 的行被查询过滤 ✓。
Doc/DocLink(异步入库)、Task/Eval(无 owner)留待增量2b。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 17:58:58 +08:00
Blizzard
f7d12cbf66
docs: DEPTH_ROADMAP T4.A 多租户增量1 打勾(租户身份地基)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 17:32:11 +08:00
Blizzard
693a8f09e9
feat(gateway): 多租户地基·增量1 —— 租户身份/成员/上下文(T4.A/SaaS P1)
...
按 SAAS_DESIGN.md P1 第一刀(真·多租户 + 桌面端为主):先立租户身份,不动查询。
- store.Tenant / TenantMember 表 + AutoMigrate
- store: CreateTenant / AddMember(幂等) / DefaultTenantForUser / EnsureDefaultTenant(幂等) /
BackfillDefaultTenants / GetTenant / MemberRole
- 注册即建单人默认租户(owner);启动回填给存量用户补建(幂等)
- middleware.TenantContext(挂 Auth 后)解析当前租户→注入 tenant_id;handler.tenantID(c) 助手
- GET /api/v1/tenants/current 验证端点(租户上下文 + 角色)
- live:存量用户(回填 7 租户)/tenants/current 返回默认租户+owner;新注册自动建租户
下一步(增量2):核心表加 tenant_id + 统一 gorm scope 强制隔离 + 存量行回填 + 重写查询。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 17:31:09 +08:00
Blizzard
2f19e322e3
docs: 多租户 SaaS 化设计(收敛版 SAAS_DESIGN.md)
...
前提:真·多租户商业化 + 桌面端仍是主产品(非 Web-first)。
对 GPT 重构方案的收敛替代:保留正确诊断(tenant_id前置/usage先落事件/subject规范/migration),
砍掉过度设计(Web重写/7服务拆分/Temporal/Remote MCP/独立Tool Policy)。
核心:保架构、穿一条多租户+计费脊柱、按需求拉动分阶段(P1地基tenant_id→P2用量→P3客户端租户化→P4enforcement→P5支付+硬化)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 16:47:11 +08:00
Blizzard
53f7e172c3
fix: KB 级联删「事务化」—— 失败不再留不可删孤儿(T4.F)
...
原为 best-effort:三库删失败只 log、MinIO 删错误全吞、PG 照删 → 删一半失败即在
向量/全文/图谱/MinIO 留下「PG 无记录、连 file_id 都查不到」的不可删孤儿。
改为「类事务」(跨库 2PC 不可行,退而求其次:不留不可恢复孤儿 + 失败可见可重试):
- milvus.deleteByFile / bleve.deleteDoc / blob.Delete 改返回 error(原 void 吞错)
- rag.DeleteDoc 三库全试一遍(最大化清理)+ 聚合错误(原只回 Neo4j 的错);三库删幂等
- gateway KbDeleteDoc 失败闭合:先删依赖存储(三库→MinIO)、PG 最后删;
任一存储删失败 → 不删 PG、返 502「未删除请重试」(保留 file_id 供幂等重试)
- 语义翻转:从「总能从列表删掉但留孤儿」→「有孤儿风险就不删、报错可重试」
live:杀 mcp-go→删→502+文档保留;mcp-go 活→删→200+清空(清场僵尸进程后验证)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 16:32:25 +08:00
Blizzard
55d50417a9
feat: 模型健康/熔断态 surface 到管理端(T4.F 可观测)
...
failover/熔断的运行时态原来只在 dispatcher 日志、admin 看不到 —— 本次接到概览可见:
- harness: CircuitBreaker.Snapshot() 只读观测访问器(state + fails,不动状态机)
- llm: Pool.ModelHealth() 上报主备链每模型 {provider,model,role,state,fails};
buildWithFallbacks 把模型名↔breaker 配对(同包直接读 failoverModel.breakers);
newFailoverModel 改返回具体类型以便读 breakers
- dispatcher 心跳 payload 加 models[]
- gateway /admin/overview 独立超时 Ping dispatcher,合并进 models.health
- admin 概览「模型路由」新增「运行时链路态(实时)」:逐模型状态点
(🟢 在线/🔴 熔断中+失败数/🟡 半开探测/单点)
- 单测:Snapshot、Pool.ModelHealth(名字↔态配对/单点/空)
- live:配坏主→提交任务打熔断→概览显示 broken-demo「熔断中·失败3」,备用在线
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 12:00:13 +08:00
Blizzard
6a258fc884
docs: 架构设计文档 ARCHITECTURE_DESIGN.md(当前完整版)
...
描述当前架构设计:分层总览/各组件/NATS 总线(5类通信)/Eino 编排引擎/
LLM接入(failover+熔断)/数据存储/关键数据流/安全治理/可观测/前端/部署配置/
当前能力矩阵(已建·进行中·规划)/技术栈。与 ARCHITECTURE_REVIEW(评审) 互补。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 10:25:42 +08:00
Blizzard
da04beadf5
docs: 架构评审报告 ARCHITECTURE_REVIEW.md
...
基于通读代码 + 真机验证的全栈架构评审:系统全景/核心决策取舍/关键数据流/
优缺点/技术债「税单」映射路线图/上生产前必修清单/总体判断。
一手依据:控制面热切换 live、failover+熔断 live、双 NATS 脑裂实测、审计误报核实。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 16:35:21 +08:00
Blizzard
c060000f64
docs: DEPTH_ROADMAP 记入「模型健康/熔断态 surface 到管理端」(T4.F 可观测)
...
failover demo 暴露的真实缺口:熔断/failover 运行时态只在 dispatcher 日志、admin UI 不可见。
待补:dispatcher 心跳上报每模型 breaker 态 → gateway 聚合 → 概览/状态页展示。后期一起做。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 14:29:24 +08:00
Blizzard
23b8fa5e8a
feat(gateway): 安全收口 —— CORS 生产收紧 + 限流按用户(T4.F)
...
- CORS:开发期缺省仍放行 *(便利);生产(APP_ENV=prod/GIN_MODE=release)未显式配
CORS_ALLOW_ORIGIN 则不发 ACAO 头(浏览器按同源拦截),逼运维显式配置允许的源
- 限流键改「已认证按 uid、未认证按 IP」:企业网多人共享出口 IP 不再互相拖累,
单用户换 IP 也绕不过;中间件顺序调整 Auth 前置于 RateLimit(否则取不到 uid)
- isProd() 判定与 middleware.RequireAdmin 同口径
- live 冒烟:登录/认证请求正常(重排未破链),dev CORS 仍 *
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 13:55:20 +08:00
Blizzard
6095bc71d2
refactor(mcp-go): 拆除 search.Hybrid 残骸(T4.F)
...
- search.Hybrid 空转(NewHybrid 返空、Query 返 nil TODO)、构造后存进 Gateway
却从不被调用(真实 RAG 走 rag.Engine)→ 纯误导性死重量
- 删 internal/search 包 + gateway.go 字段/构造参数 + main.go 接线
- build/vet 干净;wiki_search 不受影响(本就用 g.rag)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 13:39:14 +08:00
Blizzard
7c211719d2
fix(dispatcher): 熔断器接回 failover —— 挂掉的主模型跳过而非每次白试(T4.E 收官)
...
- failoverModel 加每模型熔断器(阈值3/冷却20s,比编排层更紧):
主模型持续失败达阈值 → 熔断 → 后续请求直接跳过主、直连备用(省掉每次白试主的失败往返);
冷却到点半开放行探测打回主,成功即自动恢复走主(靠熔断器半开机制,无需外部通知)
- 全部模型都熔断时强制试主兜底(编排层 o.breaker 兜"全挂")
- WithTools 重包共享同一批 breakers(状态不清零)——否则每次 rewrap 熔断失效,关键坑
- harness 加 NewCircuitBreakerWith(threshold,cooldown,halfOpenMax) 参数化构造
- Generate/Stream 用泛型 runFailover 共用选路循环(去重)
- 3 新单测:熔断跳过主/WithTools 共享熔断状态/冷却后半开恢复(全三态)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 10:49:54 +08:00
Blizzard
65e939889e
feat(gateway): DSL 拓扑校验 —— 前置拦截坏图(T4.E)
...
- ParseAndAssemble 加 validateTopology:拦重复/空节点 id + 悬挂边
(source/target 指向不存在节点),避免坏图进编排后被 compose 静默跳过
- 保守策略:空图 / 报告任务 {topic} / 非标准载荷一律宽松放过,不误伤合法提交
- 单测覆盖合法/重复id/空id/悬挂边×2/空端点/放过场景
- live:悬挂边 POST /tasks → 422(错误指明具体边与缺失节点);合法图 → 202
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 10:14:31 +08:00
Blizzard
4bec95fde1
fix(dispatcher): Branch else 兜底 + 选路留痕(T4.E)
...
- branchNode 识别 default/else 边:主选择(true/false 或边序)未命中任何下游时,
走显式 default 边而非悄悄落 END
- trace 明确区分「走 default / 未匹配收口结束(END) / 正常选路」,
杜绝静默 fall-through 被误当 bug
- compose 层原有 len(chosen)==0 → END 收口保留(无 default 时的安全兜底)
- 3 断言单测:default 命中 / 条件真走 true / 无 default 未命中仍返回空
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 10:08:05 +08:00
Blizzard
f9c849b14b
fix(dispatcher): 多智能体专家超时 —— 卡死专家不再拖垮协调(T4.E)
...
- specialistTool 加 timeout 字段(默认 specialistTimeout=3min,专家可多轮 react+工具故给宽)
- InvokableRun 用 WithTimeout 包裹专家派发;超时(DeadlineExceeded)作为"观察"
跳过该专家(err=nil),lead 据其余专家继续综合,不中断整个协调
- 2 单测:卡死专家 ~50ms 跳过并返回超时观察 / 正常专家不受影响
- timeout=0 时不包裹(向后兼容既有 specialistTool 构造)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 10:03:29 +08:00
Blizzard
de36ed4cb3
fix(dispatcher): Map 节点错误传播 —— 并行子项失败不再静默(T4.E)
...
- writeSection 返回 (body, error):仅真·LLM 调用失败返 err;预算触顶/模型未配置
是主动降级(可见降级正文,err=nil)不计失败
- writeSections 返回 ([]section, failed):失败项 Body 带可见「撰写失败」标记 + 汇总失败数
- mapNode:全部子项失败 → 置 b.fatalErr(任务判 failed 而非静默 done-空);
部分失败 → trace span + 流式 ⚠️ 告警
- report handleReport:部分章节失败时流式提示,不再当全成功
- 3 单测:全失败/部分精确计数(=1 非 all-or-nothing)/mapNode 置 fatalErr
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 09:56:26 +08:00
Blizzard
a830ae2a04
feat(admin): 审计 & 安全事件页(T4.B 收尾)
...
- 新增「审计 & 安全」页(/audit,运维组):接 /admin/audit + /admin/guardrail-events
- 安全事件:护栏拦截/灰区放行(kind 徽标 + 原因 + method/path + actor/ip/时间)
- 操作审计:变更操作(方法配色徽标 + 路径 + 状态码着色 + actor/ip/时间)
- 顶栏统计(操作留痕/护栏拦截/灰区) + 30s 自刷 + 手动刷新
- api.ts 增 listAudit / listGuardrailEvents + 类型
- 风格对齐重做后的服务状态页(中性克制)
- T4.B 整组完成(剩 HITL 审批明细可选小项)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 09:43:41 +08:00
Blizzard
9e43d07428
feat(gateway): 护栏拦截事件落库 + 安全事件流(T4.B)
...
- store.GuardrailEvent 表(sundynix_guardrail_event) + AppendGuardrailEvent/ListGuardrailEvents
- middleware.Guardrail(db):命中 blocked/suspect 时 best-effort 落库
(actor/kind/reason/signals/method/path/ip,独立超时 ctx)
- GET /api/v1/admin/guardrail-events:安全事件流(倒序,翻页)
- store.clampPage 抽出分页归一(audit/guardrail 共用)
- live:注入 "ignore all previous instructions" → 422 硬拦 + 事件留痕(kind=blocked)
- DEPTH_ROADMAP T4.B 护栏事件打勾
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 09:31:31 +08:00
Blizzard
16c67dcb4f
feat(gateway): 敏感操作审计日志(T4.B)
...
- store.AuditLog 表(sundynix_audit_log) + AppendAudit/ListAudit
- middleware.Audit(db):只审计变更类(POST/PUT/DELETE/PATCH),收尾 best-effort
落库(独立超时 ctx,失败静默不拖垮主流程);挂管理组 + prompt 激活/撤销 + HITL 审批
- GET /api/v1/admin/audit:倒序审计流(limit/offset 翻页)
- live:PUT pricing / POST prompts/deactivate 留痕(actor/path/status/ip),GET 不记
- DEPTH_ROADMAP T4.B:overview✅ + audit✅ 打勾
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 09:19:52 +08:00
Blizzard
30e80c0eed
feat(admin): 提示词控制面页 + 概览/状态页重做 + 接入 vitest
...
- 新增「提示词」页(/prompts):接 /api/v1/prompts,建版本/激活热下发/
对比激活版行级 diff/撤销,挂到配置组
- 概览页重做为系统控制塔:吃 admin/overview(全平台口径)——平台任务/
评测/用户规模/服务在线 + 模型路由态 + 提示词覆盖态 + 健康拓扑 + 30s自刷
- 服务状态页重做:中性克制风 + 请求链路做成深色实时数据管道(流动光点/
节点辉光) + MCP 工具改能力域配色小卡片
- 接入 vitest(此前零测试):41 单测(api 控制面/lib diff/路由派生)
- api.ts 增 statsOverview/adminOverview/prompt 控制面四接口 + groupPrompts
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 08:59:40 +08:00
Blizzard
e2104efc64
feat(gateway): admin/overview 系统级聚合接口 + Tier4 后端路线图
...
- GET /api/v1/admin/overview (RequireAdmin):全平台口径聚合——
用户/KB/文档总数 + 全局任务(今日/累计/趋势/终态) + 全局评测均分
+ 模型配置态(主/备/各kind数) + 提示词覆盖态 + 服务健康
- store.SystemCounts:全局 users/kb/doc 计数(区别 owner 隔离的个人口径)
- 区别桌面端个人 stats/overview:管理端概览改吃此接口(系统控制塔)
- DEPTH_ROADMAP 新增 Tier4 后端做实(A–F 六组,三路 Explore 审计产出)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 08:59:28 +08:00
Blizzard
cb7dd86121
docs: DEPTH_ROADMAP T2.3 ✅ + T2 深化 4/4 全清(prompt DB 控制面热切换,live)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 14:59:52 +08:00
Blizzard
5736ad145e
feat(prompts): v2 DB 控制面热切换 —— 版本留存 + 不重启即生效
...
在 v1(注册表+文件覆盖)上加 DB 管理层与热切换,镜像 model-config 控制面:
- store: sundynix_prompt 表(key/version/content/active) + ActivePrompts/ListPrompts/
CreateVersion/Activate/Deactivate
- 控制面: ServePrompts/RequestActivePrompts(+Retry)/PublishPromptsUpdated/SubscribePromptsUpdated;
prompts.ApplyOverrides 整体替换覆盖集(DB 激活集为权威)
- gateway API: GET/POST /api/v1/prompts、version/activate/deactivate;激活/撤销即广播
- dispatcher/mcp-go: 启动拉激活集 + 订阅热更新(不重启)
- live: 建版本→激活→mcp-go 图谱抽取 2→0→回滚 2(全程不重启);deactivate 回退代码默认;版本可回溯
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 14:59:52 +08:00
Blizzard
aa61590b6c
Merge remote-tracking branch 'origin/dev' into dev
2026-06-30 14:37:07 +08:00
Blizzard
16c9298cf8
docs: DEPTH_ROADMAP T2.3 prompt 版本化地基 ✅ (文件覆盖,live A/B),v2待
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 14:36:04 +08:00
Blizzard
9a3a816c80
feat(prompts): prompt 版本化地基 —— 注册表 + 运行期文件覆盖
...
把散落各服务的硬编码 system prompt 收口为受管注册表,不重编译即可改/回滚/对比:
- shared/prompts:内置默认(随代码) + 运行期覆盖(PROMPTS_FILE) + Get/Keys,并发安全,含单测
- 接入 9 处:mcp-go(graph.extract);dispatcher(eval.quality/eval.refine/guard.jailbreak/
coordinator.lead/memory.extract,按引用登记默认、无文本重复)
- main 启动调 LoadFile 加载 PROMPTS_FILE 覆盖
- live A/B:覆盖 graph.extract → 图谱抽取 2 条→0 条、向量仍正常(覆盖生效、管道未坏)
- v2(DB 控制面热切换 + 灰度)留后续
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 14:36:04 +08:00
Blizzard
865a47eeea
docs: DEPTH_ROADMAP T2.2 ✅ 检索质量评测(混合稳健兜底,量化在案)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 13:56:39 +08:00
Blizzard
a17e25b6ba
feat(rag): 检索质量评测 —— 单路检索能力 + recall@k/MRR 评测台
...
- 引擎抽 searchPaths(三路召回) + SearchByMode(vector/fulltext/graph/hybrid,
纯检索不 rerank,公平对比);kb_search 加 mode 参数(空=生产含rerank),
gateway KbSearch 透传 mode
- scripts/rageval.py:标注语料+查询 → 四模式 recall@k/MRR 对比表(可复用)
- live 量化:纯语义改写让全文0.88/图谱0.75 漏召回,混合 1.00 兜回,
混合=各路上界的稳健组合
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 13:56:39 +08:00