Files
a-share-analysis/docs/features/GOV-009-application-scaffold-baseline/implementation-plan.md
T

644 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# GOV-009 应用工程脚手架基线实施计划
> **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 的本地配置、固定远程白名单和空库基线能力。
**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 6.4.3、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 6.4.3、plugin-vue 5.2.4、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。该组合兼容开发机 Node 22.11;Vite 7 要求 Node 22.12+,不在本基线采用。
`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`
- Create: `docs/features/GOV-009-application-scaffold-baseline/architecture-review.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: 基于实际实现完成架构复核并向用户论证**
`architecture-review.md` 必须引用真实文件和 GitNexus 流程,说明 Web 分层、Server 三进程、HTTP 与消息契约方向、基础设施边界、部署产物、替代方案、已知限制和后续调整触发条件。将 manifest 保持 `reviewing` 和 `unreleased`,提交并推送复核材料,然后向用户展示论证;未获得用户再次确认时在此停止,不创建标签。
- [ ] **Step 7: 用户确认后执行最终提交、推送与标签**
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 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`。