test_phase2_wp00_baseline_contract.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. from __future__ import annotations
  2. import json
  3. import re
  4. from pathlib import Path
  5. ROOT = Path(__file__).resolve().parents[1]
  6. SCOPE_BASELINE = ROOT / "docs/phase2/P2_WP00_SCOPE_BASELINE.md"
  7. REQUIREMENTS = ROOT / "docs/phase2/P2_WP00_REQUIREMENTS.json"
  8. MANIFEST = ROOT / "docs/phase2/P2_WP00_BASELINE_MANIFEST.json"
  9. FUNCTION_LEDGER = ROOT / "docs/FUNCTION_MODULE_CENSUS_20260726.md"
  10. PHASE1_RELEASE_COMMIT = "1b926255374191b25716a947c44f121a0e327dd5"
  11. PHASE2_START_COMMIT = "ff42feec7dda1b8bd80d0ba0f17ea31f9ffb4d2d"
  12. def _load(path: Path) -> dict:
  13. return json.loads(path.read_text(encoding="utf-8"))
  14. def test_wp00_deliverables_exist() -> None:
  15. for path in (SCOPE_BASELINE, REQUIREMENTS, MANIFEST):
  16. assert path.is_file(), path
  17. def test_wp00_scope_document_freezes_the_entry_gate() -> None:
  18. content = SCOPE_BASELINE.read_text(encoding="utf-8")
  19. for heading in (
  20. "范围冻结",
  21. "第二业务域",
  22. "对象与样本",
  23. "责任与验收",
  24. "指标与裁剪",
  25. "不可变基线",
  26. "P2-WP01 进入门禁",
  27. "外部确认边界",
  28. ):
  29. assert heading in content
  30. assert "备品备件/物料主数据" in content
  31. assert "不扩展为分析开发平台" in content
  32. assert "OIDC SSO" in content
  33. assert "不能替代企业真实数据验收" in content
  34. assert not re.search(r"\b(?:TBD|TODO)\b|\{\{[^}]+\}\}", content)
  35. def test_wp00_second_domain_has_actionable_samples() -> None:
  36. payload = _load(REQUIREMENTS)
  37. assert payload["work_package"] == "P2-WP00"
  38. domain = payload["second_business_domain"]
  39. assert domain["code"] == "spare_parts_material_master"
  40. assert domain["name"] == "备品备件/物料主数据"
  41. assert domain["selection_status"] == "DEFAULT_BASELINE"
  42. assert len(domain["governance_objects"]) >= 3
  43. assert len(domain["sources"]) >= 1
  44. assert len(domain["terms_and_code_sets"]) >= 1
  45. assert len(domain["quality_rule_samples"]) >= 5
  46. assert len(domain["workflow_samples"]) >= 1
  47. for collection in (
  48. "governance_objects",
  49. "sources",
  50. "terms_and_code_sets",
  51. "quality_rule_samples",
  52. "workflow_samples",
  53. ):
  54. assert all(item.get("id") and item.get("name") for item in domain[collection])
  55. def test_wp00_p0_requirements_are_owned_traceable_and_accepted() -> None:
  56. payload = _load(REQUIREMENTS)
  57. requirements = payload["requirements"]
  58. assert {item["id"] for item in requirements} == {
  59. "P2-WP00-R01",
  60. "P2-WP00-R02",
  61. "P2-WP00-R03",
  62. "P2-WP00-R04",
  63. "P2-WP00-R05",
  64. }
  65. ledger = FUNCTION_LEDGER.read_text(encoding="utf-8")
  66. for item in requirements:
  67. assert item["priority"] == "P0"
  68. assert item["status"] == "BASELINED"
  69. assert item["owner_role"]
  70. assert item["acceptance_criterion"]
  71. assert item["sample_refs"]
  72. assert item["module_ids"]
  73. for module_id in item["module_ids"]:
  74. if re.fullmatch(r"[A-Z]{3}-\d{2}", module_id):
  75. assert f"| {module_id} |" in ledger
  76. def test_wp00_metrics_and_capacity_cut_line_are_explicit() -> None:
  77. payload = _load(REQUIREMENTS)
  78. metrics = payload["acceptance_metrics"]
  79. assert len(metrics) >= 8
  80. assert all(metric["id"] and metric["definition"] and metric["target"] for metric in metrics)
  81. assert any(metric["id"] == "P2-M13" and metric["target"] == "0" for metric in metrics)
  82. cut_line = payload["capacity_cut_line"]
  83. assert cut_line["p0_must_keep"]
  84. assert cut_line["p1_reducible"]
  85. assert "P2-WP12" in cut_line["never_remove"]
  86. assert "P2-WP13" in cut_line["never_remove"]
  87. def test_wp00_responsibility_roles_do_not_invent_enterprise_people() -> None:
  88. payload = _load(REQUIREMENTS)
  89. assignments = payload["responsibility_assignments"]
  90. required_roles = {
  91. "phase2_product_owner",
  92. "material_domain_owner",
  93. "material_data_steward",
  94. "phase2_technical_owner",
  95. "phase2_acceptance_owner",
  96. }
  97. assert required_roles.issubset({item["role"] for item in assignments})
  98. for item in assignments:
  99. assert item["accountability"]
  100. assert item["binding_status"] in {"PLATFORM_ASSIGNED", "ENTERPRISE_TO_ASSIGN"}
  101. if item["binding_status"] == "ENTERPRISE_TO_ASSIGN":
  102. assert item["person_name"] is None
  103. def test_wp00_manifest_pins_source_api_migration_and_data_state() -> None:
  104. manifest = _load(MANIFEST)
  105. assert manifest["work_package"] == "P2-WP00"
  106. source = manifest["source_control"]
  107. assert source["phase1_release"]["commit"] == PHASE1_RELEASE_COMMIT
  108. assert source["phase1_release"]["tag"] == "v0.2.0-phase1"
  109. assert source["phase1_release"]["remote_branch"] == "codex/dataops-phase1-release"
  110. assert source["phase2_start"]["commit"] == PHASE2_START_COMMIT
  111. assert source["phase2_start"]["branch"] == "codex/dataops-phase2-platformization"
  112. api = manifest["api_contract"]
  113. assert api["operation_count"] == 219
  114. assert re.fullmatch(r"[0-9a-f]{64}", api["sha256"])
  115. assert api["generated_matches_committed"] is True
  116. migrations = manifest["migration_contract"]
  117. assert migrations["head"] == "20260730_360"
  118. assert migrations["file_count"] == 42
  119. assert re.fullmatch(r"[0-9a-f]{40}", migrations["git_tree"])
  120. release_copy = manifest["release_copy_contract"]
  121. assert release_copy["app_git_tree"] == release_copy["deployment_app_git_tree"]
  122. assert release_copy["matches"] is True
  123. snapshot = manifest["data_snapshot"]
  124. assert snapshot["scope"] == "LOCAL_ENGINEERING_BASELINE"
  125. assert snapshot["enterprise_uat"] is False
  126. assert snapshot["public_table_count"] == 116
  127. for table in (
  128. "users",
  129. "device_assets",
  130. "device_asset_source_mappings",
  131. "device_quality_runs",
  132. "device_quality_issues",
  133. "device_operational_events",
  134. "governance_audit_seals",
  135. ):
  136. assert isinstance(snapshot["record_counts"][table], int)
  137. assert manifest["limitations"]