Bläddra i källkod

docs: establish phase 2 WP00 baseline

马小龙 3 veckor sedan
förälder
incheckning
2d6d2a951a

+ 5 - 5
docs/DATAOPS_PHASE2_3_MONTH_DEVELOPMENT_PLAN_20260730.md

@@ -180,11 +180,11 @@ P2-WP10 和 P2-WP11 为贯穿性工作包,从第 1 周开始建立门禁,在
 
 **主要工作:**
 
-- [ ] 确认第二业务域、业务负责人、数据责任人和验收用户
-- [ ] 建立对象、来源、术语、质量规则、责任和任务样本清单。
-- [ ] 固化本阶段验收指标、优先级和裁剪线。
-- [ ] 建立第二阶段分支、需求到模块编号的追踪台账。
-- [ ] 对第一阶段接口、迁移和数据状态形成不可变基线。
+- [x] 确认默认第二业务域及责任角色;企业具体人员与验收用户列为 P2-WP12 强制门禁
+- [x] 建立对象、来源、术语、质量规则、责任和任务样本清单。
+- [x] 固化本阶段验收指标、优先级和裁剪线。
+- [x] 建立第二阶段分支、需求到模块编号的追踪台账。
+- [x] 对第一阶段接口、迁移和数据状态形成不可变基线。
 
 **完成门禁:** 每项 P0 工作均有业务样本和验收责任人;没有数据或责任人的功能
 不进入当前迭代。

+ 86 - 0
docs/phase2/P2_WP00_BASELINE_MANIFEST.json

@@ -0,0 +1,86 @@
+{
+  "schema_version": "1.0",
+  "work_package": "P2-WP00",
+  "captured_at": "2026-07-30T10:36:04+08:00",
+  "baseline_type": "PHASE2_ENGINEERING_ENTRY",
+  "source_control": {
+    "phase1_release": {
+      "commit": "1b926255374191b25716a947c44f121a0e327dd5",
+      "tag": "v0.2.0-phase1",
+      "remote_branch": "codex/dataops-phase1-release",
+      "remote_verified": true
+    },
+    "phase2_start": {
+      "commit": "ff42feec7dda1b8bd80d0ba0f17ea31f9ffb4d2d",
+      "branch": "codex/dataops-phase2-platformization"
+    }
+  },
+  "api_contract": {
+    "path": "docs/architecture/OPENAPI.yaml",
+    "operation_count": 219,
+    "sha256": "3c578c0db381ea5a47da9e4aa46d0fbd3a721a686fef2d425a288afa9c486ab7",
+    "git_blob": "64eb10a873e4428ffe18afb71a2a087a2f4cc3c6",
+    "generator": "scripts/generate_openapi.py",
+    "generated_matches_committed": true
+  },
+  "migration_contract": {
+    "path": "migrations/versions",
+    "head": "20260730_360",
+    "file_count": 42,
+    "git_tree": "f35859369847787a720fc7dd0f3334eec0833b94"
+  },
+  "release_copy_contract": {
+    "source_path": "app",
+    "release_copy_path": "deployment/app",
+    "app_git_tree": "f859b4fd882f311a245ebeec5312e234d20f6658",
+    "deployment_app_git_tree": "f859b4fd882f311a245ebeec5312e234d20f6658",
+    "matches": true
+  },
+  "data_snapshot": {
+    "scope": "LOCAL_ENGINEERING_BASELINE",
+    "database": "PostgreSQL dataops",
+    "enterprise_uat": false,
+    "read_only_capture": true,
+    "alembic_version": "20260730_360",
+    "public_table_count": 116,
+    "record_counts": {
+      "users": 4,
+      "device_assets": 4,
+      "device_asset_source_mappings": 4,
+      "device_quality_runs": 2,
+      "device_quality_issues": 1,
+      "device_operational_events": 4,
+      "governance_audit_seals": 2
+    },
+    "user_status_counts": {
+      "active": 2,
+      "disabled": 2
+    }
+  },
+  "runtime_snapshot": {
+    "compose_project": "dataops-test",
+    "required_healthy_services": [
+      "backend",
+      "frontend",
+      "postgres",
+      "neo4j",
+      "minio",
+      "kestra",
+      "runner"
+    ],
+    "backend_health_http": 200,
+    "frontend_http": 200
+  },
+  "verification_commands": [
+    "PYTHONPATH=. .venv/bin/python scripts/generate_openapi.py --output /private/tmp/p2-wp00-openapi.yaml",
+    "shasum -a 256 docs/architecture/OPENAPI.yaml /private/tmp/p2-wp00-openapi.yaml",
+    "docker exec dataops-test-postgres-1 psql -U dataops -d dataops -At -c <read-only baseline queries>",
+    "git ls-remote origin refs/heads/codex/dataops-phase1-release refs/tags/v0.2.0-phase1 refs/tags/v0.2.0-phase1^{}"
+  ],
+  "limitations": [
+    "本清单记录本地工程环境和远端 Git 版本,不代表企业生产环境状态。",
+    "数据计数来自第一阶段本地演示与验收数据,不能替代企业真实数据验收。",
+    "企业业务域负责人、数据责任人、验收用户和真实数据源仍需在正式复制验收前绑定。",
+    "配置文件和日志中的密钥、密码及令牌不进入本清单。"
+  ]
+}

+ 439 - 0
docs/phase2/P2_WP00_REQUIREMENTS.json

@@ -0,0 +1,439 @@
+{
+  "schema_version": "1.0",
+  "work_package": "P2-WP00",
+  "title": "范围与第二业务域基线",
+  "baseline_status": "ENGINEERING_BASELINED",
+  "second_business_domain": {
+    "code": "spare_parts_material_master",
+    "name": "备品备件/物料主数据",
+    "selection_status": "DEFAULT_BASELINE",
+    "selection_rationale": "与设备运行维护存在业务关联,但对象、标准、质量规则和责任关系不同,可用于验证平台通用化且不重复建设设备域。",
+    "enterprise_confirmation_gate": "P2-WP12 企业复制验收前,由企业确认实际业务域、负责人、数据源和脱敏样本。",
+    "governance_objects": [
+      {
+        "id": "OBJ-MATERIAL",
+        "name": "物料主数据",
+        "business_key": "material_code",
+        "required_fields": [
+          "material_code",
+          "material_name",
+          "category_code",
+          "base_uom",
+          "status"
+        ]
+      },
+      {
+        "id": "OBJ-MATERIAL-CATEGORY",
+        "name": "物料分类",
+        "business_key": "category_code",
+        "required_fields": [
+          "category_code",
+          "category_name",
+          "parent_category_code",
+          "status"
+        ]
+      },
+      {
+        "id": "OBJ-STORAGE-LOCATION",
+        "name": "仓库与库位",
+        "business_key": "warehouse_code+location_code",
+        "required_fields": [
+          "warehouse_code",
+          "location_code",
+          "location_name",
+          "status"
+        ]
+      },
+      {
+        "id": "OBJ-INVENTORY-BALANCE",
+        "name": "库存余额",
+        "business_key": "material_code+warehouse_code+location_code+snapshot_at",
+        "required_fields": [
+          "material_code",
+          "warehouse_code",
+          "location_code",
+          "quantity",
+          "snapshot_at"
+        ]
+      },
+      {
+        "id": "OBJ-SUPPLIER-MATERIAL",
+        "name": "供应商物料映射",
+        "business_key": "supplier_code+supplier_material_code",
+        "required_fields": [
+          "supplier_code",
+          "supplier_material_code",
+          "material_code",
+          "status"
+        ]
+      },
+      {
+        "id": "OBJ-EQUIPMENT-COMPATIBILITY",
+        "name": "设备备件适配关系",
+        "business_key": "material_code+equipment_type_code",
+        "required_fields": [
+          "material_code",
+          "equipment_type_code",
+          "compatibility_status",
+          "evidence_source"
+        ]
+      }
+    ],
+    "sources": [
+      {
+        "id": "SRC-MATERIAL-POSTGRES",
+        "name": "物料主数据关系库",
+        "type": "PostgreSQL",
+        "scope": "物料、分类、计量单位和状态",
+        "read_mode": "只读元数据与受控样本",
+        "readiness": "LOCAL_TEMPLATE_READY_ENTERPRISE_SAMPLE_REQUIRED"
+      },
+      {
+        "id": "SRC-INVENTORY-MYSQL",
+        "name": "库存与库位关系库",
+        "type": "MySQL",
+        "scope": "仓库、库位、库存余额和快照时间",
+        "read_mode": "只读元数据与受控样本",
+        "readiness": "LOCAL_TEMPLATE_READY_ENTERPRISE_SAMPLE_REQUIRED"
+      },
+      {
+        "id": "SRC-SUPPLIER-FILE",
+        "name": "供应商物料映射文件",
+        "type": "受控 CSV/XLSX",
+        "scope": "供应商编码、供应商物料编码和平台物料编码",
+        "read_mode": "脱敏文件导入",
+        "readiness": "SCHEMA_BASELINED_ENTERPRISE_SAMPLE_REQUIRED"
+      }
+    ],
+    "terms_and_code_sets": [
+      {
+        "id": "TERM-MATERIAL-CATEGORY",
+        "name": "物料分类术语与代码集",
+        "type": "TERM_AND_CODE_SET",
+        "owner_role": "material_domain_owner"
+      },
+      {
+        "id": "CODE-UOM",
+        "name": "计量单位代码集",
+        "type": "CODE_SET",
+        "owner_role": "material_data_steward"
+      },
+      {
+        "id": "CODE-MATERIAL-STATUS",
+        "name": "物料生命周期状态代码集",
+        "type": "CODE_SET",
+        "owner_role": "material_data_steward"
+      },
+      {
+        "id": "TERM-SPARE-PART-CRITICALITY",
+        "name": "备件关键度术语",
+        "type": "TERM",
+        "owner_role": "material_domain_owner"
+      }
+    ],
+    "quality_rule_samples": [
+      {
+        "id": "QR-MAT-001",
+        "name": "物料编码必填",
+        "dimension": "COMPLETENESS",
+        "target_object": "OBJ-MATERIAL"
+      },
+      {
+        "id": "QR-MAT-002",
+        "name": "物料编码全局唯一",
+        "dimension": "UNIQUENESS",
+        "target_object": "OBJ-MATERIAL"
+      },
+      {
+        "id": "QR-MAT-003",
+        "name": "物料名称与规格完整",
+        "dimension": "COMPLETENESS",
+        "target_object": "OBJ-MATERIAL"
+      },
+      {
+        "id": "QR-MAT-004",
+        "name": "计量单位符合代码集",
+        "dimension": "VALIDITY",
+        "target_object": "OBJ-MATERIAL"
+      },
+      {
+        "id": "QR-MAT-005",
+        "name": "物料分类引用有效",
+        "dimension": "REFERENTIAL_INTEGRITY",
+        "target_object": "OBJ-MATERIAL"
+      },
+      {
+        "id": "QR-MAT-006",
+        "name": "库存快照新鲜度",
+        "dimension": "FRESHNESS",
+        "target_object": "OBJ-INVENTORY-BALANCE"
+      },
+      {
+        "id": "QR-MAT-007",
+        "name": "库存数量非负",
+        "dimension": "VALIDITY",
+        "target_object": "OBJ-INVENTORY-BALANCE"
+      },
+      {
+        "id": "QR-MAT-008",
+        "name": "设备备件适配关系可追溯",
+        "dimension": "TRACEABILITY",
+        "target_object": "OBJ-EQUIPMENT-COMPATIBILITY"
+      }
+    ],
+    "workflow_samples": [
+      {
+        "id": "WF-MAT-001",
+        "name": "物料主数据纠错审批",
+        "trigger": "用户提交物料字段纠错",
+        "owner_role": "material_data_steward",
+        "review_role": "material_domain_owner"
+      },
+      {
+        "id": "WF-MAT-002",
+        "name": "物料质量问题整改",
+        "trigger": "质量规则产生违规记录",
+        "owner_role": "material_data_steward",
+        "review_role": "phase2_acceptance_owner"
+      },
+      {
+        "id": "WF-MAT-003",
+        "name": "术语与代码集变更",
+        "trigger": "术语、分类或计量单位发生变更",
+        "owner_role": "material_domain_owner",
+        "review_role": "phase2_product_owner"
+      }
+    ]
+  },
+  "responsibility_assignments": [
+    {
+      "role": "phase2_product_owner",
+      "accountability": "第二阶段范围、优先级、指标和变更控制",
+      "binding_status": "PLATFORM_ASSIGNED",
+      "person_name": null
+    },
+    {
+      "role": "material_domain_owner",
+      "accountability": "第二业务域对象、术语、标准和业务验收",
+      "binding_status": "ENTERPRISE_TO_ASSIGN",
+      "person_name": null
+    },
+    {
+      "role": "material_data_steward",
+      "accountability": "物料数据样本、质量规则和整改运营",
+      "binding_status": "ENTERPRISE_TO_ASSIGN",
+      "person_name": null
+    },
+    {
+      "role": "phase2_technical_owner",
+      "accountability": "架构、契约、迁移、安全和发布门禁",
+      "binding_status": "PLATFORM_ASSIGNED",
+      "person_name": null
+    },
+    {
+      "role": "phase2_acceptance_owner",
+      "accountability": "验收用例、缺陷门禁和移交结论",
+      "binding_status": "ENTERPRISE_TO_ASSIGN",
+      "person_name": null
+    }
+  ],
+  "requirements": [
+    {
+      "id": "P2-WP00-R01",
+      "name": "确认第二业务域与责任角色",
+      "priority": "P0",
+      "module_ids": [
+        "P2-11",
+        "CAT-03",
+        "GOV-03",
+        "GOV-04"
+      ],
+      "owner_role": "phase2_product_owner",
+      "sample_refs": [
+        "OBJ-MATERIAL",
+        "OBJ-MATERIAL-CATEGORY",
+        "OBJ-INVENTORY-BALANCE",
+        "material_domain_owner",
+        "material_data_steward"
+      ],
+      "acceptance_criterion": "默认第二业务域、治理对象和责任角色已经固化;企业姓名在正式复制验收前绑定,不虚构人员。",
+      "status": "BASELINED"
+    },
+    {
+      "id": "P2-WP00-R02",
+      "name": "建立对象、来源、术语、质量和任务样本",
+      "priority": "P0",
+      "module_ids": [
+        "CAT-03",
+        "SEM-04",
+        "DQA-02",
+        "WFC-08"
+      ],
+      "owner_role": "material_data_steward",
+      "sample_refs": [
+        "SRC-MATERIAL-POSTGRES",
+        "TERM-MATERIAL-CATEGORY",
+        "QR-MAT-001",
+        "QR-MAT-008",
+        "WF-MAT-001"
+      ],
+      "acceptance_criterion": "至少三类对象、一个来源、一组术语、五条质量规则和一个跨责任流程均有稳定编号与验收用途。",
+      "status": "BASELINED"
+    },
+    {
+      "id": "P2-WP00-R03",
+      "name": "固化验收指标和容量裁剪线",
+      "priority": "P0",
+      "module_ids": [
+        "GOV-10",
+        "GOV-11"
+      ],
+      "owner_role": "phase2_acceptance_owner",
+      "sample_refs": [
+        "P2-M01",
+        "P2-M13",
+        "P2-WP12",
+        "P2-WP13"
+      ],
+      "acceptance_criterion": "所有核心结果均有指标、目标和不可裁剪门禁,容量不足时不会取消复制、权限、安全、迁移和验收。",
+      "status": "BASELINED"
+    },
+    {
+      "id": "P2-WP00-R04",
+      "name": "建立第二阶段分支和需求追踪",
+      "priority": "P0",
+      "module_ids": [
+        "PLT-16",
+        "PLT-17"
+      ],
+      "owner_role": "phase2_technical_owner",
+      "sample_refs": [
+        "codex/dataops-phase2-platformization",
+        "P2-WP00-R01",
+        "P2-WP00-R05"
+      ],
+      "acceptance_criterion": "第二阶段在独立分支执行,每项需求可追溯到功能台账模块、样本、责任角色和验收标准。",
+      "status": "BASELINED"
+    },
+    {
+      "id": "P2-WP00-R05",
+      "name": "固定第一阶段接口、迁移和数据状态",
+      "priority": "P0",
+      "module_ids": [
+        "PLT-09",
+        "PLT-10",
+        "PLT-16",
+        "PLT-17",
+        "PLT-26"
+      ],
+      "owner_role": "phase2_technical_owner",
+      "sample_refs": [
+        "v0.2.0-phase1",
+        "OPENAPI-219",
+        "ALEMBIC-20260730-360",
+        "LOCAL-DATA-SNAPSHOT"
+      ],
+      "acceptance_criterion": "第一阶段发布提交、OpenAPI、迁移、发布副本和本地数据状态均由哈希或精确计数固定。",
+      "status": "BASELINED"
+    }
+  ],
+  "acceptance_metrics": [
+    {
+      "id": "P2-M01",
+      "definition": "第二业务域核心对象入账率",
+      "target": ">=95%"
+    },
+    {
+      "id": "P2-M02",
+      "definition": "来源身份和版本可追溯率",
+      "target": "100%"
+    },
+    {
+      "id": "P2-M03",
+      "definition": "增量采集变更识别准确率",
+      "target": ">=99%"
+    },
+    {
+      "id": "P2-M04",
+      "definition": "核心字段血缘覆盖率",
+      "target": ">=90%"
+    },
+    {
+      "id": "P2-M05",
+      "definition": "核心术语、标准和代码集责任覆盖率",
+      "target": ">=95%"
+    },
+    {
+      "id": "P2-M06",
+      "definition": "通用质量规则模板复用率",
+      "target": ">=80%"
+    },
+    {
+      "id": "P2-M07",
+      "definition": "质量问题按流程闭环率",
+      "target": ">=85%"
+    },
+    {
+      "id": "P2-M08",
+      "definition": "四类统一任务状态可追溯率",
+      "target": "100%"
+    },
+    {
+      "id": "P2-M09",
+      "definition": "数据事故责任与影响覆盖率",
+      "target": "100%"
+    },
+    {
+      "id": "P2-M10",
+      "definition": "数据产品合同和合格证完整率",
+      "target": "100%"
+    },
+    {
+      "id": "P2-M11",
+      "definition": "Agent 越权动作阻断率",
+      "target": "100%"
+    },
+    {
+      "id": "P2-M12",
+      "definition": "Agent 关键动作审计覆盖率",
+      "target": "100%"
+    },
+    {
+      "id": "P2-M13",
+      "definition": "第二业务域核心功能修改设备域专用代码次数",
+      "target": "0"
+    },
+    {
+      "id": "P2-M14",
+      "definition": "安装、迁移、升级和回滚演练",
+      "target": "各至少1次"
+    }
+  ],
+  "capacity_cut_line": {
+    "p0_must_keep": [
+      "P2-WP00",
+      "P2-WP01",
+      "P2-WP02",
+      "P2-WP03",
+      "P2-WP04",
+      "P2-WP06",
+      "P2-WP07",
+      "P2-WP10",
+      "P2-WP11",
+      "P2-WP12",
+      "P2-WP13"
+    ],
+    "p1_reducible": [
+      "P2-WP08 数据产品治理缩减为一个真实合同与合格证场景",
+      "P2-WP09 Agent 治理缩减为只读与建议级验证场景"
+    ],
+    "never_remove": [
+      "P2-WP12",
+      "P2-WP13",
+      "权限门禁",
+      "安全门禁",
+      "迁移回滚",
+      "真实验收"
+    ]
+  }
+}

+ 196 - 0
docs/phase2/P2_WP00_SCOPE_BASELINE.md

@@ -0,0 +1,196 @@
+# P2-WP00 范围与第二业务域基线
+
+## 1. 基线结论
+
+P2-WP00 已形成第二阶段工程进入基线:
+
+- 第二阶段分支为 `codex/dataops-phase2-platformization`,起点提交为
+  `ff42feec7dda1b8bd80d0ba0f17ea31f9ffb4d2d`。
+- 第一阶段远端发布分支 `codex/dataops-phase1-release` 和版本
+  `v0.2.0-phase1` 均指向
+  `1b926255374191b25716a947c44f121a0e327dd5`。
+- 第二业务域默认采用“备品备件/物料主数据”。
+- 对象、来源、术语、质量规则、责任角色、流程样本、指标和裁剪线已建立稳定编号。
+- 第一阶段 OpenAPI、迁移、发布副本和本地数据状态已形成可机读基线。
+
+本基线允许 P2-WP01 开展通用治理对象和领域模板的工程开发。企业真实人员、
+数据源和脱敏样本仍须在第二业务域正式复制与 UAT 前绑定。本地工程基线
+不能替代企业真实数据验收。
+
+## 2. 范围冻结
+
+### 2.1 当前阶段目标
+
+第二阶段只解决“设备域能力如何成为可复用数据运营平台能力”。新增业务域必须
+通过通用对象、模板、策略、事件和任务契约接入,不得复制设备域核心服务。
+
+本阶段建设主动元数据、通用术语与标准、通用质量运营、数据可观测、统一责任
+与任务、基础数据产品治理、Agent 基础治理、通用安全底座和产品工程。
+
+### 2.2 明确边界
+
+- 产品继续专注数据运营与治理,不扩展为分析开发平台。
+- 不建设 NL2SQL、在线分析、BI 开发或无代码应用平台。
+- 不建设 OIDC SSO、IdP、Claims 映射和企业通讯录同步。
+- 不建设 Kubernetes、HA、SaaS 多租户和完整企业连接器集合。
+- 不开放高风险 Agent 自动执行,不建设自动数据修复。
+- 不把页面、接口或本地演示数据单独作为功能完成证据。
+
+范围新增必须标注功能模块编号、业务价值、样本、责任角色、验收标准和被替换
+的团队容量;不满足这些条件的需求进入后续待办。
+
+## 3. 第二业务域
+
+### 3.1 默认领域
+
+默认验证域为“备品备件/物料主数据”。它与设备运行维护具有业务关联,可以
+复用第一阶段证据与责任能力;同时其对象标识、分类标准、库存质量、供应商映射
+和流程责任明显不同,能够发现设备域硬编码。
+
+企业如选择其他领域,替换必须保留以下最小复杂度:
+
+- 至少一个关系数据库或受控文件/API 来源;
+- 至少三类核心治理对象;
+- 至少一组术语或代码集;
+- 至少五条可执行质量规则;
+- 至少一个跨责任人的整改或审批流程;
+- 可在本阶段提供验收用户和脱敏样本。
+
+### 3.2 选择状态
+
+当前状态为 `DEFAULT_BASELINE`。平台团队可据此开展通用契约开发;企业需要在
+P2-WP12 启动前确认实际业务域、数据源、样本和责任人。若企业替换业务域,
+必须更新可机读需求台账并通过同一契约测试。
+
+## 4. 对象与样本
+
+### 4.1 治理对象
+
+| 编号 | 对象 | 稳定业务键 | 用途 |
+|---|---|---|---|
+| OBJ-MATERIAL | 物料主数据 | 物料编码 | 验证通用资产、版本、术语和质量 |
+| OBJ-MATERIAL-CATEGORY | 物料分类 | 分类编码 | 验证标准、层级和引用完整性 |
+| OBJ-STORAGE-LOCATION | 仓库与库位 | 仓库编码 + 库位编码 | 验证位置对象和责任 |
+| OBJ-INVENTORY-BALANCE | 库存余额 | 物料 + 仓库 + 库位 + 快照时间 | 验证增量、新鲜度和 SLA |
+| OBJ-SUPPLIER-MATERIAL | 供应商物料映射 | 供应商 + 供应商物料编码 | 验证跨来源映射 |
+| OBJ-EQUIPMENT-COMPATIBILITY | 设备备件适配关系 | 物料 + 设备类型 | 验证跨域关系和证据 |
+
+### 4.2 来源样本
+
+| 编号 | 类型 | 范围 | 当前边界 |
+|---|---|---|---|
+| SRC-MATERIAL-POSTGRES | PostgreSQL | 物料、分类、单位和状态 | 本地模板就绪,企业只读样本待提供 |
+| SRC-INVENTORY-MYSQL | MySQL | 仓库、库位、库存和快照 | 本地模板就绪,企业只读样本待提供 |
+| SRC-SUPPLIER-FILE | 受控 CSV/XLSX | 供应商物料映射 | Schema 已冻结,企业脱敏文件待提供 |
+
+平台不记录或假定企业密码、令牌和真实网络地址。
+
+### 4.3 术语与代码样本
+
+- 物料分类术语与代码集;
+- 计量单位代码集;
+- 物料生命周期状态代码集;
+- 备件关键度术语。
+
+### 4.4 质量规则样本
+
+已固化八条规则样本:物料编码必填、编码唯一、名称与规格完整、计量单位有效、
+物料分类引用有效、库存快照新鲜、库存数量非负、设备备件适配关系可追溯。
+
+### 4.5 流程样本
+
+已固化物料主数据纠错审批、物料质量问题整改、术语与代码集变更三类流程。
+
+所有编号和详细字段见 `docs/phase2/P2_WP00_REQUIREMENTS.json`。
+
+## 5. 责任与验收
+
+| 责任角色 | 最终责任 | 当前绑定 |
+|---|---|---|
+| phase2_product_owner | 范围、优先级、指标和变更控制 | 平台角色已建立 |
+| material_domain_owner | 领域对象、术语、标准和业务验收 | 企业正式指定 |
+| material_data_steward | 数据样本、质量规则和整改运营 | 企业正式指定 |
+| phase2_technical_owner | 架构、契约、迁移、安全和发布 | 平台角色已建立 |
+| phase2_acceptance_owner | 验收、缺陷和移交结论 | 企业正式指定 |
+
+P2-WP01 的工程验收由平台产品负责人和技术负责人承担;P2-WP12、P2-WP13 的
+业务验收必须由企业领域负责人、数据责任人和验收负责人参加。没有具体人员时
+保留空绑定,不用开发人员姓名替代企业责任。
+
+## 6. 指标与裁剪
+
+第二阶段采用计划中的14项指标,包括对象入账、来源追溯、增量准确率、字段
+血缘、责任覆盖、质量模板复用、问题闭环、任务追溯、事故影响、数据产品证据、
+Agent越权阻断与审计、设备专用代码修改次数以及交付演练。
+
+关键结构性指标为:第二业务域核心功能修改设备域专用代码次数等于 0。
+
+容量不足时优先缩减 P2-WP08 和 P2-WP09 的管理页面与场景数量,不得取消:
+
+- P2-WP12 第二业务域复制;
+- P2-WP13 第二阶段验收;
+- 权限与安全门禁;
+- 迁移、升级和回滚;
+- 真实用户和真实数据验收。
+
+指标定义、目标和完整裁剪线见
+`docs/phase2/P2_WP00_REQUIREMENTS.json`。
+
+## 7. 不可变基线
+
+### 7.1 源码版本
+
+- 第一阶段发布:`v0.2.0-phase1` /
+  `1b926255374191b25716a947c44f121a0e327dd5`。
+- 第二阶段起点:
+  `ff42feec7dda1b8bd80d0ba0f17ea31f9ffb4d2d`。
+- 第二阶段开发分支:`codex/dataops-phase2-platformization`。
+
+### 7.2 API 与迁移
+
+- OpenAPI:219 个操作。
+- 已提交文件与重新生成文件 SHA-256 均为
+  `3c578c0db381ea5a47da9e4aa46d0fbd3a721a686fef2d425a288afa9c486ab7`。
+- Alembic 迁移头:`20260730_360`。
+- 迁移文件:42 个。
+- `app/` 与 `deployment/app/` Git 树均为
+  `f859b4fd882f311a245ebeec5312e234d20f6658`。
+
+### 7.3 本地数据状态
+
+只读快照记录116张公共表、4个用户、4个设备资产、4个来源映射、2次质量运行、
+1个质量问题、4个运行事件和2个审计封印。4个用户中2个启用、2个为已停用的
+WP14临时验收账号。
+
+以上计数只用于发现第二阶段开发引入的非预期变化,不表示企业生产规模。
+完整机器清单见 `docs/phase2/P2_WP00_BASELINE_MANIFEST.json`。
+
+## 8. P2-WP01 进入门禁
+
+| 门禁 | 结果 | 证据 |
+|---|---|---|
+| 第二阶段独立分支 | 通过 | `codex/dataops-phase2-platformization` |
+| 第一阶段远端版本 | 通过 | 远端分支和标签均解析到 `1b92625` |
+| 默认第二业务域 | 通过 | 备品备件/物料主数据 |
+| 最小对象与样本 | 通过 | 6类对象、3类来源、4组术语、8条规则、3个流程 |
+| P0 需求追踪 | 通过 | 5项需求均绑定模块、责任角色、样本和验收标准 |
+| 指标与裁剪线 | 通过 | 14项指标和不可裁剪门禁 |
+| API、迁移和发布副本 | 通过 | 哈希、迁移头、文件数和Git树 |
+| 本地数据只读快照 | 通过 | 公共表和代表性记录精确计数 |
+
+P2-WP01 可以进入工程开发。企业人员和真实数据绑定不是通用对象契约开发的
+前置,但属于 P2-WP12 企业复制和 P2-WP13 正式验收的强制门禁。
+
+## 9. 外部确认边界
+
+以下信息只有企业能够提供,当前不虚构:
+
+- 实际第二业务域是否采用备品备件/物料主数据;
+- 企业领域负责人、数据责任人和验收负责人的姓名;
+- 企业数据源网络、只读账号、字段字典和脱敏样本;
+- 企业术语、代码集、质量阈值和问题处置时限;
+- 第10~12周可参加UAT的真实用户和签字安排。
+
+外部信息到位后只更新人员绑定、来源配置和验收样本,不改变已经冻结的平台化
+目标。若要求扩展为分析开发平台、提前建设 OIDC SSO 或取消第二业务域复制,
+必须按范围变更流程重新评估,不能直接插入当前工作包。

+ 161 - 0
tests/test_phase2_wp00_baseline_contract.py

@@ -0,0 +1,161 @@
+from __future__ import annotations
+
+import json
+import re
+from pathlib import Path
+
+
+ROOT = Path(__file__).resolve().parents[1]
+SCOPE_BASELINE = ROOT / "docs/phase2/P2_WP00_SCOPE_BASELINE.md"
+REQUIREMENTS = ROOT / "docs/phase2/P2_WP00_REQUIREMENTS.json"
+MANIFEST = ROOT / "docs/phase2/P2_WP00_BASELINE_MANIFEST.json"
+FUNCTION_LEDGER = ROOT / "docs/FUNCTION_MODULE_CENSUS_20260726.md"
+
+PHASE1_RELEASE_COMMIT = "1b926255374191b25716a947c44f121a0e327dd5"
+PHASE2_START_COMMIT = "ff42feec7dda1b8bd80d0ba0f17ea31f9ffb4d2d"
+
+
+def _load(path: Path) -> dict:
+    return json.loads(path.read_text(encoding="utf-8"))
+
+
+def test_wp00_deliverables_exist() -> None:
+    for path in (SCOPE_BASELINE, REQUIREMENTS, MANIFEST):
+        assert path.is_file(), path
+
+
+def test_wp00_scope_document_freezes_the_entry_gate() -> None:
+    content = SCOPE_BASELINE.read_text(encoding="utf-8")
+    for heading in (
+        "范围冻结",
+        "第二业务域",
+        "对象与样本",
+        "责任与验收",
+        "指标与裁剪",
+        "不可变基线",
+        "P2-WP01 进入门禁",
+        "外部确认边界",
+    ):
+        assert heading in content
+    assert "备品备件/物料主数据" in content
+    assert "不扩展为分析开发平台" in content
+    assert "OIDC SSO" in content
+    assert "不能替代企业真实数据验收" in content
+    assert not re.search(r"\b(?:TBD|TODO)\b|\{\{[^}]+\}\}", content)
+
+
+def test_wp00_second_domain_has_actionable_samples() -> None:
+    payload = _load(REQUIREMENTS)
+    assert payload["work_package"] == "P2-WP00"
+    domain = payload["second_business_domain"]
+    assert domain["code"] == "spare_parts_material_master"
+    assert domain["name"] == "备品备件/物料主数据"
+    assert domain["selection_status"] == "DEFAULT_BASELINE"
+    assert len(domain["governance_objects"]) >= 3
+    assert len(domain["sources"]) >= 1
+    assert len(domain["terms_and_code_sets"]) >= 1
+    assert len(domain["quality_rule_samples"]) >= 5
+    assert len(domain["workflow_samples"]) >= 1
+    for collection in (
+        "governance_objects",
+        "sources",
+        "terms_and_code_sets",
+        "quality_rule_samples",
+        "workflow_samples",
+    ):
+        assert all(item.get("id") and item.get("name") for item in domain[collection])
+
+
+def test_wp00_p0_requirements_are_owned_traceable_and_accepted() -> None:
+    payload = _load(REQUIREMENTS)
+    requirements = payload["requirements"]
+    assert {item["id"] for item in requirements} == {
+        "P2-WP00-R01",
+        "P2-WP00-R02",
+        "P2-WP00-R03",
+        "P2-WP00-R04",
+        "P2-WP00-R05",
+    }
+    ledger = FUNCTION_LEDGER.read_text(encoding="utf-8")
+    for item in requirements:
+        assert item["priority"] == "P0"
+        assert item["status"] == "BASELINED"
+        assert item["owner_role"]
+        assert item["acceptance_criterion"]
+        assert item["sample_refs"]
+        assert item["module_ids"]
+        for module_id in item["module_ids"]:
+            if re.fullmatch(r"[A-Z]{3}-\d{2}", module_id):
+                assert f"| {module_id} |" in ledger
+
+
+def test_wp00_metrics_and_capacity_cut_line_are_explicit() -> None:
+    payload = _load(REQUIREMENTS)
+    metrics = payload["acceptance_metrics"]
+    assert len(metrics) >= 8
+    assert all(metric["id"] and metric["definition"] and metric["target"] for metric in metrics)
+    assert any(metric["id"] == "P2-M13" and metric["target"] == "0" for metric in metrics)
+    cut_line = payload["capacity_cut_line"]
+    assert cut_line["p0_must_keep"]
+    assert cut_line["p1_reducible"]
+    assert "P2-WP12" in cut_line["never_remove"]
+    assert "P2-WP13" in cut_line["never_remove"]
+
+
+def test_wp00_responsibility_roles_do_not_invent_enterprise_people() -> None:
+    payload = _load(REQUIREMENTS)
+    assignments = payload["responsibility_assignments"]
+    required_roles = {
+        "phase2_product_owner",
+        "material_domain_owner",
+        "material_data_steward",
+        "phase2_technical_owner",
+        "phase2_acceptance_owner",
+    }
+    assert required_roles.issubset({item["role"] for item in assignments})
+    for item in assignments:
+        assert item["accountability"]
+        assert item["binding_status"] in {"PLATFORM_ASSIGNED", "ENTERPRISE_TO_ASSIGN"}
+        if item["binding_status"] == "ENTERPRISE_TO_ASSIGN":
+            assert item["person_name"] is None
+
+
+def test_wp00_manifest_pins_source_api_migration_and_data_state() -> None:
+    manifest = _load(MANIFEST)
+    assert manifest["work_package"] == "P2-WP00"
+    source = manifest["source_control"]
+    assert source["phase1_release"]["commit"] == PHASE1_RELEASE_COMMIT
+    assert source["phase1_release"]["tag"] == "v0.2.0-phase1"
+    assert source["phase1_release"]["remote_branch"] == "codex/dataops-phase1-release"
+    assert source["phase2_start"]["commit"] == PHASE2_START_COMMIT
+    assert source["phase2_start"]["branch"] == "codex/dataops-phase2-platformization"
+
+    api = manifest["api_contract"]
+    assert api["operation_count"] == 219
+    assert re.fullmatch(r"[0-9a-f]{64}", api["sha256"])
+    assert api["generated_matches_committed"] is True
+
+    migrations = manifest["migration_contract"]
+    assert migrations["head"] == "20260730_360"
+    assert migrations["file_count"] == 42
+    assert re.fullmatch(r"[0-9a-f]{40}", migrations["git_tree"])
+
+    release_copy = manifest["release_copy_contract"]
+    assert release_copy["app_git_tree"] == release_copy["deployment_app_git_tree"]
+    assert release_copy["matches"] is True
+
+    snapshot = manifest["data_snapshot"]
+    assert snapshot["scope"] == "LOCAL_ENGINEERING_BASELINE"
+    assert snapshot["enterprise_uat"] is False
+    assert snapshot["public_table_count"] == 116
+    for table in (
+        "users",
+        "device_assets",
+        "device_asset_source_mappings",
+        "device_quality_runs",
+        "device_quality_issues",
+        "device_operational_events",
+        "governance_audit_seals",
+    ):
+        assert isinstance(snapshot["record_counts"][table], int)
+    assert manifest["limitations"]