forked from wangziqi/gongxue-base
test: add production launch evidence gate
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
- `docs/refactor/taro-frontend-integration.md`:Taro/H5/小程序启动、请求封装、页面/API 映射。
|
||||
- `docs/refactor/taro-h5-deployment.md`:Taro H5 三域名部署、运行时配置、Nginx、CSP、缓存和 CORS 边界。
|
||||
- `docs/refactor/multitenant-auth-security-contract.md`:多租户隔离、鉴权、权限和资源安全红线。
|
||||
- `docs/refactor/production-launch-evidence.template.json`:生产上线证据模板;真实证据填入本地 `production-launch-evidence.json` 后运行 `npm run launch:gate`。
|
||||
|
||||
下一步优先级:
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
- 多租户、鉴权、权限、资源签名和生产安全红线。
|
||||
9. `docs/refactor/content-import-contract.md`
|
||||
- 后台内容导入、题目 JSON、单词、知识手册、分数线、视频的后端校验契约。
|
||||
10. `docs/refactor/production-launch-evidence.template.json`
|
||||
- 上线前证据文件模板;真实生产验收结果填入 `production-launch-evidence.json` 后运行 `npm run launch:gate`,该真实证据文件不入 Git。
|
||||
|
||||
## 当前可进入的前端工作
|
||||
|
||||
@@ -41,6 +43,7 @@
|
||||
- `apps/taro/src/services/api.ts` 现在默认 Supabase JWT 优先、迁移期 `tk_` 兜底;公共接口必须显式 `authMode='none'`。页面不要手写 `Authorization`、`x-tenant-id` 或 `x-user-id`。
|
||||
- H5 可以优先验证 `@supabase/supabase-js` 管理 Auth session;微信小程序端先验证运行时兼容性,业务数据默认仍走 `apps/api`。
|
||||
- H5 生产部署优先用每个静态目录自己的 `runtime-config.json` 配置 `apiBaseUrl`、`supabaseUrl`、`supabasePublishableKey`、`tenantCode`;不要为了换域名重打包,也不要把任何 service role、数据库、支付、短信、对象存储密钥放进该文件。
|
||||
- 上线前需要把三套 H5 构建、`runtime-config.json` 人工复核、真实 Auth/RLS、迁移 dry-run、对象存储、支付对账和 `@codex-security` 结果写入 `production-launch-evidence.json`,并通过 `npm run launch:gate`。
|
||||
- 可以接入租户品牌、已发布主题、公开素材、功能开关和域名/小程序参数解析;学生端只读 `/api/tenant/resolve` 的 `branding.theme/publicAssets`,租户后台草稿走 `/api/tenant-admin/theme`。
|
||||
- 租户后台可以接入角色模板和成员 API:`/api/tenant-admin/role-templates`、`/api/tenant-admin/members`,用于运营、教师、销售、代理等自定义菜单/模块/字段可见性和成员模板绑定。
|
||||
- 租户后台可以接入勋章管理和手动发放:`GET/PUT /api/tenant-admin/badges`、`GET/POST /api/tenant-admin/badge-grants`;学生端用 `GET /api/profile/badges` 展示成就。
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
- 生产 `.env` 模板和 `npm run readiness:production` / `npm run readiness:production:db` 已补,后续上云必须作为验收 gate。
|
||||
- Auth/JWKS 上云后必须临时设置 `AUTH_SMOKE_*` 环境变量并运行 `npm run smoke:auth:remote`,真实 access token 不得写入仓库、前端配置或日志。
|
||||
- 本地/预生产必须同时跑 `npm run test:rls`,它验证运行时 JWT claim 下的租户隔离,和 `readiness:production:db` 的静态 policy 检查互补。
|
||||
- 已补 `npm run launch:gate` 生产上线证据门禁和 `docs/refactor/production-launch-evidence.template.json` 模板;最终切换前必须把 readiness、远程 Auth、RLS、生产 dry-run、导入校验、API/worker/Taro、运行时审计、`@codex-security`、备份/回滚/真实抽样/生产 provider 等证据填入本地 `production-launch-evidence.json` 并通过门禁。
|
||||
- 确认数据库迁移流程、备份恢复、日志、告警。
|
||||
- 准备 API 容器部署和 Supabase 云端/自托管连接方案。
|
||||
|
||||
|
||||
241
docs/refactor/production-launch-evidence.template.json
Normal file
241
docs/refactor/production-launch-evidence.template.json
Normal file
@@ -0,0 +1,241 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"environment": "production",
|
||||
"commit": "replace-with-deployed-git-sha",
|
||||
"target": {
|
||||
"apiBaseUrl": "https://api.example.com",
|
||||
"studentH5Url": "https://www.example.com",
|
||||
"tenantAdminH5Url": "https://admin.example.com",
|
||||
"platformAdminH5Url": "https://console.example.com"
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"id": "readiness.production.env",
|
||||
"status": "pass",
|
||||
"command": "npm run readiness:production -- --json > docs/refactor/launch-artifacts/readiness-production.json",
|
||||
"completedAt": "2026-06-30T10:00:00+08:00",
|
||||
"artifact": "launch-artifacts/readiness-production.json",
|
||||
"summary": {
|
||||
"blocker": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "readiness.production.db",
|
||||
"status": "pass",
|
||||
"command": "npm run readiness:production:db -- --json > docs/refactor/launch-artifacts/readiness-production-db.json",
|
||||
"completedAt": "2026-06-30T10:05:00+08:00",
|
||||
"artifact": "launch-artifacts/readiness-production-db.json",
|
||||
"summary": {
|
||||
"blocker": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "auth.remote-smoke",
|
||||
"status": "pass",
|
||||
"command": "AUTH_SMOKE_REQUIRE_ADMIN_TOKENS=true npm run smoke:auth:remote > docs/refactor/launch-artifacts/auth-remote-smoke.log",
|
||||
"completedAt": "2026-06-30T10:10:00+08:00",
|
||||
"artifact": "launch-artifacts/auth-remote-smoke.log",
|
||||
"summary": {
|
||||
"failed": 0,
|
||||
"requireAdminTokens": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rls.tenant-isolation",
|
||||
"status": "pass",
|
||||
"command": "npm run test:rls > docs/refactor/launch-artifacts/rls-tenant-isolation.log",
|
||||
"completedAt": "2026-06-30T10:20:00+08:00",
|
||||
"artifact": "launch-artifacts/rls-tenant-isolation.log",
|
||||
"summary": {
|
||||
"failed": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "migration.pb-production-dry-run",
|
||||
"status": "pass",
|
||||
"command": "npm run pb:import:dry-run -- --profile=production --json --fail-on-warnings > docs/refactor/launch-artifacts/pb-production-dry-run.json",
|
||||
"completedAt": "2026-06-30T10:30:00+08:00",
|
||||
"artifact": "launch-artifacts/pb-production-dry-run.json",
|
||||
"summary": {
|
||||
"blocker": 0,
|
||||
"warning": 0,
|
||||
"migrationProfile": "production",
|
||||
"requiredCollectionsMissing": 0,
|
||||
"criticalFieldCoverageWarnings": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "migration.pb-import-validate",
|
||||
"status": "pass",
|
||||
"command": "FAIL_ON_WARNINGS=true npm run pb:import:validate > docs/refactor/launch-artifacts/pb-import-validate.log",
|
||||
"completedAt": "2026-06-30T10:40:00+08:00",
|
||||
"artifact": "launch-artifacts/pb-import-validate.log",
|
||||
"summary": {
|
||||
"fail": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "api.integration",
|
||||
"status": "pass",
|
||||
"command": "npm run test:api > docs/refactor/launch-artifacts/api-integration.log",
|
||||
"completedAt": "2026-06-30T10:50:00+08:00",
|
||||
"artifact": "launch-artifacts/api-integration.log",
|
||||
"summary": {
|
||||
"failed": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "worker.assets",
|
||||
"status": "pass",
|
||||
"command": "npm run test:worker:assets > docs/refactor/launch-artifacts/worker-assets.log",
|
||||
"completedAt": "2026-06-30T11:00:00+08:00",
|
||||
"artifact": "launch-artifacts/worker-assets.log",
|
||||
"summary": {
|
||||
"failed": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "worker.commerce",
|
||||
"status": "pass",
|
||||
"command": "npm run test:worker:commerce > docs/refactor/launch-artifacts/worker-commerce.log",
|
||||
"completedAt": "2026-06-30T11:10:00+08:00",
|
||||
"artifact": "launch-artifacts/worker-commerce.log",
|
||||
"summary": {
|
||||
"failed": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "worker.imports",
|
||||
"status": "pass",
|
||||
"command": "npm run test:worker:imports > docs/refactor/launch-artifacts/worker-imports.log",
|
||||
"completedAt": "2026-06-30T11:20:00+08:00",
|
||||
"artifact": "launch-artifacts/worker-imports.log",
|
||||
"summary": {
|
||||
"failed": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "worker.public-banks",
|
||||
"status": "pass",
|
||||
"command": "npm run test:worker:public-banks > docs/refactor/launch-artifacts/worker-public-banks.log",
|
||||
"completedAt": "2026-06-30T11:30:00+08:00",
|
||||
"artifact": "launch-artifacts/worker-public-banks.log",
|
||||
"summary": {
|
||||
"failed": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "taro.check",
|
||||
"status": "pass",
|
||||
"command": "npm run check:taro > docs/refactor/launch-artifacts/taro-check.log",
|
||||
"completedAt": "2026-06-30T11:40:00+08:00",
|
||||
"artifact": "launch-artifacts/taro-check.log",
|
||||
"summary": {
|
||||
"failed": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "taro.build.student",
|
||||
"status": "pass",
|
||||
"command": "npm run build:taro:h5:student > docs/refactor/launch-artifacts/taro-build-student.log",
|
||||
"completedAt": "2026-06-30T11:50:00+08:00",
|
||||
"artifact": "launch-artifacts/taro-build-student.log",
|
||||
"summary": {
|
||||
"failed": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "taro.build.tenant",
|
||||
"status": "pass",
|
||||
"command": "npm run build:taro:h5:tenant > docs/refactor/launch-artifacts/taro-build-tenant.log",
|
||||
"completedAt": "2026-06-30T12:00:00+08:00",
|
||||
"artifact": "launch-artifacts/taro-build-tenant.log",
|
||||
"summary": {
|
||||
"failed": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "taro.build.platform",
|
||||
"status": "pass",
|
||||
"command": "npm run build:taro:h5:platform > docs/refactor/launch-artifacts/taro-build-platform.log",
|
||||
"completedAt": "2026-06-30T12:10:00+08:00",
|
||||
"artifact": "launch-artifacts/taro-build-platform.log",
|
||||
"summary": {
|
||||
"failed": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "audit.runtime",
|
||||
"status": "pass",
|
||||
"command": "npm run audit:runtime > docs/refactor/launch-artifacts/audit-runtime.log",
|
||||
"completedAt": "2026-06-30T12:20:00+08:00",
|
||||
"artifact": "launch-artifacts/audit-runtime.log",
|
||||
"summary": {
|
||||
"critical": 0,
|
||||
"high": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "security.codex-scan",
|
||||
"status": "pass",
|
||||
"command": "@codex-security scan; save findings to docs/refactor/launch-artifacts/codex-security.md",
|
||||
"completedAt": "2026-06-30T12:30:00+08:00",
|
||||
"artifact": "launch-artifacts/codex-security.md",
|
||||
"summary": {
|
||||
"critical": 0,
|
||||
"high": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"attestations": [
|
||||
{
|
||||
"id": "backup.snapshot",
|
||||
"status": "approved",
|
||||
"approver": "replace-with-owner",
|
||||
"approvedAt": "2026-06-30T13:00:00+08:00",
|
||||
"notes": "Production database backup/snapshot ID and restore test notes."
|
||||
},
|
||||
{
|
||||
"id": "rollback.plan",
|
||||
"status": "approved",
|
||||
"approver": "replace-with-owner",
|
||||
"approvedAt": "2026-06-30T13:05:00+08:00",
|
||||
"notes": "Old PocketBase read-only snapshot, DNS rollback path and responsible people are confirmed."
|
||||
},
|
||||
{
|
||||
"id": "migration.sampling",
|
||||
"status": "approved",
|
||||
"approver": "replace-with-owner",
|
||||
"approvedAt": "2026-06-30T13:10:00+08:00",
|
||||
"notes": "Real users, questions, orders, entitlements, assets and learning records sampled."
|
||||
},
|
||||
{
|
||||
"id": "provider.production-accounts",
|
||||
"status": "approved",
|
||||
"approver": "replace-with-owner",
|
||||
"approvedAt": "2026-06-30T13:15:00+08:00",
|
||||
"notes": "Aliyun/Tencent SMS, WeChat/QQ OAuth, WeChat Pay and Alipay callbacks verified."
|
||||
},
|
||||
{
|
||||
"id": "object-storage.production-controls",
|
||||
"status": "approved",
|
||||
"approver": "replace-with-owner",
|
||||
"approvedAt": "2026-06-30T13:20:00+08:00",
|
||||
"notes": "AV/content scanner, CDN anti-leeching, watermark, lifecycle and private asset access sampled."
|
||||
},
|
||||
{
|
||||
"id": "payment.reconciliation-sampling",
|
||||
"status": "approved",
|
||||
"approver": "replace-with-owner",
|
||||
"approvedAt": "2026-06-30T13:25:00+08:00",
|
||||
"notes": "Real payment/refund bills sampled and reconciliation differences handled."
|
||||
},
|
||||
{
|
||||
"id": "frontend.runtime-config-review",
|
||||
"status": "approved",
|
||||
"approver": "replace-with-owner",
|
||||
"approvedAt": "2026-06-30T13:30:00+08:00",
|
||||
"notes": "Student, tenant-admin and platform-admin runtime-config.json contain public values only."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -186,7 +186,16 @@ H5 正式回归时建议把前端登录态切到 Supabase Auth,并观察业务
|
||||
npm run check:taro
|
||||
```
|
||||
|
||||
7. 打开三个域名,确认启动页能解析租户,登录后接口请求使用 `Authorization` 和正确的 `x-tenant-id`。
|
||||
7. 收集生产上线证据并运行 launch gate:
|
||||
|
||||
```bash
|
||||
cp docs/refactor/production-launch-evidence.template.json docs/refactor/production-launch-evidence.json
|
||||
npm run launch:gate -- --evidence docs/refactor/production-launch-evidence.json
|
||||
```
|
||||
|
||||
证据文件只保存命令摘要、artifact 路径、审批人和时间,不保存真实 access token、支付密钥、对象存储密钥或用户隐私明细。真实 artifact 建议放在 `docs/refactor/launch-artifacts/`,该目录不入 Git。
|
||||
|
||||
8. 打开三个域名,确认启动页能解析租户,登录后接口请求使用 `Authorization` 和正确的 `x-tenant-id`。
|
||||
|
||||
## 安全审计边界
|
||||
|
||||
|
||||
Reference in New Issue
Block a user