@@ -1,14 +1,595 @@
# 实施计划
# GOV-009 应用工程脚手架基线 实施计划
书面设计确认后,使用 `superpowers:writing-plans` 将本文件细化为带文件路径、测试先行步骤、验证命令和独立提交点的执行清单。在用户确认详细计划前不生成应用代码。
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
预定阶段:
**Goal: ** 建立可启动、测试、构建和独立部署的 Vue 3 Web 与 NestJS API/Scheduler/Worker 工程,并修正开发 Schema 的本地配置、固定远程白名单和空库基线能力。
1. 修正 Schema 配置加载、固定远程白名单和空库结构基线 。
2. 建立 Node 22、pnpm 10 workspace 和统一质量命令。
3. 建立 Vue 3 Web 工程、测试和 OpenAPI 消费边界。
4. 建立 NestJS API、Scheduler、Worker 与进程级配置。
5. 建立 Server 内 HTTP/消息契约及基础设施 adapter。
6. 对齐 Docker、systemd、生产 Compose 和本地开发文档。
7. 完成全量测试、构建、浏览器验证、Review、版本与发布留痕。
**Architecture: ** 根 pnpm workspace 只编排 `web` 与 `server` 。HTTP 契约由 Server 的 DTO 和 OpenAPI 单向发布,Web 生成自己的客户端;RocketMQ 消息契约只在 Server 内共享。Schema 安全门禁继续固定开发环境与数据库,只增加代码内固定远程主机和 `captured_empty` 状态 。
**Tech Stack: ** Node.js 22.x、pnpm 10.25.0、TypeScript 5.9.3、Vue 3.5.41、Vite 7.3.6、Naive UI 2.44.1、Dart Sass 1.102.0、Vitest 3.2.7、Playwright 1.62.1、NestJS 11.1.29、TypeORM 0.3.31、mysql2 3.23.3、ioredis 5.11.1、MySQL 5.7、Redis、RocketMQ 5.x。
## Global Constraints
- 本机当前 Node.js 是 `20.20.2` ,不得用于正式安装或验收;源码运行前由开发机提供 Node 22,项目不安装 Node。实施期间可使用 `node:22.22.0-bookworm-slim` 执行安装、测试和构建。
- pnpm 固定 `10.25.0` ,根 `package.json` 写入精确 `packageManager` ,依赖提交精确版本及 `pnpm-lock.yaml` ,禁止 `latest` 。
- 所有关键业务边界、配置安全、异常恢复和架构取舍使用准确中文注释,不做逐行翻译式注释。
- Web 不得导入 `server/` ;RocketMQ 消息契约不得进入 Web;数据库实体不得作为 HTTP DTO。
- MySQL 只允许 5.7.x、`a_share_analysis_dev` 、`utf8mb4` 、`utf8mb4_general_ci` 和 `+08:00` 会话时区,禁止 `synchronize: true` 。
- Schema 只读取开发配置,只允许回环地址或固定白名单 `112.192.20.11` ,不得接触生产环境。
- RocketMQ 在 GOV-009 中保持默认关闭;打开但没有已验证客户端时必须失败,不提供内存队列伪降级。
- 每个行为遵循 RED-GREEN-REFACTOR:先运行并确认测试因行为缺失而失败,再写最小实现;测试断言真实结果,不断言 Mock 自身。
- 每个提交使用中文 Conventional Commit 并带 `GOV-009` ,验证后立即精确推送当前分支到 Gitea。
---
### Task 1: 修正 Schema 开发配置与空库基线
**Files: **
- Modify: `tools/database/schema-baseline.mjs`
- Modify: `tools/database/schema-baseline.test.mjs`
- Modify: `docs/governance/development-workflow.md`
- Modify: `docs/governance/code-development-standards.md`
- Modify: `docs/deployment/local-development.md`
**Interfaces: **
- Produces: `resolveFixedDevelopmentEnv(repositoryRoot): string` ,优先返回 `deploy/env/development/api.env.local` ,缺失时退回模板。
- Produces: `assertSafeDevelopmentConfiguration(config): Readonly<SchemaConfig>` ,只接受回环地址或 `112.192.20.11` 。
- Produces: `buildCapturedEmptyArtifacts(metadata): BaselineArtifacts` ,状态为 `captured_empty` 、format v2,只保存结构化清单、`object_counts` 和空结构 SHA-256,不创建 `schema.sql` 。
- Preserves: `assertCurrentReleasedBaselinePublished(repositoryRoot)` 的已提交、已推送和远程标签严格门禁。
- [ ] **Step 1: 写入配置路径和主机白名单失败测试 **
在 `schema-baseline.test.mjs` 增加行为测试:临时目录同时存在 `api.env.local` 与模板时必须选择 local; `112.192.20.11` 被接受;`112.192.20.12` 、域名和空主机被拒绝。期望值直接使用固定路径和错误类型,不复用实现常量。
``` js
assert . equal (
resolveFixedDevelopmentEnv ( repositoryRoot ) ,
path . join ( repositoryRoot , 'deploy/env/development/api.env.local' ) ,
) ;
assert . doesNotThrow ( ( ) =>
assertSafeDevelopmentConfiguration ( {
... validConfig ,
MYSQL _HOST : '112.192.20.11' ,
} ) ,
) ;
assert . throws (
( ) => assertSafeDevelopmentConfiguration ( { ... validConfig , MYSQL _HOST : '112.192.20.12' } ) ,
/固定开发数据库主机白名单/ ,
) ;
```
- [ ] **Step 2: 运行测试并确认 RED **
Run: `node --test --test-name-pattern='配置路径|远程开发库白名单' tools/database/schema-baseline.test.mjs`
Expected: FAIL,原因分别为 `api.env.local` 未被选择、固定远程地址仍被拒绝。
- [ ] **Step 3: 实现固定配置解析与白名单 **
在代码内使用不可变集合,不读取动态白名单变量:
``` js
const DEVELOPMENT _HOSTS = new Set ( [
'127.0.0.1' ,
'localhost' ,
'::1' ,
'112.192.20.11' ,
] ) ;
export function resolveFixedDevelopmentEnv ( repositoryRoot ) {
const localEnv = path . join ( repositoryRoot , 'deploy' , 'env' , 'development' , 'api.env.local' ) ;
if ( existsSync ( localEnv ) ) return localEnv ;
return path . join ( repositoryRoot , 'deploy' , 'env' , 'development' , 'api.env.example' ) ;
}
```
错误文案只输出主机,不输出用户名或密码。导出 `resolveFixedDevelopmentEnv` 供真实行为测试调用。
- [ ] **Step 4: 写入空库 captured 失败测试 **
模拟 metadata 查询返回合法 MySQL 5.7 元数据,`mysqldump` 返回空结构;断言结果为 `captured_empty` 、format v2、文件集合只有 `manifest.yaml` 、`object_counts` 全为零、摘要为 SHA-256 空串,且没有 `schema.sql` 。另加 fixture 回归测试证明既有 format v1 的 `not_applicable` 与 captured 清单仍可验证,并且当前运行配置改变不会静默重写历史。
- [ ] **Step 5: 运行测试并确认 RED **
Run: `node --test --test-name-pattern='空库|历史不适用基线' tools/database/schema-baseline.test.mjs`
Expected: FAIL,现有实现以“真实 Schema SQL 不能为空”拒绝空库,并重新读取当前配置否定历史 N/A 清单。
- [ ] **Step 6: 实现 `captured_empty` 和不可变历史语义 **
`buildCapturedArtifacts` 在规范化 SQL 为空时调用:
``` js
export function buildCapturedEmptyArtifacts ( metadata ) {
assertMysql57Metadata ( metadata ) ;
return Object . freeze ( {
status : 'captured_empty' ,
files : Object . freeze ( {
'manifest.yaml' : buildManifest ( {
... metadata ,
status : 'captured_empty' ,
captured : false ,
schemaSha256 : sha256 ( '' ) ,
} ) ,
} ) ,
} ) ;
}
```
新生成的 format v2 Manifest 增加 `object_counts` ,空库固定为零;普通 captured 从导出 SQL 解析并记录非负计数。既有 format v1 的 `objects` 布尔结构保持只读兼容,验证器按格式版本与状态校验精确文件集合。已发布 N/A 清单只验证生成事实,不在 `schema:verify` 阶段用当前 env 改写历史;下一版本生成时才读取当前 local 配置。
- [ ] **Step 7: 验证真实固定开发库 **
先运行只读元数据查询确认目标仍为空,再使用项目支持的 MySQL 5.7 原生客户端或 Docker 客户端执行一次临时目录生成测试;不得覆盖 `database/schema-baselines/v0.0.7/` 。
Expected: 服务端 `5.7.42` 、数据库 `a_share_analysis_dev` 、字符集 `utf8mb4` 、排序规则 `utf8mb4_general_ci` 、对象计数 0。
- [ ] **Step 8: 全量 Schema 回归、影响检测、提交和推送 **
Run:
``` bash
npm run test:schema-baseline
npm run schema:verify
npm run test:governance
git diff --check
```
Expected: 全部 PASS, `v0.0.7` 清单逐字节不变。运行 GitNexus `detect_changes` ,风险只能落在本报告列出的 Schema/governance 流程。
Commit: `feat(GOV-009): 支持固定开发库结构基线`
Push: `git push origin "$(git branch --show-current)"`
---
### Task 2: 建立 Node 22 与 pnpm workspace 质量基线
**Files: **
- Modify: `package.json`
- Create: `pnpm-workspace.yaml`
- Create: `pnpm-lock.yaml`
- Create: `.npmrc`
- Create: `eslint.config.mjs`
- Create: `.prettierrc.json`
- Create: `tools/runtime/verify-runtime.mjs`
- Create: `tools/runtime/verify-runtime.test.mjs`
**Interfaces: **
- Produces: `assertSupportedRuntime({ nodeVersion, pnpmVersion })` 。
- Produces root scripts: `check:runtime` , `lint` , `format:check` , `typecheck` , `test` , `build` , `verify` 。
- [ ] **Step 1: 写运行时门禁失败测试 **
覆盖 Node `22.0.0` 与 pnpm `10.25.0` 通过,Node 20/24、pnpm 9/11 拒绝;错误消息包含实际版本,不执行安装。
- [ ] **Step 2: 运行并确认 RED **
Run: `node --test tools/runtime/verify-runtime.test.mjs`
Expected: FAIL with `ERR_MODULE_NOT_FOUND` 。
- [ ] **Step 3: 实现运行时门禁和 workspace **
``` js
export function assertSupportedRuntime ( { nodeVersion , pnpmVersion } ) {
if ( Number ( nodeVersion . split ( '.' ) [ 0 ] ) !== 22 ) {
throw new Error ( ` Node.js 必须为 22.x,实际 ${ nodeVersion } ` ) ;
}
if ( Number ( pnpmVersion . split ( '.' ) [ 0 ] ) !== 10 ) {
throw new Error ( ` pnpm 必须为 10.x,实际 ${ pnpmVersion } ` ) ;
}
}
```
`pnpm-workspace.yaml` 只包含:
``` yaml
packages :
- web
- server
```
根 `package.json` 写入 `engines.node: 22.x` 、`packageManager: pnpm@10.25.0` ,根脚本通过 `pnpm --filter` 编排两个工程,不创建业务依赖。
- [ ] **Step 4: 在 Node 22 容器安装精确依赖并生成锁文件 **
Run: `docker run --rm --user "$(id -u):$(id -g)" -e COREPACK_HOME=/tmp/corepack -v "$PWD:/workspace" -w /workspace node:22.22.0-bookworm-slim sh -lc 'corepack pnpm install --lockfile-only'`
Expected: 生成单一 `pnpm-lock.yaml` ,无 `latest` ,宿主项目文件归当前用户可读写。
- [ ] **Step 5: 验证、提交和推送 **
Run: Node 22 容器中的 `corepack pnpm check:runtime` 、运行时单测、`git diff --check` 。
Commit: `build(GOV-009): 建立 Node 与工作区基线`
Push current branch and verify local/remote SHA equality.
---
### Task 3: 建立 Vue 3 Web 工程
**Files: **
- Delete: `web/.gitkeep`
- Create: `web/package.json`
- Create: `web/tsconfig.json`
- Create: `web/tsconfig.node.json`
- Create: `web/vite.config.ts`
- Create: `web/vitest.config.ts`
- Create: `web/playwright.config.ts`
- Create: `web/index.html`
- Create: `web/src/main.ts`
- Create: `web/src/app/App.vue`
- Create: `web/src/app/router.ts`
- Create: `web/src/app/providers.ts`
- Create: `web/src/styles/index.scss`
- Create: `web/src/env.d.ts`
- Create: `web/src/app/App.test.ts`
- Create: `web/tests/smoke.spec.ts`
**Interfaces: **
- Produces: `createAppRouter()` 和 `installAppProviders(app)` 。
- Consumes: only `VITE_APP_ENV` , `VITE_API_BASE_URL` , `VITE_APP_TITLE` from `web.env.local` 。
- Preserves: FEAT-MO-001 将在 `src/features/market-overview/` 添加首屏,不需要重构应用入口。
- [ ] **Step 1: 写 Web 应用外壳失败测试 **
使用真实 Pinia、Router 和 Naive UI provider 挂载 `App.vue` ,断言应用标题和主内容区域可访问;禁止 mock 子组件。
- [ ] **Step 2: 运行并确认 RED **
Run: `pnpm --filter @a-share-analysis/web test --run`
Expected: FAIL,因为 Web 包和应用入口尚不存在。
- [ ] **Step 3: 创建精确依赖和最小应用外壳 **
Web dependencies 精确锁定 Vue 3.5.41、Vue Router 4.6.4、Pinia 3.0.4、Naive UI 2.44.1; dev dependencies 锁定 Vite 7.3.6、plugin-vue 6.0.8、TypeScript 5.9.3、vue-tsc 3.3.9、Sass 1.102.0、Vitest 3.2.7、Vue Test Utils 2.4.11、jsdom 26.1.0、Playwright 1.62.1。
`main.ts` 固定从项目外部加载开发 env:
``` ts
const app = createApp ( App ) ;
installAppProviders ( app ) ;
app . mount ( '#app' ) ;
```
Vite `envDir` 指向 `../deploy/env/development` ,开发模式通过 `--mode web` 加载 `web.env.local` ;生产构建只接受部署注入的 `VITE_` 值。样式仅建立字体、颜色 token、页面高度和可访问焦点基线,不提前设计市场概览。
- [ ] **Step 4: 运行单测、类型和构建 **
Run:
``` bash
pnpm --filter @a-share-analysis/web test --run
pnpm --filter @a-share-analysis/web typecheck
pnpm --filter @a-share-analysis/web build
```
Expected: PASS, `web/dist` 不包含 `MYSQL_` 、`REDIS_` 、`ROCKETMQ_` 、密码或 Server 消息字段。
- [ ] **Step 5: 写 Playwright 冒烟测试并确认 RED/GREEN **
测试访问 `/` ,验证 `main` 存在、无页面错误和水平溢出,视口覆盖 `1440x900` 与 `390x844` 。先在未配置 webServer 时确认失败,再配置 Vite webServer 并通过。
- [ ] **Step 6: 提交和推送 **
Commit: `feat(GOV-009): 建立 Vue 前端工程基线`
Push current branch and verify remote SHA.
---
### Task 4: 建立 NestJS 三进程与最小权限配置
**Files: **
- Delete: `server/.gitkeep`
- Create: `server/package.json`
- Create: `server/nest-cli.json`
- Create: `server/tsconfig.json`
- Create: `server/tsconfig.build.json`
- Create: `server/apps/api/src/main.ts`
- Create: `server/apps/api/src/api.module.ts`
- Create: `server/apps/scheduler/src/main.ts`
- Create: `server/apps/scheduler/src/scheduler.module.ts`
- Create: `server/apps/worker/src/main.ts`
- Create: `server/apps/worker/src/worker.module.ts`
- Create: `server/libs/config/src/runtime-config.ts`
- Create: `server/libs/config/src/runtime-config.test.ts`
- Create: `server/libs/config/src/load-process-env.ts`
- Create: `server/test/process-smoke.test.ts`
**Interfaces: **
- Produces: `loadApiConfig` , `loadSchedulerConfig` , `loadWorkerConfig` ,返回冻结且不含未知字段的配置。
- Produces: `bootstrapApi` , `bootstrapScheduler` , `bootstrapWorker` ,支持依赖注入端口和优雅关闭。
- Consumes: `deploy/env/development/<process>.env.local` only in development; production only consumes `process.env` injected by Compose/systemd.
- [ ] **Step 1: 写进程配置失败测试 **
分别证明 API 不要求 Scheduler/Worker 变量、Scheduler 不读取 MySQL 密码、Worker 不要求 API 端口;MySQL 启用时缺少时区或密码失败;日志序列化不包含敏感值。
- [ ] **Step 2: 运行并确认 RED **
Run: `pnpm --filter @a-share-analysis/server test -- runtime-config.test.ts`
Expected: FAIL,因为配置模块不存在。
- [ ] **Step 3: 实现进程级配置 **
使用纯函数解析、显式条件必填和对象冻结。公共字段保持在同一文件连续阅读,避免每个变量一个文件。MySQL 配置接口固定:
``` ts
export interface MysqlRuntimeConfig {
enabled : boolean ;
host : string ;
port : number ;
database : 'a_share_analysis_dev' | string ;
username : string ;
password : string ;
charset : 'utf8mb4' ;
collation : 'utf8mb4_general_ci' ;
timezone : '+08:00' ;
}
```
生产配置不从仓库路径加载;开发入口根据自身 `import.meta.url` 定位唯一 local 文件,不接受用户传入 env 路径。
- [ ] **Step 4: 写三进程启动关闭失败测试 **
测试调用三个 bootstrap 函数,使用真实 Nest application/context 和只在基础设施端口注入的轻量 fake;断言 API 监听给定端口,Scheduler/Worker 不打开 HTTP 端口,三者收到关闭信号后释放资源。
- [ ] **Step 5: 运行并确认 RED 后实现入口 **
API 使用 `NestFactory.create` ; Scheduler/Worker 使用 `NestFactory.createApplicationContext` 。所有进程启用 shutdown hooks,启动日志只记录进程名、环境和非敏感端口。
- [ ] **Step 6: 验证构建产物路径 **
Run:
``` bash
pnpm --filter @a-share-analysis/server test
pnpm --filter @a-share-analysis/server typecheck
pnpm --filter @a-share-analysis/server build
test -f server/dist/apps/api/main.js
test -f server/dist/apps/scheduler/main.js
test -f server/dist/apps/worker/main.js
```
Expected: 与现有 systemd/Compose 命令完全一致。
- [ ] **Step 7: 提交和推送 **
Commit: `feat(GOV-009): 建立 NestJS 三进程基线`
---
### Task 5: 建立后端契约、基础设施与 API 健康检查
**Files: **
- Create: `server/libs/http-contracts/src/health.dto.ts`
- Create: `server/libs/message-contracts/src/message-envelope.ts`
- Create: `server/libs/message-contracts/src/message-envelope.test.ts`
- Create: `server/libs/infrastructure/src/message-bus.adapter.ts`
- Create: `server/libs/infrastructure/src/disabled-message-bus.adapter.ts`
- Create: `server/libs/infrastructure/src/redis-health.client.ts`
- Create: `server/libs/infrastructure/src/mysql-health.client.ts`
- Create: `server/libs/infrastructure/src/typeorm-options.ts`
- Create: `server/apps/api/src/health.controller.ts`
- Create: `server/apps/api/src/health.service.ts`
- Create: `server/apps/api/test/health.e2e-spec.ts`
- Create: `server/tools/export-openapi.ts`
- Create: `server/openapi/openapi.json`
- Modify: `deploy/env/development/api.env.example`
- Modify: `deploy/env/development/worker.env.example`
- Modify: `deploy/env/production/api.env.example`
- Modify: `deploy/env/production/worker.env.example`
- Modify ignored local API/Worker env files to retain `MYSQL_TIMEZONE=+08:00` without staging them.
**Interfaces: **
- Produces: `GET /api/v1/health -> HealthResponseDto` 。
- Produces: `MessageEnvelope<TPayload>` with `version` , `eventType` , `idempotencyKey` , `occurredAt` , `payload` 。
- Produces: `MessageBusAdapter.publish/subscribe/close` , disabled adapter rejects publish/subscribe with a typed error。
- Produces: TypeORM options with `synchronize: false` , MySQL 5.7 charset/collation/timezone.
- [ ] **Step 1: 写消息契约与禁用 adapter 失败测试 **
覆盖有效信封、缺失幂等键、非法时间、未知版本;disabled adapter 的发布/订阅必须拒绝且不缓存消息。
- [ ] **Step 2: RED 后实现 Server 内部契约 **
``` ts
export interface MessageEnvelope < TPayload > {
version : 1 ;
eventType : string ;
idempotencyKey : string ;
occurredAt : string ;
payload : TPayload ;
}
```
验证函数返回规范化冻结对象;Web 无路径别名能够解析此目录。
- [ ] **Step 3: 写健康检查 E2E 失败测试 **
启动真实 Nest API,注入确定性的 Redis/MySQL health ports,调用 `/api/v1/health` ,断言 HTTP 200、进程状态、Redis/MySQL 分项和 RocketMQ `disabled` ;依赖失败时 HTTP 503 且错误体不包含凭据。
- [ ] **Step 4: RED 后实现只读健康客户端 **
Redis 只执行 `PING` ; MySQL 使用 `SELECT VERSION(), DATABASE(), @@character_set_database, @@collation_database, @@session.time_zone` ,连接建立时设置 `+08:00` 。TypeORM options 明确:
``` ts
{
type : 'mysql' ,
connectorPackage : 'mysql2' ,
charset : 'utf8mb4' ,
timezone : '+08:00' ,
synchronize : false ,
migrationsRun : false ,
}
```
如果 TypeORM 实际类型不支持 `connectorPackage` ,删除该字段并通过安装的 `mysql2` 驱动解析;不得用类型断言掩盖错误。
- [ ] **Step 5: 写 OpenAPI 导出失败测试并实现 **
导出命令从真实 Controller/DTO 生成排序稳定的 `server/openapi/openapi.json` 。测试解析 JSON,断言 `/api/v1/health` 与 `HealthResponseDto` 存在;再次生成后 `git diff --exit-code -- server/openapi/openapi.json` 必须通过。
- [ ] **Step 6: 更新环境模板并运行契约测试 **
四份 MySQL 模板增加 `MYSQL_TIMEZONE=+08:00` ,开发/生产同进程键集合继续一致,生产密码保持空值。
Run: Server tests, `npm run test:environment-baseline` , typecheck, OpenAPI drift check.
- [ ] **Step 7: 使用真实开发基础设施只读冒烟 **
Redis DB 0/1/2 分别 PING; API/Worker MySQL 连接验证版本、库名、字符集、排序规则和时区。RocketMQ 只检查既有 NameServer/Broker/Proxy 管理链路,Node adapter 保持 disabled。
- [ ] **Step 8: 提交和推送 **
Commit: `feat(GOV-009): 建立后端契约与健康检查`
---
### Task 6: 生成 Web OpenAPI 客户端并强制边界
**Files: **
- Create: `web/src/api/generated/schema.ts`
- Create: `web/src/api/client.ts`
- Create: `web/src/api/adapters/health.adapter.ts`
- Create: `web/src/api/adapters/health.adapter.test.ts`
- Create: `tools/boundaries/verify-project-boundaries.mjs`
- Create: `tools/boundaries/verify-project-boundaries.test.mjs`
- Modify: `web/package.json`
- Modify: root `package.json`
**Interfaces: **
- Consumes: `server/openapi/openapi.json` as the only cross-project contract artifact.
- Produces: `createApiClient(baseUrl)` using `openapi-fetch` and generated `paths` type.
- Produces: `toHealthViewModel(dto): HealthViewModel` ,页面模型不暴露后端枚举细节。
- [ ] **Step 1: 写项目边界失败测试 **
在临时 fixture 中创建 Web 对 `server/libs` 的 import 和消息字段,运行真实边界检查器并断言失败;合法的 `web/src/api/generated/schema.ts` 通过。检查器解析 TypeScript import/export specifier,不用简单 grep 代替模块边界。
- [ ] **Step 2: RED 后实现边界检查器 **
检查 Web 源文件的静态 import、动态 import 和路径别名,拒绝解析到仓库 `server/` ;检查生成 OpenAPI schema 不包含 `MessageEnvelope` 或 `message-contracts` 。
- [ ] **Step 3: 生成 Web 客户端 **
Run: `openapi-typescript ../server/openapi/openapi.json -o src/api/generated/schema.ts`
`client.ts` 只包装 base URL 和错误归一化;健康 adapter 使用手写页面模型和显式映射,不把生成 DTO 传入 Vue 组件。
- [ ] **Step 4: 写 adapter 失败测试并实现 **
使用手写完整 DTO fixture,断言 healthy/degraded/unavailable 映射和未知依赖状态拒绝。先运行确认函数缺失失败,再实现最小映射。
- [ ] **Step 5: 运行边界、生成漂移、Web 测试和构建 **
Run root `check:contracts` twice; second run must leave clean diff. Scan `web/dist` for Server env keys, message fields and secrets; expected no match.
- [ ] **Step 6: 提交和推送 **
Commit: `feat(GOV-009): 建立前端 OpenAPI 客户端边界`
---
### Task 7: 对齐 Docker、Compose、systemd 与源码开发命令
**Files: **
- Create: `web/Dockerfile`
- Create: `web/nginx.conf`
- Create: `server/Dockerfile`
- Modify: `deploy/compose/production.yml.example`
- Modify: `deploy/nginx/a-share-analysis.conf.example`
- Modify: `deploy/systemd/a-share-api.service.example`
- Modify: `deploy/systemd/a-share-scheduler.service.example`
- Modify: `deploy/systemd/a-share-worker.service.example`
- Modify: `docs/deployment/local-development.md`
- Modify: `docs/deployment/docker-compose-production.md`
- Modify: `docs/deployment/ubuntu-source-production.md`
- Modify: `README.md`
- Modify: `tools/governance/environment-baseline.test.mjs`
**Interfaces: **
- Produces immutable Web image serving `/` and proxying `/api/` according to deployment contract.
- Produces one Server image with commands `dist/apps/api/main.js` , `dist/apps/scheduler/main.js` , `dist/apps/worker/main.js` .
- [ ] **Step 1: 扩展部署行为测试并确认 RED **
使用 `docker build` 和 `docker compose config` 作为真实行为测试;验证镜像入口文件存在、非 root 运行、生产 Compose 必填不可变镜像、systemd ExecStart 对应真实 dist 文件。不要只断言文档文本。
- [ ] **Step 2: 实现多阶段 Dockerfile **
Builder 使用固定 Node 22.22.0 与 pnpm 10.25.0; Server runtime 只复制生产依赖和 dist,使用非 root 用户;Web runtime 使用固定 Nginx 镜像并复制 dist。镜像层不得复制 `deploy/env/development/*.local` 。
- [ ] **Step 3: 更新双部署说明 **
本地命令明确 Node 22 前置、`pnpm install --frozen-lockfile` 、四进程启动顺序和 env 路径;生产说明分别给出 Compose 与 Ubuntu/systemd 的构建、迁移、启动、健康检查和回退命令。不得记录真实开发密码或远程库账号。
- [ ] **Step 4: 构建并启动容器冒烟 **
Build Web/Server images with local-only tags. 使用临时生产 env(无真实生产密钥)验证配置缺失会失败;使用开发只读配置只启动 API 健康检查,不启动 Scheduler/Worker 的 RocketMQ 消费。
- [ ] **Step 5: 提交和推送 **
Commit: `build(GOV-009): 对齐应用部署与运行入口`
---
### Task 8: 完成全量验证、Review 与发布留痕
**Files: **
- Update: `docs/features/GOV-009-application-scaffold-baseline/manifest.yaml`
- Create: `docs/features/GOV-009-application-scaffold-baseline/gitnexus/detected-changes.md`
- Create: `docs/features/GOV-009-application-scaffold-baseline/gitnexus/after.md`
- Create: `docs/features/GOV-009-application-scaffold-baseline/gitnexus/comparison.md`
- Create: `docs/features/GOV-009-application-scaffold-baseline/qa/test-results.md`
- Create: `docs/features/GOV-009-application-scaffold-baseline/qa/visual-qa.md`
- Create: `docs/features/GOV-009-application-scaffold-baseline/review/code-review.md`
- Create: `docs/features/GOV-009-application-scaffold-baseline/review/standards-review.md`
- Create: `docs/features/GOV-009-application-scaffold-baseline/release-notes.md`
- Create: `docs/features/GOV-009-application-scaffold-baseline/rollback.md`
- Modify: `VERSION`
- Modify: `CHANGELOG.md`
**Interfaces: **
- Produces: reproducible evidence for tests, builds, browser screenshots, GitNexus changes, reviews, release and rollback.
- [ ] **Step 1: 在固定 Node 22 环境运行完整验证 **
Run root `pnpm verify` , all four governance suites, `schema:verify` , Web Playwright at desktop/mobile, both Docker builds, development and production Compose validation, secret scan and `git diff --check` .
Expected: zero failures and zero unexpected warnings. Save exact commands, versions and result counts in `qa/test-results.md` .
- [ ] **Step 2: 保存视觉证据 **
启动 Web/API 本地服务,Playwright 保存 `1440x900` 与 `390x844` 截图;检查无空白画面、溢出、重叠和控制台错误。GOV-009 只验证应用外壳,不评价市场概览视觉质量。
- [ ] **Step 3: 执行 GitNexus 变更检测和结束门禁 **
Run GitNexus `detect_changes` ,核对新增 API、Scheduler、Worker、OpenAPI 和 Web 流程,记录与开发前 23 条流程的差异。运行 `npm run governance:finish -- GOV-009 application-scaffold-baseline` 。
- [ ] **Step 4: 独立代码与规范 Review **
代码 Review 检查配置泄漏、MySQL 5.7、进程生命周期、契约方向、Docker 产物和真实测试。规范 Review 逐条核对中文注释、文件拆分、中文提交、GitNexus 和文档追溯。两份报告必须包含精确行:
``` text
最终结论:passed
```
- [ ] **Step 5: 更新版本、发布说明和回退 **
将版本提升到 `0.0.8` ,说明工程能力而不声称市场概览已实现。回退说明不得删除远程数据库或 Redis 数据,只回退应用代码和镜像。
- [ ] **Step 6: 最终提交、推送与标签 **
Commit: `docs(GOV-009): 封存应用脚手架验证结果`
Push current branch, compare local and remote SHA, create annotated tag `v0.0.8` , then run `git push origin v0.0.8` 。禁止 `--tags` 和任何 force 选项。
- [ ] **Step 7: 刷新 GitNexus 并确认干净状态 **
Run `npx gitnexus status` ; stale 时运行 analyze。最终要求工作区干净、`main` 与 `origin/main` 一致、远程 `v0.0.8^{}` 指向本地标签提交。