Files
OpenFlare/docs/superpowers/specs/2026-08-30-openflare-cordis-alignment-design.md
T
ryan 823bec1272 docs(cordis): 记录与 Wavelet 对齐的 Cordis 设计
明确框架与业务边界、Wavelet 通用扩展点、stamp 升级路径、
金标准库验证,以及以 git merge 接入上游且不改写历史。
2026-08-30 10:26:37 +08:00

17 KiB
Raw Blame History

OpenFlare Cordis 对齐设计

  • 文档状态: 已敲定 (Approved)
  • 版本: v1.0.0 (2026-08-30)
  • 范围: 后端。禁止修改前端源码。禁止修改金标准树。
  • 取代: 2026-08-29-openflare-cordis-refactor-design.md 中尚未落地或与本文冲突的部分(装配旁路、server 内平台副本、rsync 长期同步、在 server 上挂 health/self/cap 别名)。
  • Wavelet: /Users/ryan/Code/Go/Wavelet
  • OpenFlare Cordis 工作树: /Users/ryan/Code/Go/OpenFlare-cordis(分支 cordis)
  • 金标准(未改造旧框架): /Users/ryan/Code/Go/OpenFlare @ 9f79fb99(v3.5.4,与工作树 main 同 SHA)

1. 目标

OpenFlare 是 Wavelet 的下游产品。框架层与 Wavelet 一致,便于此后用 git merge wavelet/main 吸收上游。OpenFlare 只做 OpenFlare 业务,不再承担与 Wavelet 同类的职责。

Wavelet 提供框架与平台能力(微内核、infra、drivers、auth/admin/user/cap/upload/message_gateway/risk_control/system)。缺能力时改 Wavelet,且必须通用,禁止把 OpenFlare 业务写进框架。


2. 决策记录

# 决策 理由
D1 前端 HTTP 契约冻结 禁止改 frontend/。路径可超集,不可删改金标准已有 path+method 的响应形状。
D2 同一轮删除全部平台副本 cap / admin / infra / oauth / user / upload / config / health / admin/push / w_* model+repository。留两套会双注册路由。
D3 完整 stamp 桥接 现网 goose_db_version 76 链不重跑;新装走 Wavelet 每插件迁移 + of_* initial。
D4 兼容靠 Wavelet 补通用能力,不靠 server 包装同类 HTTP health / user/self / cap 路径差由拥有该功能的 Wavelet 插件多挂入口。
D5 Wavelet 作为 git 上游,merge 不改写历史 接线后废弃 rsync。禁止 rebase / filter-repo / force-push OpenFlare。
D6 OpenFlare 不承担同类职责 同一张 w_* 表、同一类鉴权/用户/验证码/上传/管理端/推送/任务/配置/健康检查/HTTP 引擎/关系库 migrator,均属 Wavelet。
D7 金标准树只读 /Users/ryan/Code/Go/OpenFlare @ 9f79fb99 零改动。升级与对拍以它现跑现导为准。
D8 先对齐代码,再第一次 unrelated merge 半成品 merge 会把分叉和 Wavelet 前端搅进 OpenFlare。

3. 职责铁律

判定「同类职责」:Wavelet 已经覆盖的平台能力,OpenFlare 不得再实现一份,也不得在 server 里用别名/包装再挂一套同类 HTTP。缺口(路径、中间件、贡献点)→ 改 Wavelet。

OpenFlare 只拥有:of_*、边缘协议、节点/Pages/WAF/TLS/Cloudflare 等产品逻辑;以及通过契约 注册 自己的事件与公共配置贡献(不是再写一套推送或配置服务)。


4. 目标结构与装配根

单 module Wavelet,与上游同构:

backend/
├── core/  pkg/  plugins/     【Wavelet】merge 进来,OpenFlare 不留私补丁
├── cmd/                      【Wavelet 骨架 + OpenFlare 持久 diff】
│   ├── app.go                即 Wavelet newWaveletApp,多 app.Use(server.New())
│   ├── api.go / worker.go / scheduler.go / all.go / root.go
│   └── {agent,relay,flared}/ OpenFlare 额外守护进程
├── main.go
└── OpenFlare/                占据上游 downstream/ 的位置
    ├── plugins/server        只留 OpenFlare 业务
    ├── plugins/{agent,relay,flared}
    └── share/

控制面 newOpenFlareApp(profile) 的 app.Use 顺序与 Wavelet newWaveletApp 相同:

  1. infra:database、logger、storage
  2. cache / cache_memory,asynq / inproc worker+cron(配置门控不变)
  3. domain:admin、user、auth、message_gateway、risk_control、upload、cap、system
  4. OpenFlare/plugins/server.New()
  5. SetMigrationEngine(Wavelet 那份 w_schema_versions goose 引擎,不复制第二套)
  6. core.WithMigrationBaseline(stampFn):stamp 函数本体在 OpenFlare/ 内,装配根只传入回调。与 server.New() 同属 cmd 允许的持久 diff。
  7. driver_http.New(),不再 WithEngine(router.BuildEngine())

api / worker / scheduler / all 全部 runProfileApp(profile)。删除:

  • bootstrap.RegisterAPI / Init 进程级副作用链
  • Cobra PreRun 本地 migrator.Migrate()
  • all.go 自行拉起 worker.StartWorker / scheduler.StartScheduler
  • 全局 infra/config.Config 单例;host 配置抄 Wavelet root.go 的 config.NewSource() + hostConfig

cmd 允许的持久 diff 仅:app.Use(server.New())、WithMigrationBaseline(stampFn)、agent/relay/flared 子命令、swagger 标题。禁止在 cmd 里自己拉起 DB/worker/migrator,禁止在 cmd 里实现 stamp 细节。

agent / relay / flared 入口已经是 core.NewApp + 单插件,本轮不改 CLI 形态。


5. Wavelet 通用扩展点(W1–W9)

先在 /Users/ryan/Code/Go/Wavelet 落地并进入其主线,再进入 OpenFlare。接口与实现禁止出现 OpenFlare / of_ / 边缘节点等业务词。

W1. HandleRaw

Handle() 经 cleanPath 会剥尾斜杠。需要 HandleRaw(保留尾斜杠)与 BasePath()。本地补丁已在 OpenFlare upstream-patches.md 与 Wavelet 分支 feat/cordis-router-raw-routes。合并进 Wavelet main 后清空补丁清单。

W2. contracts.CaptchaService

Wavelet 已有 cap.login_enabled,但 user 的 login/register/send-email-code 未挂验证码。跨插件禁止 import cap。

type CaptchaService interface {
    VerifyMiddleware(scope string) any // gin.HandlerFunc
    ChallengeHandler() any
    RedeemHandler() any
}

cap.Apply 中 Provide;user 在上述三路由上 Inject,有实现则包一层,没有则保持裸路由。cap 同时再挂 POST /api/cap/{challenge,redeem}(原 /api/v1/cap/* 保留)。pow 留在 cap 内,不抽到 pkg/pow。

W3. 可选 PublicConfigProvider

GET /api/v1/config/public 路径相同。Wavelet 默认 {configs, app};金标准前端消费扁平 map[string]string。

type PublicConfigProvider interface {
    PublicConfig(ctx context.Context) (any, error)
}

system 在 handler 里 Inject:有 provider 用其 payload,没有走默认。OpenFlare server Provide 扁平 map。默认 Wavelet 行为不变。

W4. driver_http 引擎选项

已有 Recovery / CORS / session / otelgin / 错误处理,以及 embed_frontend 的 SPA NoRoute。再增加配置项控制 RedirectTrailingSlash(默认 true,Wavelet 行为不变)。OpenFlare 通过配置关闭尾斜杠重定向。前端静态资源走 Wavelet embed_frontend(Makefile 拷贝目标改到 driver_http/dist,不改 frontend/ 源码)。去掉 WithEngine 旁路。

W5. upload 补挂已有 handler

ListMyFiles / UpdateMyFile / DownloadFile 已实现,测试已用 GET /my,但 plugin.go 未全部挂到用户组。在 /api/v1/upload 用户组补上 GET /my、PUT /:id、GET /download/:id、POST /download/batch(只增不改)。原 GET ""、POST /batch-download 保留。

W6. contracts.PushRegistry

RegisterBuiltInEvent / SyncEvents 已在 message_gateway 存在,但是包级函数。收到契约上,下游只 Inject 注册自己的事件,禁止 import 域插件。

W7. system 增加 GET /api/health

与现有 /healthz、/api/healthz 同一 handler。金标准调用 /api/health。响应保持金标准 {error_msg, data: null} 信封(response.OKNil()),不得只返回 {status: ok} 却让金标准路径变成另一种 JSON。若 /healthz 维持 Wavelet 原 JSON,/api/health 必须是金标准形状。

W8. user 增加 GET /api/v1/user/self

内部 AuthService.GetCurrentUser。鉴权中间件与其它需登录的 user 路由相同。

W9. Migration Baseline 钩子

MigrationEngine 在创建 w_schema_versions 之后、Up 之前调用可选 Baseline 回调(core.WithMigrationBaseline)。任何有历史库的下游可用。引擎接口禁止出现产品名。OpenFlare 的 stamp 实现放在 OpenFlare/ 内,由 cmd 传入。并发保护(PG advisory lock / sqlite 单写者)加在 Wavelet 引擎上,不在 OpenFlare 再写迁移框架。


6. 清理后的 server 边界

删除(改走上游)

现路径 改走
admin/(除 updater) plugins/domain/admin
oauth/ plugins/domain/auth
cap/、pkg/cap/ plugins/domain/cap + CaptchaService
user/ plugins/domain/user
upload/ plugins/domain/upload
config/ system + PublicConfigProvider
health/ Wavelet system 的 GET /api/health
admin/push/、pkg/push/ message_gateway + PushRegistry
infra/config、persistence、task、objectstore、diskcache、idgen 上游 infra / drivers / pkg/idgen
infra/persistence/migrator(关系库引擎与 76 链执行) Wavelet 引擎 + stamp
model / repository 中所有 w_* 对应上游域插件
listener/ ctx.Events().On
platform/bootstrap 的任务/推送/进程初始化 各插件 Apply + 上游驱动
ClickHouse 链中的 w_user_access_logs risk_control

删除后禁止再 import 这些包。业务只通过 core/contracts、ctx.Config()、ctx.Events()、ctx.Task()、ctx.Router() 协作。

保留

  • openflare/(zone、node、origin、pages、waf、tls、cloudflare、agent/relay/flared 接入、websocket、dashboard、option 等)
  • of_* 的 model / repository(含节点访问日志、可观测性、chwriter)
  • admin/updater(三段式 Prepare/Apply/Finish;上游只有 GetUpdateStatus/ApplyUpdate)
  • integration/githubrelease(仅 server 使用则留 server;多插件再用再进 share/)
  • ClickHouse of_node_* 链(不并入 w_schema_versions)
  • share/(protocol、wsclient、geoip、edge;本轮不搬)

server.Apply 不注册 /api/health、/api/v1/user/self、/api/cap/*。这些由 Wavelet 插件声明。

Apply 职责:绑定 OpenFlare 自有配置;注册 of_* 迁移;Provide PublicConfigProvider;Inject 契约并注册 of_* 业务路由/任务/设置;经 PushRegistry 注册 OpenFlare 自己的事件。拆掉 router/v1 里把平台路由和业务编在一起的接线。SPA 兜底交给 driver_http + embed_frontend。


7. 迁移与 stamp

OpenFlare 不再自建关系库 migrator。迁移即 Wavelet cmd/app.go 的 w_schema_versions + 每插件 goose。

表所有权

表 谁建、谁改
w_* Wavelet 各 domain migrations/
of_* OpenFlare/plugins/server/migrations/{postgres,sqlite}
w_schema_versions Wavelet 引擎
ClickHouse of_node_* server 自有 CH 链
ClickHouse w_user_access_logs Wavelet risk_control

76 条历史 SQL 混着 w_* 与 of_*,停止执行。仅暂时留作与金标准 schema 对拍的夹具;门禁通过后可靠 git 历史,不再注册到引擎。

新装

  1. Wavelet 各插件 00001_initial(CREATE IF NOT EXISTS / seed ON CONFLICT DO NOTHING)建 w_*,并补金标准没有的 w_message_channels / w_message_bindings / w_message_pairing_codes。
  2. server 只含 of_* 的 00001_initial(当前 schema 压缩版,postgres/sqlite 文件名与版本号对齐),全部 IF NOT EXISTS。
  3. 禁止 DROP TABLE / DROP COLUMN / TRUNCATE。

升级(金标准库:已有 goose_db_version,最大版本 202608090003)

Baseline 回调(幂等,ON CONFLICT DO NOTHING):

  1. 按与 Wavelet 逐字相同的 DDL 确保 w_schema_versions 存在。
  2. 写入 (openflare/legacy, 0) 与 (openflare/legacy, MAX(goose_db_version)),76 条历史不再跑。
  3. 写入 (server, 1),压缩版 of_* initial 视为已应用。
  4. 不 stamp Wavelet 插件,让其 00001 以 IF NOT EXISTS 补缺口表。
  5. 仅当老库版本落在 [202607120002, 202607130001) 时调用 zone.ImportLegacyTx。金标准已在 202608090003,此步为空操作。
  6. ClickHouse 仍走 goose_clickhouse_version,只保留 of_node_*。

删除 infra/persistence/migrator 整包(关系库 Migrate()、Cobra PreRun、进程级 goose)。CH 升级保留为 server 内小函数,不是第二套通用 migrator。


8. Git 上游

两边目前没有共同祖先(OpenFlare 从 init 起 1064 提交;Wavelet 根提交为历史压缩)。禁止 rebase / filter-repo / force-push OpenFlare。

路径 所有权 merge
backend/{core,pkg,plugins} Wavelet 三路合并,无私补丁
backend/cmd、backend/main.go Wavelet 骨架 + 第 4 节所述 diff 三路合并
backend/OpenFlare/ OpenFlare merge=ours
frontend/ OpenFlare merge=ours
docs/changelog、docs/superpowers OpenFlare merge=ours

.gitattributes:

backend/OpenFlare/**    merge=ours
frontend/**             merge=ours
docs/changelog/**       merge=ours
docs/superpowers/**     merge=ours

接线(对齐完成且第 9 节三层门禁全绿之后):

git remote add wavelet <Wavelet.git>
git fetch wavelet
git merge --allow-unrelated-histories wavelet/main   # 仅此一次
# 框架目录以 Wavelet 为准;ours 路径保持 OpenFlare

此后:git fetch wavelet && git merge wavelet/main。删除 scripts/sync-upstream.sh。接线前为导入 W1–W9 可使用一次 rsync。接线后 OpenFlare 不得再改 core/pkg/plugins。

成功判据:git merge-base HEAD wavelet/main 非空;git log --first-parent 仍是 OpenFlare 提交链;frontend/ 与 backend/OpenFlare/ 的历史 SHA 不被重写。


9. 验证门禁

金标准目录 /Users/ryan/Code/Go/OpenFlare @ 9f79fb99,本轮零改动。Cordis 仓只读它。

L1 — 金标准自身绿

在金标准树:go test ./... 全过。失败则停。

L2 — Cordis 仓绿

在 /Users/ryan/Code/Go/OpenFlare-cordis:

  • go test ./...
  • Wavelet 仓 go test ./...(W1–W9 落地后)
  • make swagger:金标准 swagger 的 path+method 一条不删、形状不改;允许超集
  • 路由:金标准引擎表 ⊆ 完整 newOpenFlareApp 路由表。plugin_parity_test 必须对完整装配断言子集,不再只 server.New().Apply,不再要求精确相等
  • git diff --name-only -- frontend/ 为空

金标准必须包含且不得丢失的路径:GET /api/health、GET /api/v1/user/self、POST /api/cap/challenge、POST /api/cap/redeem、GET /api/v1/config/public(扁平 map)。

docs/superpowers/specs/baseline/* 必须与金标准二进制 现跑现导 一致;不一致以金标准为准改夹具。

L3 — 从金标准库升级

用金标准二进制在空库跑完 76 链得库 A(sqlite 必做,Postgres 必做):

  1. 记录 goose_db_version 最大版本(202608090003)、表清单、of_* / w_* 行数抽样。
  2. 同一份库文件启动 Cordis api。
  3. 断言:已有表不丢列、不丢行;76 链不重跑;w_schema_versions 含 openflare/legacy 与 server;允许补 w_message_*;对升级后进程请求金标准关键 API,HTTP 与 JSON 形状与金标准一致。
路径 断言
A 金标准二进制 + 76 链 与现导 schema 一致
B Cordis 空库新装 of_* 与 A 一致;w_* 允许 Wavelet 多表,禁止少列、禁止改已有列
C 恢复 A 再启 Cordis 数据仍在;关系库无重放历史

升级路径必须是 go test(夹具来自金标准迁移产物或测试内生成),禁止只手测一次。

B/C 失败即改造未完成。只测空库新装、未测金标准升级,算失败。改了金标准树,算失败。


10. 落地顺序

  1. Wavelet 落地 W1–W9,go test ./... 绿。
  2. OpenFlare-cordis 同步 core/pkg/plugins(接线前可用一次 sync-upstream.sh),与 Wavelet 零差,清空 upstream-patches.md。
  3. 装配根同构:runProfileApp + server.New();删除 bootstrap / 本地 migrator / WithEngine。
  4. 删除平台副本;server 收敛到第 6 节边界。
  5. stamp + of_* 压缩 initial;删除关系库 migrator 包。
  6. 第 9 节 L1/L2/L3 全绿。
  7. 第一次 git merge --allow-unrelated-histories wavelet/main;此后只 merge。

11. 明确不做

  • 修改 /Users/ryan/Code/Go/OpenFlare
  • 修改 frontend/ 源码
  • 在 OpenFlare 实现鉴权/用户/验证码/上传/管理端(updater 除外)/推送实现/健康检查/HTTP 引擎/关系库 migrator
  • 在 server 再挂 /api/health、/api/v1/user/self、/api/cap/*
  • 把 pow 抽到 pkg/pow
  • 把 OpenFlare 业务写进 Wavelet
  • rebase / filter-repo / force-push 改写 OpenFlare 历史
  • 本轮改变 agent/relay/flared 的 CLI 形态
  • 把 ClickHouse of_node_* 并进 w_schema_versions

12. 完成定义

以下同时成立才算完成:

  • 金标准库能升到 Cordis,数据还在,76 链不重跑
  • 金标准路由 ⊆ Cordis 路由;GET /api/v1/config/public 仍为扁平 map
  • 金标准 /Users/ryan/Code/Go/OpenFlare 上 go test ./... 绿
  • Cordis go test ./... 绿;Wavelet go test ./... 绿
  • frontend/ 无 diff
  • 第 7 步之后 git merge-base HEAD wavelet/main 非空;git log --first-parent 仍是 OpenFlare 提交