import importlib.util from pathlib import Path import unittest ROOT = Path(__file__).resolve().parents[1] def module(name): spec = importlib.util.spec_from_file_location(name, ROOT / "deploy" / f"{name}.py") result = importlib.util.module_from_spec(spec) spec.loader.exec_module(result) return result automation = module("automation_acceptance") privacy = module("identity_privacy_acceptance") def base(system): return { "schemaVersion": 1, "system": system, "targetGate": "B", "releaseCommit": "a" * 40, "releaseRecordSha256": "b" * 64, "environment": "https://sandbox-guestops.futuresens.co.uk", "operator": "Acceptance operator", "reviewedBy": "Independent reviewer", "startedAt": "2026-10-01T09:00:00Z", "endedAt": "2026-10-01T10:00:00Z", "reviewedAt": "2026-10-01T11:00:00Z", } def automation_record(): record = base("guestops-automation-acceptance") record.update({ "dataClassification": "synthetic-only", "faqEvaluation": {"positiveCases": 20, "negativeCases": 20, "falsePositives": 0, "falseNegatives": 0, "reportSha256": "c" * 64}, "aiEvaluation": {"casesReviewed": 20, "unsafeDraftsApproved": 0, "reportSha256": "d" * 64}, "staffTrained": 3, "monitoringOwner": "Monitoring owner", "rollbackOwner": "Rollback owner", "scenarios": [{"id": item, "status": "pass", "evidence": ["restricted-" + item]} for item in sorted(automation.SCENARIOS)], "postAcceptanceState": {"faqMode": "off", "pmsWrites": "disabled", "paymentCreation": "disabled"}, }) return record def privacy_record(): record = base("guestops-identity-privacy") record.update({ "retention": {"conversationDays": 365, "auditDays": 730, "backupDays": 30, "accountDays": 730, "privacyOwner": "Privacy owner", "deletionOwner": "Deletion owner", "legalHoldOwner": "Legal hold owner", "deletionProcedure": "restricted-deletion", "legalHoldProcedure": "restricted-legal-hold"}, "providers": {"google": {"status": "accepted", "evidence": ["restricted-google"]}, "openai": {"status": "disabled", "evidence": ["restricted-openai-decision"]}}, "preferencesReviewed": sorted(privacy.PREFERENCES), "scenarios": [{"id": item, "status": "pass", "evidence": ["restricted-" + item]} for item in sorted(privacy.SCENARIOS)], }) return record class GateBAcceptanceTests(unittest.TestCase): def test_complete_records_pass(self): automation.validate(automation_record()) privacy.validate(privacy_record()) def test_automation_requires_zero_faq_errors_and_training(self): record = automation_record();record["faqEvaluation"]["falsePositives"] = 1 with self.assertRaisesRegex(ValueError, "zero false positives"): automation.validate(record) record = automation_record();record["staffTrained"] = 0 with self.assertRaisesRegex(ValueError, "staff member"): automation.validate(record) def test_privacy_requires_retention_and_google_decisions(self): record = privacy_record();record["retention"]["conversationDays"] = 0 with self.assertRaisesRegex(ValueError, "conversationDays"): privacy.validate(record) record = privacy_record();record["providers"]["google"]["status"] = "pending" with self.assertRaisesRegex(ValueError, "Google processing"): privacy.validate(record) def test_privacy_requires_exact_preference_coverage(self): record = privacy_record();record["preferencesReviewed"].pop() with self.assertRaisesRegex(ValueError, "exact owner-controlled"): privacy.validate(record) if __name__ == "__main__": unittest.main()