M0 实施与验收
# M0 实施与验收
2026-10-22,时区 Asia/Shanghai。返回总览。本页区分工程完成、PoC 观察和生产认证;以实际运行记录为依据。
# 交付范围
本轮交付 M0 工程底座、合同和风险 PoC:后端 fago/、前端 fago-vben5/、文档站 fago-doc/。Go 使用用户要求的 1.27 系列,固定官方稳定补丁 1.27.3。新增根目录 AGENTS.md,要求开发时编写详细中文注释,说明关键规则、事务、并发、错误和失败恢复。
运行服务只实现 GET /api/health/live、GET /api/health/ready。OpenAPI 中其他接口属于 M1–M3 冻结合同,访问仍返回 404;没有演示登录、虚构权限或默认管理员密码。worker 可独立启动并优雅退出,当前没有注册生产任务。
| 工作包 | 交付与验证 | 边界 |
|---|---|---|
| A01 基线 | Vben 固定提交、原始/派生锁文件、MIT 许可证、能力参考提交 SHA;工具链和 UI 固定 | 该参考仅作能力对照,未搬入 Java 源码 |
| A02 多模块 | 13 个 go.mod、依赖 DAG、配置、JSON 日志、脱敏错误、存活/就绪、SIGTERM | 本地 module namespace 未发布;登录等业务后续实现 |
| A03 数据与初始化 | MySQL/Redis Compose;MySQL/PostgreSQL 迁移、校验和/dirty、显式 bootstrap | 本机 Docker API 未响应,Compose 配置校验通过;实际数据库验证使用本机独立测试实例 |
| A04 前端 | Vben 共享依赖闭包、Ant Design Vue、静态路由、同域代理和真实健康 API | 上游 mock 应用、统计脚本和示例业务页已裁剪 |
| A05 合同 | OpenAPI 3.0.3,69 个操作,身份/用户/菜单/组织/平台、分页、权限、成功/失败样例、生成 TS 类型 | Go DTO 手写并测试实际 health;其余接口没有伪装成已实现 |
| A06 质量入口 | workspace race/vet、13 模块 GOWORK=off 独立测试、两租户 fixture、GitHub Actions | 无远程仓库,本轮只执行本机检查,未声称 GitHub 云端 CI 已运行 |
| A07 数据库风险 | MySQL/PostgreSQL 实测;Oracle/SQL Server 独立纯 Go 探针构建;DM 驱动和部署约束有结论 | 用户确认没有三种商业库实例;Oracle/SQL Server/DM 均未认证 |
| A08 SSO/流程 | 真实 Dex 双客户端协议探测;双数据库持久化、并发、幂等、fencing、跨进程恢复、双资源适配器 | Dex 本地密码候选淘汰;工作流为有边界 PoC,不能称为完整 SSO/Flowable 替代 |
M0 退出演示为:浏览器访问工作台 → 真 Go 就绪接口成功 → 独立迁移和 bootstrap 准备身份数据。初始化成功后仍没有 M1 登录能力。
# 工程与依赖
当前 13 个独立模块:
fago/
go.work
apps/fago-server/
cmd/{api,worker,migrate,bootstrap}/
modules/
fago-contracts/
fago-framework/
fago-identity/
fago-tenancy/
fago-system/
fago-workflow-core/
fago-workflow-resource/
adapters/database/{fago-mysql,fago-postgres}/
fago-migrations/{mysql,postgres}/
poc/{fago-database,fago-oidc}/
fago-vben5/
apps/web-antd/src/
packages/ internal/
pnpm-workspace.yaml
pnpm-lock.yaml
模块目录与 module path 的模块段使用 fago- 前缀,包名使用 Go 合法标识符 fago 前缀,如 fagocontracts、fagoworkflow;命令保留语言规定的 main 包。未创建的 authorization/infra/bpm/approval 等属于未来模块,不能因架构树列出而标记实现。
无 Git remote,因此明确使用 fago.local/fago 本地命名空间。当前统一开发 train 为 v0.1.0,模块依赖在各 go.mod 显式声明;临时本地 module proxy 由源码生成,独立测试在复制的模块和新缓存中执行,不添加隐藏的工作区 replace。它不是公网版本发布。正式发布前必须统一迁移为实际仓库 module path 并打对应模块 tag。
精确依赖由 go.mod/go.sum、派生 pnpm 锁文件、baselines/sources.json 和 baselines/modules.json 记录。原始 Vben 锁文件保存在 baselines/vben-pnpm-lock.yaml,上游许可证在 fago-vben5/LICENSE。前端保留共享 package 依赖闭包,只裁剪其他应用、mock、演示页面和文档站。
# 配置、迁移与初始化
命令从仓库根目录运行,完整启动步骤见根目录 README:
bash scripts/fago.sh migrate
bash scripts/fago.sh bootstrap
bash scripts/fago.sh api
bash scripts/fago.sh worker
bash scripts/web.sh install --frozen-lockfile
bash scripts/web.sh dev
主要配置为 APP_ENV、APP_HTTP_ADDR、APP_DATABASE_DRIVER、APP_DATABASE_DSN、APP_REDIS_ADDR、APP_REDIS_PASSWORD、APP_SHUTDOWN_SECONDS。schema/driver/依赖异常有明确错误,响应和日志不输出 DSN、密码、Cookie、请求体或底层 SQL 参数。请求 ID 由服务端生成,不直接信任任意请求头。API/worker 启动校验 schema 与 Redis,迁移/bootstrap 只依赖数据库。
迁移使用方言专用 SQL,连接级锁阻止同库并发迁移;已执行版本校验内容摘要。PostgreSQL 的 DDL 在事务中执行;MySQL DDL 可能隐式提交,故执行前先记录 dirty,失败保留 dirty 并停止。历史脚本不可修改后重跑,也不在服务启动时自动迁移。
当前只有初始逻辑版本 1;后续 schema 增量需要新增版本和升级验收,不能改写 0001。失败恢复步骤:停止后续迁移 → 备份/检查数据库实际对象和失败语句编号 → 对照原始脚本确认完成/缺失对象 → 人工补偿或在可丢弃空库重建 → 确认版本与校验和后再显式恢复元数据。没有自动清 dirty 或自动 down 命令,生产升级回滚仍需专门演练。
bootstrap 使用同一业务事务创建租户、必须改密的用户和审计记录;tenancy/system 两个模块通过窄契约协作且绑定同一 tx。密码由操作者提供,至少 14 字符且不超过 bcrypt 的 72 字节上限;重复租户拒绝初始化,绝不重置密码。整个命令有总超时。集成 fixture 不经生产 bootstrap 注入固定账号。
# 合同与前端
api/openapi.yaml 已冻结身份、当前用户/权限/菜单、用户/部门/岗位/角色/授权、平台租户/套餐和日志等核心合同。ID 为字符串,写 DTO 禁止未知字段,更新带 version;分页 pageSize 最大 100。认证端点包含 clientId;平台/租户 refresh Cookie 分别定义,不共用身份凭证。
bash scripts/web.sh contract
bash scripts/web.sh typecheck
bash scripts/web.sh build
node scripts/check-web-bundle.mjs
合同生成 fago-vben5/apps/web-antd/src/api/schema.d.ts。校验器保留递归菜单/部门树的引用,校验 567 个响应样例、权限映射和 HTTP/业务错误码对应关系。当前 health 响应统一 code/data/message/requestId;依赖不可用返回 503 + 503001。生产包检查上游 mock 地址、mock 适配器与统计脚本,避免仅关闭开发 mock 却仍将演示入口带入运行包。
开发代理不去掉 /api,生产 nginx 同样原样转发并对 history 路由回退到 index.html。浏览器已实际显示“连接正常”,重新检查会再次请求真实后端。根目录更名后,所有脚本、CI、类型生成路径均采用 fago-vben5/。
# 数据库风险结论
| 数据库 | 当前证据 | 结论/后续 |
|---|---|---|
| MySQL 8.4.13 | 空库迁移、重复迁移、dirty/校验和拒绝、两租户、事务与工作流 PoC | M0 主线通过;尚非全部平台模块认证 |
| PostgreSQL 14.18 Homebrew | 同一集成套件通过 | M0 PoC 通过;Compose/CI 另配置 PG16.6,未将其当成本机已测版本 |
| Oracle | go-ora/v2 2.9.0 在独立探针构建,默认 API 不依赖 Oracle 客户端 | 纯 Go 路径可构建;无实例,identity/分页/锁/DDL 恢复均未认证 |
| SQL Server | go-mssqldb 1.9.5 在独立探针构建 | Go 驱动可构建;无实例,IDENTITY/NVARCHAR/NOWAIT 等待实测 |
| DM | 开发实例 DM8(服务端完整 banner 见证据),服务端 Linux aarch64,客户端 darwin/arm64。安装包 Go 驱动 8.1.3.62 与官方 GORM V2 方言编进独立 fago-dm。bash scripts/dm-poc.sh 通过序列、LIMIT/OFFSET 分页、NOWAIT 行锁、唯一约束失败后的继续写入与回滚、DDL 隐式提交,以及 GORM 自增主键/中文 CRUD/分页/READ COMMITTED 回滚。证据见 docs/evidence/dm-m0-poc.json | M0 行为 PoC。默认 API 和迁移命令未装配,不是生产许可或 R4 认证 |
Oracle/SQL Server 探针并非只测连接:已准备 identity 主键、Unicode、唯一约束、事务回滚、分页和两个连接的 NOWAIT 行锁排斥验证。给专用测试实例设置 FAGO_PROBE_DRIVER 与 FAGO_PROBE_DSN 后运行 bash scripts/db-poc.sh;缺少环境会以非零状态退出,不能在测试报告中标成通过。探针不替代平台各模块的方言迁移 runner 和完整认证。
达梦 PoC 使用本机已启动的 qinchz/dm8-arm64 开发实例,驱动和 GORM V2 方言从该实例安装目录 drivers/go 原样接入,并按官方接入说明 (opens new window)核对。实例参数:COMPATIBLE_MODE=0、LENGTH_IN_CHAR=1、GLOBAL_STR_CASE_SENSITIVE=0、GLOBAL_CHARSET=1(UTF-8)。DDL 之后原先的插入仍然在,而 Rollback 返回 nil;不能把 nil 当成 DML 已撤销。容器使用开发授权,社区镜像可用不等于生产许可。该次 PoC 时默认 API 不引用 fago-dm,平台迁移 runner 尚未包括达梦。缺少 FAGO_DM_DSN 时探针跳过,不能把跳过记成通过。
MariaDB/TiDB/其他消息后端仍按 R4 计划执行,没有把 MySQL 成功当作它们已通过。
# OIDC 候选结论
Dex v2.44.0 以独立 Go 进程运行,测试包含两个不同注册应用、同一浏览器 cookie jar、授权码 + PKCE、签名/issuer/audience/nonce、同 issuer/sub 身份关联,以及授权码重放拒绝。
实测输出:
clients=2 signature/audience/nonce/PKCE/code-replay passed
password_prompts=2 end_session_endpoint=false
CANDIDATE_REJECTED
该 local-password 配置没有达到双应用共享登录与 RP 统一登出要求,因此不作为默认 IdP。不推断其他 Dex connector 或其他产品都不可行。成熟 coreos/go-oidc 客户端保留为可复用候选;M5 前继续验证满足完整会话/登出合同的成熟 IdP,不自行开发生产授权服务器。
一键复现入口为 bash scripts/run-oidc-poc.sh,专用端口 15556,使用明确的测试账号和测试 client secret,退出时停止并清理本次进程。当前业务 API 没有暴露 SSO 入口,默认 Compose 也不部署该淘汰候选。
# 工作流 PoC 结论
自有 fago-workflow-core PoC 使用业务数据库状态,而不是仅依赖内存 goroutine:
- AND 按预期分支身份 join;OR 并发只有一个成功,后续分支不能重复完成。
- 命令按租户/commandId 去重,结构化实例/分支摘要避免拼接歧义,复用不同负载拒绝。
- 子进程提交首个分支并退出,父进程从数据库读取检查点完成剩余分支。
- 精确 engineVersion 不匹配时拒绝执行,不使用新运行时覆盖旧绑定。
- timer 领取递增 fence;过期/旧 fence、重复触发均拒绝。
- 固定版本 PDF 与采购单采用相同资源接口和同一引擎;注入事务失败验证状态/命令/资源一起回滚,批准 v1 不改变 v2,跨租户资源访问拒绝。
以上在 MySQL/PostgreSQL 均运行 go test -race。fixture 在测试结束清理,清理 SQL 错误会使测试失败。PoC 建表只针对 wf_poc/poc_* 专用表,生产应用 schema 仍走显式迁移。
此 PoC 仅有单个结构化激活、最小资源归属检查和运行时精确匹配。嵌套 scope/循环、definition admission_epoch、运行时退役/回滚闸口、任务 ACL/临时 grant、完整表单、设计器和完整 BPMN 尚未实现;不得以当前测试替代 R3 套件。
olive-io/bpmn 1.3.4 的源码与依赖仅作评估,未引入生产依赖;没有获得其满足本项目同库审批事务、七库恢复和全部资源合同的证据。结论是保留解析器候选、继续自有持久化契约和有界核心研发,不能宣称已找到完整替代或完整审计了候选库。
# 实际验证与复现
本轮执行并通过的入口:
python3 scripts/check-baseline.py
bash scripts/test-backend.sh
bash scripts/test-independent.sh
bash scripts/web.sh install --frozen-lockfile
bash scripts/web.sh contract
bash scripts/web.sh typecheck
bash scripts/web.sh build
node scripts/check-web-bundle.mjs
bash scripts/integration.sh # 分别以专用 MySQL/PostgreSQL 配置运行
bash scripts/run-oidc-poc.sh
另验证 migrate/bootstrap 在 Redis 地址不可用时仍成功、重复 bootstrap 被拒绝且测试用户清理;Compose 运行配置静态校验;浏览器调用真实健康 API;文档同步、链接检查与生产构建。源码在本地交付;尚未配置远程发布、部署公网或运行远端 CI。
M0 风险 PoC 有明确结论,不代表七数据库/五消息、完整 SSO、Flowable 等价或生产容量已通过。下一阶段仍是 M1 真实身份与导航;任何未来宣称的支持能力都须补对应实际环境与完整测试证据。
# 达梦适配复核(2026-10-23,业务接入前)
新增 GORM 真库用例后发现:供应商方言没有实现 GORM ErrorTranslator,即使打开 TranslateError,重复唯一键仍返回原始 *dm.DmError。已在适配层外包方言,仅把本实例原生 SQL 与 GORM 共同确认的 -6625 映射为 gorm.ErrDuplicatedKey;用 Oops 携带稳定码 409002,保留 errors.Is/As,未知码原样保留,不按文字猜测。未修改供应商 Create 回调。新增独立单元测试及真库重复唯一键回归均通过;清理失败会使探针失败,并且只有全部检查和清理成功才更新证据。
该次检查只覆盖 PoC 证据中的组合;当时达梦尚无 schema v11 全量业务迁移、M1–M3 业务回归、外键错误映射和完整类型/批量 upsert 认证。当时默认业务构建拒绝 dm,避免把 PoC 描述成平台支持。后续业务接入、v13 迁移和全量回归已另行完成,当前状态以 数据库适配 与 业务验收证据 为准。Oracle/SQL Server 探针按各自标签只链接选定驱动,业务发布包不带这些探针。