chore: register work review brand migration
This commit is contained in:
parent
cda8ba4f78
commit
a2e7d197cb
@ -91,7 +91,8 @@ RUN test -s "/app/public/brand/header-logo.png" && \
|
||||
test -f "/app/prisma/migrations/20260828_add_task_detail_change_approvals/migration.sql" && \
|
||||
test -f "/app/prisma/migrations/20260829_add_recoverable_api_key_secret/migration.sql" && \
|
||||
test -f "/app/prisma/migrations/20260831_add_private_task_detail/migration.sql" && \
|
||||
test -f "/app/prisma/migrations/20260902_add_employee_roster_history/migration.sql"
|
||||
test -f "/app/prisma/migrations/20260902_add_employee_roster_history/migration.sql" && \
|
||||
test -f "/app/prisma/migrations/20260903_add_work_review_brand/migration.sql"
|
||||
|
||||
# 这两个动态路由负责兜底访问本地上传文件;如果它们在构建上下文里丢失,
|
||||
# 线上音频/视频就会直接 404。这里提前失败,避免再把缺路由的镜像发出去。
|
||||
@ -216,6 +217,7 @@ COPY --from=builder --chown=root:root /app/prisma/migrations/20260828_add_task_d
|
||||
COPY --from=builder --chown=root:root /app/prisma/migrations/20260829_add_recoverable_api_key_secret ./prisma/migrations/20260829_add_recoverable_api_key_secret
|
||||
COPY --from=builder --chown=root:root /app/prisma/migrations/20260831_add_private_task_detail ./prisma/migrations/20260831_add_private_task_detail
|
||||
COPY --from=builder --chown=root:root /app/prisma/migrations/20260902_add_employee_roster_history ./prisma/migrations/20260902_add_employee_roster_history
|
||||
COPY --from=builder --chown=root:root /app/prisma/migrations/20260903_add_work_review_brand ./prisma/migrations/20260903_add_work_review_brand
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/lib ./lib
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/scripts/public-traffic-rollup-from-nginx.py ./scripts/public-traffic-rollup-from-nginx.py
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/scripts/collapse-performance-survey-template-versions.mjs ./scripts/collapse-performance-survey-template-versions.mjs
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
## 结论
|
||||
|
||||
当前应用发布工具验证十八份目标 migration。普通部署脚本**只读验证,不会自动修改数据库**。只有独立 migrator 在同时拿到精确 Git 提交、数据库身份和备份凭证后,才允许执行 `prisma migrate deploy`。
|
||||
当前应用发布工具验证十九份目标 migration。普通部署脚本**只读验证,不会自动修改数据库**。只有独立 migrator 在同时拿到精确 Git 提交、数据库身份和备份凭证后,才允许执行 `prisma migrate deploy`。
|
||||
|
||||
目标 migration:
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
- `20260829_add_recoverable_api_key_secret`
|
||||
- `20260831_add_private_task_detail`
|
||||
- `20260902_add_employee_roster_history`
|
||||
- `20260903_add_work_review_brand`
|
||||
|
||||
它们新增可空租约字段/索引、修改未来 Logo 默认值、精确把 `system_settings` 品牌两列中的退役 `s3.1080p.hk` 值改为本地品牌图,并把 `materials_uploadId_key` 重建为“`uploadId` 非空且素材未软删除”范围内的唯一 btree 索引;同时新增人员组织树、素材托管字段、素材不完整空壳标记、账号—人员绑定表、钉钉流程快照字段,以及任务详情图片变更审批单和通知 outbox。审批迁移只新增表、约束与索引,不改写现有素材和资产行。`20260829_add_recoverable_api_key_secret` 只新增 `api_keys.secret_ciphertext` / `secret_nonce` / `secret_auth_tag` 三个可空加密列、`api_key_reveal_audits` 审计表和 `api_key_id+created_at`、`viewer_user_id+created_at` 两个非唯一 btree 索引,不回填旧 key。`20260831_add_private_task_detail` 只新增 `materials.task_detail_demand_type`、`materials.task_detail_private default false`、`material_permission_pack_items.can_access_private default false`,并加上 `materials_private_optimization_check`,确保私密任务详情只能落在“任务-详情 + 优化”语义下。`20260902_add_employee_roster_history` 新增员工完整花名册的时间快照表及查询索引,团队产能按每周结束前最近一份快照统计在职人数;不会用当前花名册回填没有快照的历史周。新增 `form_fields` 后会将同步游标重置一次,让下一轮同步完整补齐明细字段。索引迁移会先检测未删除素材中的重复 `uploadId`;人员绑定迁移会先检测一个钉钉身份对应多个账号的冲突。任一预检失败都会直接停止,不会猜测性合并、删除素材或留下一半回填。迁移没有自动 down;执行失败时停止发布,恢复依赖执行前的数据库备份。
|
||||
|
||||
@ -40,7 +41,7 @@
|
||||
1. 构建并固定同一 exact SHA/digest 的 runner 与 migrator;
|
||||
2. legacy bootstrap `Audit`;
|
||||
3. legacy bootstrap `Apply`,外部验收 503,确认旧容器停止且保留;
|
||||
4. 在同一维护窗内运行本文的真实备份、隔离恢复、十八条目标 migration 和迁移后审计;
|
||||
4. 在同一维护窗内运行本文的真实备份、隔离恢复、十九条目标 migration 和迁移后审计;
|
||||
5. 仅在所有门禁通过后,运行普通同 SHA 应用部署。
|
||||
|
||||
顺序不得改为“先 migration,后 bootstrap”。本文备份工具的生产前后摘要若有变化,说明维护窗内仍可能存在写入,会直接失败且不生成 receipt。
|
||||
@ -364,7 +365,7 @@ docker run --rm \
|
||||
"$MIGRATOR_IMAGE" --apply
|
||||
```
|
||||
|
||||
工具会在修改数据库前先验证审计目录可执行“新建→原子改名→删除”,再依次完成:核对镜像 SHA、配置端口与数据库实际端口、核对机器生成的 receipt 和完整备份产物、拒绝未知待执行/失败 migration、执行 `prisma migrate deploy`,最后核对十八条目标 migration 的数据库 checksum、完成状态、关键人员组织表、素材空壳标记、钉钉流程快照表及需求类型和表单字段、员工花名册历史快照表、字段类型与可空性、`api_keys` 三个可空加密列、`api_key_reveal_audits` 关键列、租约/需求类型/API Key reveal 审计索引的精确列顺序、`materials_uploadId_key` 的唯一性/btree/单列/有效就绪状态/active-only 谓词、Logo 默认值,以及 `materials.task_detail_demand_type` 无默认值、`materials.task_detail_private` / `material_permission_pack_items.can_access_private` 默认值为 `false`,并用稳定语义核对 `materials_private_optimization_check`,确认私密任务详情仍被精确限制在“任务-详情 + 优化”条件下;最后原子写审计 JSON。
|
||||
工具会在修改数据库前先验证审计目录可执行“新建→原子改名→删除”,再依次完成:核对镜像 SHA、配置端口与数据库实际端口、核对机器生成的 receipt 和完整备份产物、拒绝未知待执行/失败 migration、执行 `prisma migrate deploy`,最后核对十九条目标 migration 的数据库 checksum、完成状态、关键人员组织表、素材空壳标记、钉钉流程快照表及需求类型和表单字段、员工花名册历史快照表、字段类型与可空性、`api_keys` 三个可空加密列、`api_key_reveal_audits` 关键列、租约/需求类型/API Key reveal 审计索引的精确列顺序、`materials_uploadId_key` 的唯一性/btree/单列/有效就绪状态/active-only 谓词、Logo 默认值,以及 `materials.task_detail_demand_type` 无默认值、`materials.task_detail_private` / `material_permission_pack_items.can_access_private` 默认值为 `false`,并用稳定语义核对 `materials_private_optimization_check`,确认私密任务详情仍被精确限制在“任务-详情 + 优化”条件下;最后原子写审计 JSON。
|
||||
|
||||
apply 成功后立即固定审计文件本身;后续“迁移批次部署”读取这一个文件,不能手工重写:
|
||||
|
||||
@ -380,7 +381,7 @@ test -n "$MIGRATION_AUDIT_SHA256"
|
||||
只有以下全部成立,才进入应用首次安全上线步骤:
|
||||
|
||||
- `applied.json` 的 `verified=true`;
|
||||
- 十八条 target migration 均 `completed=true`,source/database checksum 一致;
|
||||
- 十九条 target migration 均 `completed=true`,source/database checksum 一致;
|
||||
- `_prisma_migrations.finished_at` 非空、`rolled_back_at` 为空;
|
||||
- 视频租约列/索引、任务详情作业租约列/索引、active-only `uploadId` 唯一索引、品牌默认值精确匹配;
|
||||
- 再次运行 `--audit` 仍通过;
|
||||
|
||||
@ -1511,6 +1511,7 @@ const expectedMigrationNames = new Set([
|
||||
'20260829_add_recoverable_api_key_secret',
|
||||
'20260831_add_private_task_detail',
|
||||
'20260902_add_employee_roster_history',
|
||||
'20260903_add_work_review_brand',
|
||||
]);
|
||||
if (migrations.length !== expectedMigrationNames.size
|
||||
|| migrations.some((item) => !expectedMigrationNames.has(item && item.name)
|
||||
|
||||
@ -27,6 +27,7 @@ const TARGET_MIGRATIONS = Object.freeze([
|
||||
'20260829_add_recoverable_api_key_secret',
|
||||
'20260831_add_private_task_detail',
|
||||
'20260902_add_employee_roster_history',
|
||||
'20260903_add_work_review_brand',
|
||||
])
|
||||
const VERIFIED_BACKUP_GENERATOR = 'scripts/create-verified-production-backup.mjs'
|
||||
const VERIFIED_BACKUP_MAINTENANCE_CONFIRMATION = 'NO_WRITES_DURING_SNAPSHOT'
|
||||
|
||||
@ -343,6 +343,7 @@ test('target evidence rejects nullable logo columns and structurally wrong index
|
||||
['20260829_add_recoverable_api_key_secret', { checksum: 'p' }],
|
||||
['20260831_add_private_task_detail', { checksum: 'q' }],
|
||||
['20260902_add_employee_roster_history', { checksum: 'r' }],
|
||||
['20260903_add_work_review_brand', { checksum: 's' }],
|
||||
])
|
||||
const evidence = {
|
||||
migrationRows: [
|
||||
@ -364,6 +365,7 @@ test('target evidence rejects nullable logo columns and structurally wrong index
|
||||
{ migration_name: '20260829_add_recoverable_api_key_secret', checksum: 'p', finished_at: 'now', rolled_back_at: null },
|
||||
{ migration_name: '20260831_add_private_task_detail', checksum: 'q', finished_at: 'now', rolled_back_at: null },
|
||||
{ migration_name: '20260902_add_employee_roster_history', checksum: 'r', finished_at: 'now', rolled_back_at: null },
|
||||
{ migration_name: '20260903_add_work_review_brand', checksum: 's', finished_at: 'now', rolled_back_at: null },
|
||||
],
|
||||
columns: [
|
||||
{ table_name: 'materials', column_name: 'transcode_lease_token', data_type: 'text', is_nullable: 'YES' },
|
||||
@ -692,6 +694,7 @@ test('container supply chain and migrator image are immutable while SOP keeps co
|
||||
'20260828_add_task_detail_change_approvals',
|
||||
'20260831_add_private_task_detail',
|
||||
'20260902_add_employee_roster_history',
|
||||
'20260903_add_work_review_brand',
|
||||
]) {
|
||||
assert.match(dockerfile, new RegExp(migrationName))
|
||||
assert.match(migratorSource, new RegExp(`'${migrationName}'`))
|
||||
@ -705,6 +708,8 @@ test('container supply chain and migrator image are immutable while SOP keeps co
|
||||
assert.ok(sop.includes('`20260831_add_private_task_detail`'))
|
||||
assert.match(deploymentSource, /'20260902_add_employee_roster_history'/)
|
||||
assert.ok(sop.includes('`20260902_add_employee_roster_history`'))
|
||||
assert.match(deploymentSource, /'20260903_add_work_review_brand'/)
|
||||
assert.ok(sop.includes('`20260903_add_work_review_brand`'))
|
||||
assert.match(sop, /materials_private_optimization_check/)
|
||||
assert.match(sop, /task_detail_private/)
|
||||
assert.match(sop, /can_access_private/)
|
||||
@ -772,8 +777,8 @@ test('container supply chain and migrator image are immutable while SOP keeps co
|
||||
assert.match(sop, /-v "\$HOST_RESULT_DIR:\/audit"/)
|
||||
assert.match(sop, /test -f "\$PREFLIGHT_AUDIT_FILE" && test ! -L "\$PREFLIGHT_AUDIT_FILE"/)
|
||||
assert.doesNotMatch(sop, /-v \/root\/gongxiangp2-migration-audit:\/audit/)
|
||||
assert.match(sop, /十八份目标 migration/)
|
||||
assert.match(sop, /十八条目标 migration/)
|
||||
assert.match(sop, /十九份目标 migration/)
|
||||
assert.match(sop, /十九条目标 migration/)
|
||||
|
||||
const applyMigration = migratorSource.indexOf("['migrate', 'deploy'")
|
||||
const postEvidence = migratorSource.indexOf('after = await queryEvidence(verifyClient, localMigrations)', applyMigration)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user