test_p2_wp12_second_domain_replication.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. from __future__ import annotations
  2. import csv
  3. import importlib.util
  4. import json
  5. from pathlib import Path
  6. import pytest
  7. ROOT = Path(__file__).resolve().parents[2]
  8. PACKAGE_DIR = ROOT / "docs/phase2/p2-wp12-spare-parts-package"
  9. MODULE_PATH = ROOT / "app/core/governance/domain_replication.py"
  10. SPEC = importlib.util.spec_from_file_location("domain_replication", MODULE_PATH)
  11. assert SPEC and SPEC.loader
  12. domain_replication = importlib.util.module_from_spec(SPEC)
  13. SPEC.loader.exec_module(domain_replication)
  14. def _json(name: str) -> dict:
  15. return json.loads((PACKAGE_DIR / name).read_text(encoding="utf-8"))
  16. def _rows(name: str) -> list[dict[str, str]]:
  17. with (PACKAGE_DIR / "data" / name).open(encoding="utf-8", newline="") as handle:
  18. return list(csv.DictReader(handle))
  19. def _accept(manifest=None, evidence=None):
  20. return domain_replication.evaluate_replication_package(
  21. manifest or _json("manifest.json"),
  22. evidence or _json("evidence.json"),
  23. template=_json("domain-template.json"),
  24. snapshot_rows=_rows("material_master_snapshot.csv"),
  25. delta_rows=_rows("material_master_delta.csv"),
  26. )
  27. def test_second_domain_package_completes_all_platform_stages_without_raw_rows():
  28. report = _accept()
  29. assert report["status"] == "passed"
  30. assert report["domain"]["code"] == "spare_parts"
  31. assert report["enterprise_uat"]["status"] == "blocked_external"
  32. assert report["third_domain_reusable"] is True
  33. assert report["device_specific_core_changes"] == 0
  34. assert set(report["stages"]) == set(domain_replication.REQUIRED_STAGES)
  35. assert all(item["status"] == "passed" for item in report["stages"].values())
  36. serialized = json.dumps(report, ensure_ascii=False)
  37. assert "MAT-0001" not in serialized
  38. assert "轴承" not in serialized
  39. def test_controlled_source_snapshot_and_delta_are_incremental_and_idempotent():
  40. report = _accept()["incremental_collection"]
  41. assert report["source_kind"] == "controlled_csv"
  42. assert report["classification"] == "desensitized"
  43. assert report["snapshot_rows"] == 3
  44. assert report["delta_rows"] == 2
  45. assert report["inserted"] == 1
  46. assert report["updated"] == 1
  47. assert report["deleted"] == 0
  48. assert report["final_rows"] == 4
  49. assert report["replay_changes"] == 0
  50. assert report["cursor_before"] == 100
  51. assert report["cursor_after"] == 102
  52. assert len(report["final_digest"]) == 64
  53. def test_missing_or_failed_capability_evidence_blocks_acceptance():
  54. evidence = _json("evidence.json")
  55. evidence["receipts"] = [
  56. item for item in evidence["receipts"] if item["stage"] != "data_product"
  57. ]
  58. with pytest.raises(ValueError, match="missing stages.*data_product"):
  59. _accept(evidence=evidence)
  60. evidence = _json("evidence.json")
  61. quality = next(
  62. item for item in evidence["receipts"] if item["stage"] == "quality"
  63. )
  64. quality["status"] = "failed"
  65. with pytest.raises(ValueError, match="quality.*passed"):
  66. _accept(evidence=evidence)
  67. manifest = _json("manifest.json")
  68. manifest["enterprise_bindings"] = manifest["enterprise_bindings"][:-1]
  69. with pytest.raises(ValueError, match="missing enterprise bindings.*uat_users"):
  70. _accept(manifest=manifest)
  71. def test_quality_remediation_observability_product_and_agent_are_closed_loop():
  72. report = _accept()
  73. quality = report["stages"]["quality"]["metrics"]
  74. remediation = report["stages"]["remediation"]["metrics"]
  75. observability = report["stages"]["observability"]["metrics"]
  76. product = report["stages"]["data_product"]["metrics"]
  77. agent = report["stages"]["agent"]["metrics"]
  78. assert quality["published_rule_count"] >= 5
  79. assert quality["final_score"] >= quality["target_score"]
  80. assert remediation == {
  81. "issue_status": "closed",
  82. "task_status": "completed",
  83. "independent_closer": True,
  84. }
  85. assert observability["incident_status"] == "closed"
  86. assert observability["recovered"] is True
  87. assert product["contract_status"] == "active"
  88. assert product["certificate_status"] == "issued"
  89. assert agent["autonomy_level"] in {"read_only", "suggestion"}
  90. assert agent["decision"] == "authorized"
  91. assert agent["automatic_execution_allowed"] is False
  92. assert agent["citation_count"] >= 1
  93. assert agent["cross_domain_denied"] is True
  94. def test_device_specific_changes_and_device_api_evidence_are_rejected():
  95. manifest = _json("manifest.json")
  96. manifest["core_change_assessment"]["device_specific_changes"] = [
  97. "app/core/data_research/device_assets.py"
  98. ]
  99. with pytest.raises(ValueError, match="device-specific"):
  100. _accept(manifest=manifest)
  101. evidence = _json("evidence.json")
  102. evidence["receipts"][0]["api_refs"] = [
  103. "/api/development/v1/device-semantics/bootstrap"
  104. ]
  105. with pytest.raises(ValueError, match="device-specific API"):
  106. _accept(evidence=evidence)
  107. def test_secret_like_source_columns_and_non_monotonic_cursors_are_rejected():
  108. snapshot = _rows("material_master_snapshot.csv")
  109. snapshot[0]["api_token"] = "not-allowed"
  110. with pytest.raises(ValueError, match="secret-like source field"):
  111. domain_replication.collect_incremental_rows(
  112. snapshot,
  113. _rows("material_master_delta.csv"),
  114. _json("manifest.json")["source"],
  115. )
  116. delta = _rows("material_master_delta.csv")
  117. delta[1]["_cursor"] = "100"
  118. with pytest.raises(ValueError, match="strictly increase"):
  119. domain_replication.collect_incremental_rows(
  120. _rows("material_master_snapshot.csv"),
  121. delta,
  122. _json("manifest.json")["source"],
  123. )
  124. def test_product_certificate_and_agent_citations_must_bind_canonical_evidence():
  125. evidence = _json("evidence.json")
  126. product = next(
  127. item for item in evidence["receipts"] if item["stage"] == "data_product"
  128. )
  129. product["metrics"]["certificate_evidence_refs"] = []
  130. with pytest.raises(ValueError, match="certificate evidence"):
  131. _accept(evidence=evidence)
  132. evidence = _json("evidence.json")
  133. agent = next(item for item in evidence["receipts"] if item["stage"] == "agent")
  134. agent["metrics"]["citation_count"] = 0
  135. with pytest.raises(ValueError, match="citation"):
  136. _accept(evidence=evidence)