From f50268b70f974a6a11edfba17883faa2004306d0 Mon Sep 17 00:00:00 2001 From: Citrons <909229731@qq.com> Date: Thu, 13 Aug 2026 19:55:45 +0800 Subject: [PATCH] =?UTF-8?q?docs(GOV-009):=20=E5=B0=81=E5=AD=98=E8=84=9A?= =?UTF-8?q?=E6=89=8B=E6=9E=B6=E5=A4=8D=E6=A0=B8=E6=9D=90=E6=96=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 2 +- .../architecture-review.md | 57 ++++++++++++++++++ .../gitnexus/after.md | 19 ++++++ .../gitnexus/comparison.md | 18 ++++++ .../gitnexus/detected-changes.md | 15 +++++ .../implementation-plan.md | 32 ++++++++++ .../manifest.yaml | 3 +- .../qa/test-results.md | 48 +++++++++++++++ .../qa/visual-qa.md | 21 +++++++ .../release-notes.md | 15 +++++ .../review/code-review.md | 24 ++++++++ .../review/standards-review.md | 21 +++++++ .../rollback.md | 17 ++++++ .../app-shell-ready-1440x900-3a8120a.png | Bin 0 -> 34058 bytes .../app-shell-ready-390x844-3a8120a.png | Bin 0 -> 21024 bytes 15 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 docs/features/GOV-009-application-scaffold-baseline/architecture-review.md create mode 100644 docs/features/GOV-009-application-scaffold-baseline/gitnexus/after.md create mode 100644 docs/features/GOV-009-application-scaffold-baseline/gitnexus/comparison.md create mode 100644 docs/features/GOV-009-application-scaffold-baseline/gitnexus/detected-changes.md create mode 100644 docs/features/GOV-009-application-scaffold-baseline/qa/test-results.md create mode 100644 docs/features/GOV-009-application-scaffold-baseline/qa/visual-qa.md create mode 100644 docs/features/GOV-009-application-scaffold-baseline/release-notes.md create mode 100644 docs/features/GOV-009-application-scaffold-baseline/review/code-review.md create mode 100644 docs/features/GOV-009-application-scaffold-baseline/review/standards-review.md create mode 100644 docs/features/GOV-009-application-scaffold-baseline/rollback.md create mode 100644 docs/features/GOV-009-application-scaffold-baseline/screenshots/iterations/app-shell-ready-1440x900-3a8120a.png create mode 100644 docs/features/GOV-009-application-scaffold-baseline/screenshots/iterations/app-shell-ready-390x844-3a8120a.png diff --git a/AGENTS.md b/AGENTS.md index 404bdbc..e1776a4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,7 +24,7 @@ # GitNexus — Code Intelligence -This project is indexed by GitNexus as **a-share-analysis** (1520 symbols, 2137 relationships, 47 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **a-share-analysis** (1577 symbols, 2199 relationships, 47 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. diff --git a/docs/features/GOV-009-application-scaffold-baseline/architecture-review.md b/docs/features/GOV-009-application-scaffold-baseline/architecture-review.md new file mode 100644 index 0000000..f8b027a --- /dev/null +++ b/docs/features/GOV-009-application-scaffold-baseline/architecture-review.md @@ -0,0 +1,57 @@ +# 前后端项目架构复核与论证 + +- 复核基线:`95b8611bd9bbc282193591e472e2e19c617e5035` +- 状态:实现已验证,等待用户确认 + +## 实际落地结构 + +```text +repository +├── web/ Vue 3 独立工程 +│ └── src/ +│ ├── app/ 应用装配、路由和根视图 +│ ├── api/generated/ OpenAPI 自动生成类型 +│ ├── api/adapters/ 面向页面的稳定数据适配 +│ └── styles/ Sass 全局样式与后续 token 入口 +├── server/ NestJS 独立工程 +│ ├── apps/api/ HTTP 与 OpenAPI 入口 +│ ├── apps/scheduler/ 只负责任务触发的无 HTTP 进程 +│ ├── apps/worker/ 任务执行的无 HTTP 进程 +│ └── libs/ +│ ├── config/ 三进程最小权限配置契约 +│ ├── http-contracts/ Server DTO +│ ├── message-contracts/ Server 内部 RocketMQ 信封 +│ └── infrastructure/ Redis、MySQL、消息与生命周期适配 +├── deploy/ Compose、systemd、Nginx、env 模板 +└── tools/ 治理、Schema、契约、边界和部署验证 +``` + +## 为什么当前拆分合适 + +1. **前后端工程分开,但仓库不割裂。** Web 和 Server 有各自 package、构建与类型检查,责任清楚;根 workspace 只做质量编排。个人开发阶段仍能一次安装和验证,避免维护多个仓库与跨仓库版本同步。 +2. **不建立根共享 contracts 包。** HTTP 只有一个事实源:Server DTO 生成 OpenAPI,Web 再生成自己的类型。Web 无法直接引用 Server 源码,`.vue` 和 TypeScript 都有自动边界检查。这样比手工同步两份类型或共享内部类更明确。 +3. **一个 Server 工程、三个进程入口。** API、Scheduler、Worker 使用相同基础设施与消息契约,但启动、配置和部署彼此独立。当前规模下没有必要拆成三个仓库或三个重复 Nest 工程;未来可独立扩容进程而不复制公共代码。 +4. **配置是进程级最小权限。** API 不读取任务消费配置,Scheduler 不读取 MySQL/AI 密钥,Worker 不读取 API 端口。配置先校验和冻结,再通过动态模块注入,测试与生产使用同一装配路径。 +5. **基础设施通过 Adapter 隔离。** 业务代码将依赖 MessageBus、Repository 和数据源端口;RocketMQ 客户端尚未验证时明确禁用,避免以临时内存队列形成错误语义。 +6. **部署方式共享同一产物。** Ubuntu 源码部署使用 Web dist 与三个 Server dist 入口;现有 Docker Compose 只作为可选交付适配层复用这些产物,不形成第二套应用逻辑。按用户 2026-08-13 的决定,日常开发不再构建 Docker,待核心功能完成后再决定保留或移除。 + +## 适合下一阶段的扩展方式 + +- Web 市场概览应建立一个可连续阅读的 `features/market-overview/` 业务目录,内部按页面、数据适配和状态拆分;短线与中长线作为市场概览下的模式,不拆成两个独立应用。 +- Server 在 API/Scheduler/Worker 各入口内注册同一市场概览业务能力的不同适配面;共享领域模型和规则计算放入稳定业务库,HTTP DTO、消息 DTO 与数据库实体继续分离。 +- 用户体系、订阅和权限暂不加入当前业务模型;未来通过独立功能档案增加,不让其反向污染市场概览核心。 +- 当某个业务域出现独立发布节奏、团队所有权或明显扩容边界时,再论证拆包或拆服务;当前提前微服务化只会增加契约、部署和运维成本。 + +## 已知限制 + +- 当前 Web 只是工程外壳,没有市场概览 UI、行情图表和分析工作台。 +- RocketMQ 只有契约和禁用 Adapter,尚未选择并验证 Node 客户端。 +- TypeORM 已固定安全选项,但尚无业务实体和迁移。 +- 用户体系和订阅能力按用户决定后置,不在 GOV-009 范围。 +- Docker 交付方式尚未最终确认;现有文件和历史验证保留,但不能据此推定最终生产一定采用容器部署。 + +## 复核结论 + +现有架构适合“先做市场概览、个人使用为主、未来可扩展订阅”的阶段:边界清晰、运行方式简单、契约可自动校验,也保留 API/Scheduler/Worker 独立扩容路径。它不是不可变终局;完成市场概览真实业务后,仍应根据模块依赖和运行数据再次复核,而不是凭脚手架预判过度拆分。 + +用户确认本论证前,GOV-009 保持 `reviewing/unreleased`,不得创建 `v0.0.8` 标签。 diff --git a/docs/features/GOV-009-application-scaffold-baseline/gitnexus/after.md b/docs/features/GOV-009-application-scaffold-baseline/gitnexus/after.md new file mode 100644 index 0000000..6935188 --- /dev/null +++ b/docs/features/GOV-009-application-scaffold-baseline/gitnexus/after.md @@ -0,0 +1,19 @@ +# GitNexus 开发后基线 + +- 记录时间:2026-08-13 GMT+8 +- 分支:`main` +- 实现提交:`95b8611bd9bbc282193591e472e2e19c617e5035` +- 远端:`origin/main` 已核对为同一 SHA + +```text +Repository: a-share-analysis +Indexed commit: 95b8611 +Current commit: 95b8611 +Status: up-to-date +Nodes: 1568 +Relationships: 2190 +Clusters: 46 +Execution flows: 47 +``` + +图谱已包含 Web、API、Scheduler、Worker、配置加载、健康检查、OpenAPI、消息信封、Schema、Docker 验证和治理门禁。GitNexus 本地索引未提交仓库。 diff --git a/docs/features/GOV-009-application-scaffold-baseline/gitnexus/comparison.md b/docs/features/GOV-009-application-scaffold-baseline/gitnexus/comparison.md new file mode 100644 index 0000000..7b1dcc0 --- /dev/null +++ b/docs/features/GOV-009-application-scaffold-baseline/gitnexus/comparison.md @@ -0,0 +1,18 @@ +# GitNexus 前后对比 + +| 项目 | 开发前 `c04184a6` | 开发后 `95b8611` | 变化 | +| --- | ---: | ---: | ---: | +| 符号 | 958 | 1568 | +610 | +| 关系 | 1141 | 2190 | +1049 | +| 执行流程 | 23 | 47 | +24 | + +开发前仓库以治理文档、环境模板和 Schema 工具为主,Web/Server 仍无可运行应用。开发后形成以下新边界: + +- Vue 3 Web 可启动、测试、构建,并通过生成的 OpenAPI 类型访问 API。 +- NestJS API、Scheduler、Worker 为独立入口和独立配置容器。 +- Scheduler/Worker 具备可验证的持续运行与 SIGTERM 优雅关闭生命周期。 +- HTTP 契约由 Server DTO 单向发布;RocketMQ 消息信封只存在于 Server。 +- Docker Compose 与 Ubuntu 源码部署共用同一配置语义和构建产物。 +- Schema 空库、MySQL 5.7.42、契约漂移和 `.vue` 工程边界均有自动门禁。 + +新增关系数量符合“从占位目录建立应用基线”的预期。后续市场概览功能应在这些边界内增加业务模块,不应把 Web 与 Server 重新耦合。 diff --git a/docs/features/GOV-009-application-scaffold-baseline/gitnexus/detected-changes.md b/docs/features/GOV-009-application-scaffold-baseline/gitnexus/detected-changes.md new file mode 100644 index 0000000..2b52f83 --- /dev/null +++ b/docs/features/GOV-009-application-scaffold-baseline/gitnexus/detected-changes.md @@ -0,0 +1,15 @@ +# GitNexus 变更检测 + +- 对比基线:`c04184a6111da0cfad90d1a265dc3cd3a6d90a9c` +- 实现提交:`95b8611bd9bbc282193591e472e2e19c617e5035` +- 检测命令:`npx gitnexus detect-changes --scope compare --base-ref c04184a6 --repo a-share-analysis` +- 变更范围:110 个文件、452 个符号、45 条执行流程 +- 风险:CRITICAL + +CRITICAL 来自一次建立完整 Web、API、Scheduler、Worker、契约生成、Schema 和部署验证链路,而不是单个未受控函数。主要流程为三进程配置加载、Schema 生成/验证、OpenAPI 发布、Web 客户端边界和 Docker/源码部署。 + +Review 整改前对具体符号分别执行上游影响分析:三进程 bootstrap、动态模块、消息信封、空库捕获和 MySQL 健康检查大多为 LOW;`assertMysql57Metadata` 因位于 Schema 发布门禁核心为 CRITICAL。该变更只把服务端补丁版本固定到 5.7.42,历史 `v0.0.7` 清单回归和 `schema:verify` 均通过。 + +最终代码复审补充发现无引号 Vue 外部 `script src` 可绕过早期正则门禁;影响分析为 LOW,只有 1 个直接调用者和 1 条边界检查流程。`95b8611` 改用 Vue 3.5.41 官方 SFC 解析器,并以单双引号、无引号和属性换序用例验证,未扩大应用运行影响面。 + +检测范围与开发前影响计划一致,未出现用户体系、生产数据库、外部消息写入或其他项目流程。 diff --git a/docs/features/GOV-009-application-scaffold-baseline/implementation-plan.md b/docs/features/GOV-009-application-scaffold-baseline/implementation-plan.md index 82bdba8..db6e45f 100644 --- a/docs/features/GOV-009-application-scaffold-baseline/implementation-plan.md +++ b/docs/features/GOV-009-application-scaffold-baseline/implementation-plan.md @@ -609,3 +609,35 @@ Push current branch, compare local and remote SHA, create annotated tag `v0.0.8` - [ ] **Step 8: 刷新 GitNexus 并确认干净状态** Run `npx gitnexus status`; stale 时运行 analyze。最终要求工作区干净、`main` 与 `origin/main` 一致、远程 `v0.0.8^{}` 指向本地标签提交。 + +--- + +## 实际执行记录 + +### 已完成任务与远端提交 + +| Task | RED / GREEN 与验证摘要 | 提交 | 推送证据 | +| --- | --- | --- | --- | +| 1 Schema | 配置路径、固定远程白名单、历史 N/A 与空库用例先失败;实现后 Schema 27/27、治理 5/5、环境 7/7 | `b3d8e29` | 15:43:07 `origin/main update by push` | +| 2 Workspace | 运行时模块缺失先失败;Node 22/pnpm 10 门禁与 frozen install 后通过 | `258a2d2` | 15:54:08 推送 | +| 3 Web | App 缺失、模板 `import.meta.env` 与路由警告依次复现;Vitest、类型、构建、双视口 Playwright 后通过 | `28d5418`、`dcdf634` | 16:43:48、18:03:38 推送 | +| 4 三进程 | 配置条件与启动入口先失败;三个 Nest 入口和独立配置 Schema 后通过 | `3fc703d` | 16:59:12 推送 | +| 5 后端契约 | 消息、健康接口和 OpenAPI 先失败;Server 契约与只读客户端后通过 | `420aa22` | 17:26:53 推送 | +| 6 Web 契约 | Web 越界导入与 adapter 行为先失败;OpenAPI 生成客户端和边界门禁后通过 | `4665336` | 17:36:58 推送 | +| 7 部署 | Docker/systemd 产物断言先失败;双镜像、三入口和双部署文档后通过 | `c771a98` | 17:56:39 推送 | +| 8 Review 整改 | 生命周期、配置注入、消息字段、5.7.42、空库、SFC、漂移、代理与 Docker 集成均有 RED/GREEN;最终补齐未跟踪契约、Vue 官方 SFC 解析、镜像摘要和容器生命周期门禁;`pnpm verify` 通过,Docker 留存历史验证 | `3a8120a`、`cb550e3`、`95b8611` | 18:31:10、18:51:16、19:49:46 推送 | + +上述推送时间来自项目级 `origin/main` reflog;所有阶段均在提交后 1–83 秒内推送。提交摘要全部为包含 `GOV-009` 的中文 Conventional Commit。 + +### 计划状态 + +- Task 1–7:已实现、验证、提交并推送;原始步骤复选框保留为开发前计划,不反向改写预期命令和 RED 描述。 +- Task 8 Step 1–3、5:已完成全量验证、视觉证据、GitNexus 检测与发布/回退材料。 +- Task 8 Step 4:代码 Review 追加发现无引号外部脚本绕过,已用 Vue 官方 SFC 解析器整改;代码 Review 与规范 Review 最终结论均为 `passed`。 +- Task 8 Step 6:架构复核文档已生成,正在向用户论证并等待确认。 +- Task 8 Step 7:未执行;用户确认前禁止创建或推送 `v0.0.8`。 +- Task 8 Step 8:开发后图谱已刷新到 `95b8611`;最终档案提交后还需再次刷新并核对远端。 + +### Docker 后续执行口径 + +用户于 2026-08-13 决定:Docker 暂不作为后续日常开发与阶段验收的必跑项,待核心功能完整后再决定是否保留 Docker 部署。现有 Docker/Compose 文件与本次已通过的历史验证不删除;后续日常执行 `pnpm verify`,只有最终交付且用户确认继续采用 Docker 时才执行 `pnpm verify:docker` 或 `pnpm verify:release`。 diff --git a/docs/features/GOV-009-application-scaffold-baseline/manifest.yaml b/docs/features/GOV-009-application-scaffold-baseline/manifest.yaml index f748132..bf95784 100644 --- a/docs/features/GOV-009-application-scaffold-baseline/manifest.yaml +++ b/docs/features/GOV-009-application-scaffold-baseline/manifest.yaml @@ -1,7 +1,7 @@ id: GOV-009 name: 应用工程脚手架基线 slug: application-scaffold-baseline -status: approved +status: reviewing owner: project created_at: 2026-08-13 prd: prd.md @@ -13,3 +13,4 @@ gitnexus_before: gitnexus/before.md gitnexus_impact: gitnexus/impact-plan.md release: unreleased completion_gate: 实施完成后必须基于实际前后端架构向用户论证并再次确认 +architecture_review: architecture-review.md diff --git a/docs/features/GOV-009-application-scaffold-baseline/qa/test-results.md b/docs/features/GOV-009-application-scaffold-baseline/qa/test-results.md new file mode 100644 index 0000000..5594106 --- /dev/null +++ b/docs/features/GOV-009-application-scaffold-baseline/qa/test-results.md @@ -0,0 +1,48 @@ +# 测试结果 + +- 日期:2026-08-13 +- 实现提交:`95b8611bd9bbc282193591e472e2e19c617e5035` +- 状态:passed + +## 测试先行证据 + +- 三进程配置 Token 缺失、Scheduler/Worker 启动后自行退出:RED;动态模块注入和生命周期锚点实现后 GREEN。 +- 消息信封缺少追溯字段、MySQL 接受 5.7.41、真实空库 dump 被误判:RED;完整信封、5.7.42 固定和对象计数判空后 GREEN。 +- `.vue` 越界导入未被发现、Vite 无 `/api` 代理:RED;SFC 脚本扫描和同源代理后 GREEN。 +- 契约漂移验证模块不存在:RED;生成后限定路径 Git diff 与 Git 跟踪门禁后 3/3 GREEN。 +- Vue 外部 `script src`、可变镜像标签和干净 checkout 生命周期用例依次 RED;禁止外部脚本、摘要校验与公开生产 fixture 实现后 GREEN。 +- Playwright 访问真实健康接口时代理连接拒绝:RED;同时启动测试 API 并复用可用本机 Redis 后 GREEN。 + +## 完整验证 + +`pnpm verify`:passed。 + +- 运行时:Node.js 22.11.0、pnpm 10.25.0。 +- 治理:5/5。 +- MySQL 架构规范:2/2。 +- 环境与部署契约:11/11。 +- Schema 基线:28/28;`v0.0.7 (not_applicable)` 验证通过。 +- 生成契约漂移:3/3;OpenAPI 与 Web 生成类型同步,未跟踪生成物会阻断。 +- 工程边界:8/8,包含 Vue SFC 内联脚本,以及外部脚本单双引号、无引号和属性换序越界用例。 +- 生产镜像引用:5/5;拒绝空值、`latest`、普通 tag 和裸摘要。 +- Web Vitest:8/8。 +- Server Vitest:30/30,包含 API/Scheduler/Worker 与两个真实子进程生命周期用例。 +- Web、API、Scheduler、Worker 生产构建:passed。 +- Playwright:桌面/移动共 4/4;页面外壳与 Web → Vite → Nest API 健康链路均通过。 +- `git diff --check`、ESLint、Prettier、TypeScript:passed。 + +`pnpm verify:docker`:passed。 + +该结果是 2026-08-13 已完成的历史交付验证。按用户随后决定,Docker 不再作为日常开发必跑项;核心功能完成后再决定是否保留 Docker 部署,并仅在最终交付阶段按需重跑。 + +- Web 与 Server 多阶段镜像真实构建成功。 +- Server 镜像以 `node` 非 root 用户运行。 +- API、Scheduler、Worker 三个镜像入口存在;Scheduler/Worker 均在容器内持续运行,并在 `SIGTERM` 后退出码为 0。 +- 隔离容器内 Redis/API/Web 启动,API 健康 200、Web 首页和 Nginx `/api` 代理通过。 +- 临时容器、网络和验证镜像已清理。 + +## 本机与固定开发基础设施 + +- 本机 Redis `127.0.0.1:6379` 的 DB 0、1、2 分别执行只读 `PING`,三次均返回 `PONG`;未读取或修改业务键。 +- 固定开发 MySQL 只读会话返回 `5.7.42-0ubuntu0.18.04.1`、库 `a_share_analysis_dev`、`utf8mb4`、`utf8mb4_general_ci`、会话时区 `+08:00`;Task 1 对象计数为 0。密码只通过临时进程环境传递,未输出或写入命令参数;未连接、探测或读取生产数据库。 +- 本机 RocketMQ 只读 TCP 可达性复核:NameServer `127.0.0.1:9876`、Broker `127.0.0.1:10911`、Proxy `127.0.0.1:8081` 均可达。Node Adapter 仍保持禁用,没有创建 Topic、消费或发送消息。 diff --git a/docs/features/GOV-009-application-scaffold-baseline/qa/visual-qa.md b/docs/features/GOV-009-application-scaffold-baseline/qa/visual-qa.md new file mode 100644 index 0000000..b2d1fc3 --- /dev/null +++ b/docs/features/GOV-009-application-scaffold-baseline/qa/visual-qa.md @@ -0,0 +1,21 @@ +# 视觉 QA + +- 日期:2026-08-13 +- 浏览器:Playwright Chromium +- 状态:passed + +## 桌面 1440 × 900 + +- 证据:`../screenshots/iterations/app-shell-ready-1440x900-3a8120a.png` +- SHA-256:`79b3ef53178c9d35fc10b75aac2d844d34c886d4e2faab69762922f4cda17802` +- 顶部品牌栏、应用标题和“工程基线已就绪”可见。 +- `main` 可见,无水平溢出、控制台 warning/error 或页面异常。 + +## 移动 390 × 844 + +- 证据:`../screenshots/iterations/app-shell-ready-390x844-3a8120a.png` +- SHA-256:`12f56ae24d51c64b1e0dfc03a8a2e3538e5ce5a7a33c2863f9bff5d53efe3d1d` +- 品牌栏与正文在窄屏正常换行和留白,无水平溢出。 +- 无控制台 warning/error 或页面异常。 + +当前画面仅证明工程外壳与响应式基线,不代表市场概览产品视觉已经完成;市场概览页面将在后续功能档案中单独设计、实现和验收。 diff --git a/docs/features/GOV-009-application-scaffold-baseline/release-notes.md b/docs/features/GOV-009-application-scaffold-baseline/release-notes.md new file mode 100644 index 0000000..36222a6 --- /dev/null +++ b/docs/features/GOV-009-application-scaffold-baseline/release-notes.md @@ -0,0 +1,15 @@ +# 发布说明 + +GOV-009 建立了可运行的应用工程基线: + +- 根 pnpm workspace 编排独立的 Vue 3 Web 与 NestJS Server 工程。 +- Server 提供 API、Scheduler、Worker 三个独立入口、最小权限配置和优雅关闭生命周期。 +- HTTP 契约由 Server DTO/OpenAPI 单向生成 Web 客户端;RocketMQ 消息契约保持 Server 内部。 +- Redis/MySQL 只读健康检查、TypeORM MySQL 5.7.42 基线和 RocketMQ 禁用 Adapter 已建立。 +- 开发/生产模板、Docker Compose、Ubuntu systemd/Nginx 与源码部署路径已对齐。 +- Schema 支持固定远程开发库与真实空库 `captured_empty`,历史清单保持不可变。 +- 新增契约漂移、Vue SFC 边界、三进程生命周期、Playwright 真实 API 和 Docker 集成门禁。 + +当前状态为 `reviewing/unreleased`。实现提交已推送至 `origin/main`,但按用户要求,必须完成基于真实实现的架构论证并再次确认后才能创建和推送 `v0.0.8` 标签。 + +Docker Compose 当前降级为待定的可选交付能力:保留既有文件和历史验证,不进入后续日常开发必跑流程;核心功能完成后由用户决定最终保留 Docker 还是仅采用源码部署。 diff --git a/docs/features/GOV-009-application-scaffold-baseline/review/code-review.md b/docs/features/GOV-009-application-scaffold-baseline/review/code-review.md new file mode 100644 index 0000000..1a00347 --- /dev/null +++ b/docs/features/GOV-009-application-scaffold-baseline/review/code-review.md @@ -0,0 +1,24 @@ +# 代码 Review + +- 审查范围:`c04184a6111da0cfad90d1a265dc3cd3a6d90a9c..95b8611bd9bbc282193591e472e2e19c617e5035` +- 审查方式:独立只读复审 +- 日期:2026-08-13 + +## 发现与处置 + +第一轮发现三进程生命周期依赖本地 env、未跟踪生成契约可绕过漂移检查、Vue 外部脚本可绕过边界、生产镜像允许可变 tag,以及 Docker 未验证镜像内 Scheduler/Worker 生命周期。上述问题已在 `cb550e3` 完成整改并通过针对性测试。 + +最终复审进一步发现早期外部脚本正则不能识别合法的无引号 `src` 属性。GitNexus 单符号影响分析为 LOW,只有 `verifyProjectBoundaries` 一个直接调用者和一条边界检查流程。`95b8611` 改用固定版本 `@vue/compiler-sfc@3.5.41`,直接拒绝普通脚本或 setup 脚本的外部 `src`,测试覆盖双引号、单引号、无引号和属性换序,边界用例 8/8 通过。 + +## 最终证据 + +- `pnpm verify` 完整通过:静态检查、类型、全部测试、Web/Server 构建和 Playwright 4/4。 +- 契约漂移 3/3、工程边界 8/8、镜像引用 5/5、Web 8/8、Server 30/30。 +- Scheduler/Worker 的源码子进程生命周期使用公开 production fixture,可在干净 checkout 复现。 +- HTTP 契约保持 Server DTO → OpenAPI → Web 单向发布;RocketMQ 消息契约只在 Server 内部。 +- 未发现新的配置泄漏、安全、生命周期、错误处理或生产风险。 +- Docker 是已验证过的历史可选交付能力;按用户决定不再作为后续日常门禁,最终开发完成后再决定是否保留。 + +Critical、Important、Minor 均无未关闭问题。 + +最终结论:passed diff --git a/docs/features/GOV-009-application-scaffold-baseline/review/standards-review.md b/docs/features/GOV-009-application-scaffold-baseline/review/standards-review.md new file mode 100644 index 0000000..f049052 --- /dev/null +++ b/docs/features/GOV-009-application-scaffold-baseline/review/standards-review.md @@ -0,0 +1,21 @@ +# 开发规范符合性 Review + +- 审查范围:`c04184a6111da0cfad90d1a265dc3cd3a6d90a9c..95b8611bd9bbc282193591e472e2e19c617e5035` +- 审查方式:独立只读复审 +- 日期:2026-08-13 + +## 规范核对 + +- 所有 GOV-009 实现提交均为包含功能编号的中文 Conventional Commit,并在验证后及时推送 Gitea。 +- GitNexus 前置、影响分析、开发后索引和前后对比完整;最终索引提交为 `95b8611`。 +- 公共导出、契约字段、安全边界和架构取舍均有准确中文注释,未发现无意义逐行注释或过度拆分。 +- MySQL 固定为 5.7.42、`utf8mb4`、`utf8mb4_general_ci` 和 `+08:00`;Schema 流程只允许固定开发库和白名单主机。 +- Redis DB 0/1/2 均只读 `PING` 成功;RocketMQ NameServer/Broker/Proxy 均只读验证可达,未创建 Topic 或发送消息。 +- 截图位于 `screenshots/iterations/`,文件名包含页面、状态、视口和实现版本,文档引用与 SHA-256 一致。 +- 未发现密钥、真实 env、生产数据或 GitNexus 本地索引被 Git 跟踪。 +- Docker 最新决定已统一记录为历史验证、可选交付、非日常必跑,不篡改此前真实验证记录。 +- manifest 保持 `reviewing/unreleased`,本地与远端均不存在 `v0.0.8`。 + +Critical、Important、Minor 均无未关闭问题。 + +最终结论:passed diff --git a/docs/features/GOV-009-application-scaffold-baseline/rollback.md b/docs/features/GOV-009-application-scaffold-baseline/rollback.md new file mode 100644 index 0000000..0ccb5ea --- /dev/null +++ b/docs/features/GOV-009-application-scaffold-baseline/rollback.md @@ -0,0 +1,17 @@ +# 回退说明 + +GOV-009 没有创建业务表、执行迁移、写入生产数据库、创建 RocketMQ Topic 或发送消息。回退重点是应用代码与部署产物,不包含数据回滚。 + +## 代码回退 + +- 以中文 Conventional Commit 新建回退提交,按逆序撤销 GOV-009 提交;禁止 `reset --hard`、强推或改写 Gitea 历史。 +- 若只回退 Review 整改,目标提交为 `3a8120a33aff3fc4e1f73059eed78d88d57d33a2`,必须同时恢复消息契约、三进程生命周期、契约漂移和部署门禁,禁止形成半套状态。 +- 若整体移除脚手架,回到基线 `c04184a6111da0cfad90d1a265dc3cd3a6d90a9c` 的等价内容,并保留既有 `v0.0.7` Schema 历史目录。 + +## 部署回退 + +- Docker 恢复上一组 Web/Server 不可变镜像摘要;源码部署把 `/opt/a-share-analysis/current` 切回上一完整发布目录并重启三个 systemd 服务。 +- `/etc/a-share-analysis/*.env`、本地 `*.env.local`、Redis、MySQL 和 RocketMQ 数据不随应用回退覆盖或删除。 +- 回退后重新执行健康检查、契约门禁和可用性验证。 + +当前尚未创建 `v0.0.8` 标签,因此无需删除或移动标签。用户确认发布后,若发布回退也只能新建更高版本修复或明确的回退提交,不能移动已推送标签。 diff --git a/docs/features/GOV-009-application-scaffold-baseline/screenshots/iterations/app-shell-ready-1440x900-3a8120a.png b/docs/features/GOV-009-application-scaffold-baseline/screenshots/iterations/app-shell-ready-1440x900-3a8120a.png new file mode 100644 index 0000000000000000000000000000000000000000..7aa61091cb6dfe7e0a30757898d8df2e7bf86de1 GIT binary patch literal 34058 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 z zkm(T>Ud3yyv+Pr!Fh`VB8No&eDq+hDpq%A2%-`m-`qF#PvM>&6txGU2@(;auR#C8JCpR>Cl>XoVV6OMbVmXicHzS@odu>>-GmTJzk^ zwi<4X!=a8S8iX9&Cn$)%;=5n3)5doK(jS`g$CeD$#ifdx)T$n-*Jjk`Bj78Gd*;)W z%=1YC)^rj6yX~Z=MPr&@QeW8^D2(URIx&1R@0|Ly&^)0)X5_{RXV~t|M{5y;vdyOp zxU+N&>?-OO@Kb`L9tD5D8Rexk~ZH)5GM!~RNG?K zR|(@Itv5;&uRg27-(nJuGa<@Ex)d0pWWVlqh-M#7ZM&iop3KF-S0JIoWGUvYcUmmT zlG*$)$Nb~#Q4p8zTQaY`Ulsu04$w%1lMf@co<-3JLoN!9-2eMJUzv@6NCkWWn$xbU6fai zVZ;i}%2UU82-jPVZS8Zx7j}H-YJOk)CPJ5gb#uxo+z11X2SS!}c9Kc)A-1QUmXTLN zTqOXNT_qszgBdba&}mZ6BXux6^Z}?^7xCg48!Um06)lOs-aE^aUFVB>r zndQVEE0>k3jtkyC<4wJY9x^nBhK2uPUXaG+5bp_(w#t3ASdEx+hDrMVHyOXr78{ez z!9>Z}B(+83&dj#v@>iD5QU55z#3VGc$ziM&_fqQ(f-x|(RJ3A+Wp}9~hS+}RB#D|; z6m(y{XBS0F4yNL#UIC?f05+#VF+MW-For9C^J*>eiUQ8mTuek^`L{Kw!gjk15i*;9 zqb&{5oq6O_YJ4^$=r|O{=EL+J0bNW$T9UM&=x!b)iUQt=C&(58>X}(q*D*4hQJO0y z6)((1_;E!21*fP=axO&TYj*-*u40+qpt;mFrCef@y;{9#OTD7_!z;a+h(ldg)IF+- z) zsxfbqBWC{_QFW*ll@uk(+urn~O*uMwnMo*6AUKD0rPwul1EQ3AzAE*wW%n%eEvd%m zt^knatpE#{fX6Z9D3xEg)G=!t6-Q-RZ4f4s(^zPs$MhYKk`u*XAiJjIGe^wm=w==r z9FLU01ck{bJJt_{1X5L0)e&!5$!y?oy?>Pf3%T&O00xEhTi`PH6EVf(!^7*v#txl@ zTWX*mdonsAaK$G7h&AGM3=&{rz1x4q4EEtxg=fbS8H?_zVW)AYrzgTs0+ z%%+2raTtN0)3XPR7EbV$T~t+nFM;*SmYb?+;JcI4A0~ASmZ9BhnI_$na$QxHsmRaN zsvi}D*m89>)@krnLw4@_zr5FK5a1{hRLo$5-&*Hm`@^i){DT79Hlp20UYa_&UluzV z$JCSrsUx=qBcG$Nd%-cHKZOL(1BLFyd5d1TmJ6Kj7p3#udQt)orGXI_Wf9i>EWg3jLe>Gm0{x9x< z+O7I8FZ$C#z~N{4pR<;qjG1kLah(~^A#Qt;KYQR&l>L|X%pfA{X8^3|o6C>L7MuvU z1Mk=pIn>GkL@)Y=ivHiJe?9RpC;knEe?{S6E&Mkt{F@*D7hpltNTW6k43^*Pe*yf1 z8~@{y~g?5aS=j_y;lmL5zP81CV@f a+j%LwCf`xa@4WsyqpYN&M7fw@(EkAvxcoZ+ literal 0 HcmV?d00001