From 0a9bb266f7c22463dcf9f536a41a7adf57e8c5b8 Mon Sep 17 00:00:00 2001 From: ElliotSun Date: Sat, 29 Aug 2026 15:08:37 +1000 Subject: [PATCH 1/2] feat(governance): add golden regression scenario corpus and deterministic test harness (#32) - Add 23 read-only golden scenario fixtures under tests/fixtures/governance_scenarios/ - Implement parameterized golden regression runner in tests/test_governance_golden_scenarios.py - Assert domain invariants and byte-exact PublicGovernanceDecisionV1 serialization - Add regression tests for lifecycle states, canonical identity, reason codes, and determinism --- .../active_to_retired_transition/base.yaml | 29 + .../candidate.yaml | 29 + .../expected.json | 71 ++ .../contract_id_change/base.yaml | 29 + .../contract_id_change/candidate.yaml | 29 + .../contract_id_change/expected.json | 73 ++ .../decimal_precision_reduction/base.yaml | 29 + .../candidate.yaml | 29 + .../decimal_precision_reduction/expected.json | 74 ++ .../decimal_scale_reduction/base.yaml | 26 + .../decimal_scale_reduction/candidate.yaml | 26 + .../decimal_scale_reduction/expected.json | 74 ++ .../decimal_widening/base.yaml | 29 + .../decimal_widening/candidate.yaml | 29 + .../decimal_widening/expected.json | 56 ++ .../deprecated_entity_change/base.yaml | 29 + .../deprecated_entity_change/candidate.yaml | 29 + .../deprecated_entity_change/expected.json | 88 +++ .../descriptive_metadata_only/base.yaml | 29 + .../descriptive_metadata_only/candidate.yaml | 29 + .../descriptive_metadata_only/expected.json | 106 +++ .../draft_entity_change/base.yaml | 29 + .../draft_entity_change/candidate.yaml | 29 + .../draft_entity_change/expected.json | 88 +++ .../enum_reduction/base.yaml | 31 + .../enum_reduction/candidate.yaml | 30 + .../enum_reduction/expected.json | 39 + .../logical_type_change/base.yaml | 29 + .../logical_type_change/candidate.yaml | 29 + .../logical_type_change/expected.json | 90 +++ .../manual_version_change/base.yaml | 29 + .../manual_version_change/candidate.yaml | 29 + .../manual_version_change/expected.json | 73 ++ .../merge_conflict/base.yaml | 29 + .../merge_conflict/candidate.yaml | 29 + .../merge_conflict/expected.json | 51 ++ .../governance_scenarios/no_change/base.yaml | 29 + .../no_change/candidate.yaml | 29 + .../no_change/expected.json | 39 + .../base.yaml | 29 + .../candidate.yaml | 29 + .../expected.json | 71 ++ .../physical_type_narrowing/base.yaml | 29 + .../physical_type_narrowing/candidate.yaml | 29 + .../physical_type_narrowing/expected.json | 74 ++ .../property_addition/base.yaml | 29 + .../property_addition/candidate.yaml | 36 + .../property_addition/expected.json | 71 ++ .../property_removal/base.yaml | 29 + .../property_removal/candidate.yaml | 22 + .../property_removal/expected.json | 82 +++ .../relationship_removal/base.yaml | 29 + .../relationship_removal/candidate.yaml | 26 + .../relationship_removal/expected.json | 77 ++ .../required_tightening/base.yaml | 29 + .../required_tightening/candidate.yaml | 29 + .../required_tightening/expected.json | 74 ++ .../retired_contract_mutation/base.yaml | 26 + .../retired_contract_mutation/candidate.yaml | 26 + .../retired_contract_mutation/expected.json | 75 ++ .../schema_addition/base.yaml | 29 + .../schema_addition/candidate.yaml | 41 ++ .../schema_addition/expected.json | 78 ++ .../schema_removal/base.yaml | 30 + .../schema_removal/candidate.yaml | 20 + .../schema_removal/expected.json | 87 +++ .../validation_failure/base.yaml | 29 + .../validation_failure/candidate.yaml | 23 + .../validation_failure/expected.json | 55 ++ tests/test_governance_golden_scenarios.py | 680 ++++++++++++++++++ 70 files changed, 3666 insertions(+) create mode 100644 tests/fixtures/governance_scenarios/active_to_retired_transition/base.yaml create mode 100644 tests/fixtures/governance_scenarios/active_to_retired_transition/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/active_to_retired_transition/expected.json create mode 100644 tests/fixtures/governance_scenarios/contract_id_change/base.yaml create mode 100644 tests/fixtures/governance_scenarios/contract_id_change/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/contract_id_change/expected.json create mode 100644 tests/fixtures/governance_scenarios/decimal_precision_reduction/base.yaml create mode 100644 tests/fixtures/governance_scenarios/decimal_precision_reduction/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/decimal_precision_reduction/expected.json create mode 100644 tests/fixtures/governance_scenarios/decimal_scale_reduction/base.yaml create mode 100644 tests/fixtures/governance_scenarios/decimal_scale_reduction/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/decimal_scale_reduction/expected.json create mode 100644 tests/fixtures/governance_scenarios/decimal_widening/base.yaml create mode 100644 tests/fixtures/governance_scenarios/decimal_widening/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/decimal_widening/expected.json create mode 100644 tests/fixtures/governance_scenarios/deprecated_entity_change/base.yaml create mode 100644 tests/fixtures/governance_scenarios/deprecated_entity_change/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/deprecated_entity_change/expected.json create mode 100644 tests/fixtures/governance_scenarios/descriptive_metadata_only/base.yaml create mode 100644 tests/fixtures/governance_scenarios/descriptive_metadata_only/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/descriptive_metadata_only/expected.json create mode 100644 tests/fixtures/governance_scenarios/draft_entity_change/base.yaml create mode 100644 tests/fixtures/governance_scenarios/draft_entity_change/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/draft_entity_change/expected.json create mode 100644 tests/fixtures/governance_scenarios/enum_reduction/base.yaml create mode 100644 tests/fixtures/governance_scenarios/enum_reduction/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/enum_reduction/expected.json create mode 100644 tests/fixtures/governance_scenarios/logical_type_change/base.yaml create mode 100644 tests/fixtures/governance_scenarios/logical_type_change/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/logical_type_change/expected.json create mode 100644 tests/fixtures/governance_scenarios/manual_version_change/base.yaml create mode 100644 tests/fixtures/governance_scenarios/manual_version_change/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/manual_version_change/expected.json create mode 100644 tests/fixtures/governance_scenarios/merge_conflict/base.yaml create mode 100644 tests/fixtures/governance_scenarios/merge_conflict/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/merge_conflict/expected.json create mode 100644 tests/fixtures/governance_scenarios/no_change/base.yaml create mode 100644 tests/fixtures/governance_scenarios/no_change/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/no_change/expected.json create mode 100644 tests/fixtures/governance_scenarios/physical_name_identity_stability/base.yaml create mode 100644 tests/fixtures/governance_scenarios/physical_name_identity_stability/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/physical_name_identity_stability/expected.json create mode 100644 tests/fixtures/governance_scenarios/physical_type_narrowing/base.yaml create mode 100644 tests/fixtures/governance_scenarios/physical_type_narrowing/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/physical_type_narrowing/expected.json create mode 100644 tests/fixtures/governance_scenarios/property_addition/base.yaml create mode 100644 tests/fixtures/governance_scenarios/property_addition/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/property_addition/expected.json create mode 100644 tests/fixtures/governance_scenarios/property_removal/base.yaml create mode 100644 tests/fixtures/governance_scenarios/property_removal/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/property_removal/expected.json create mode 100644 tests/fixtures/governance_scenarios/relationship_removal/base.yaml create mode 100644 tests/fixtures/governance_scenarios/relationship_removal/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/relationship_removal/expected.json create mode 100644 tests/fixtures/governance_scenarios/required_tightening/base.yaml create mode 100644 tests/fixtures/governance_scenarios/required_tightening/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/required_tightening/expected.json create mode 100644 tests/fixtures/governance_scenarios/retired_contract_mutation/base.yaml create mode 100644 tests/fixtures/governance_scenarios/retired_contract_mutation/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/retired_contract_mutation/expected.json create mode 100644 tests/fixtures/governance_scenarios/schema_addition/base.yaml create mode 100644 tests/fixtures/governance_scenarios/schema_addition/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/schema_addition/expected.json create mode 100644 tests/fixtures/governance_scenarios/schema_removal/base.yaml create mode 100644 tests/fixtures/governance_scenarios/schema_removal/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/schema_removal/expected.json create mode 100644 tests/fixtures/governance_scenarios/validation_failure/base.yaml create mode 100644 tests/fixtures/governance_scenarios/validation_failure/candidate.yaml create mode 100644 tests/fixtures/governance_scenarios/validation_failure/expected.json create mode 100644 tests/test_governance_golden_scenarios.py diff --git a/tests/fixtures/governance_scenarios/active_to_retired_transition/base.yaml b/tests/fixtures/governance_scenarios/active_to_retired_transition/base.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/active_to_retired_transition/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/active_to_retired_transition/candidate.yaml b/tests/fixtures/governance_scenarios/active_to_retired_transition/candidate.yaml new file mode 100644 index 0000000..618b2b8 --- /dev/null +++ b/tests/fixtures/governance_scenarios/active_to_retired_transition/candidate.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: retired +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/active_to_retired_transition/expected.json b/tests/fixtures/governance_scenarios/active_to_retired_transition/expected.json new file mode 100644 index 0000000..d800c96 --- /dev/null +++ b/tests/fixtures/governance_scenarios/active_to_retired_transition/expected.json @@ -0,0 +1,71 @@ +{ + "breaking": false, + "changes": [ + { + "after": "retired", + "before": "active", + "breaking": false, + "changeType": "DEPRECATE", + "domain": "LIFECYCLE", + "entityType": "CONTRACT", + "evidence": [], + "field": "status", + "identity": [ + "orders-contract" + ], + "path": "status", + "reasonCodes": [] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "REVIEW", + "decisionId": "b18506a1-f428-510f-9c5b-848054f2b03f", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": true, + "versionViolation": false, + "violations": [ + { + "code": "CONTRACT_RETIRED_TRANSITION", + "details": {}, + "message": "Contract transition to retired status requires governance review.", + "path": "status", + "severity": "WARNING" + } + ] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT", + "CONTRACT_RETIRED_TRANSITION" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Only descriptive metadata changed; no required version bump", + "path": null, + "severity": "INFO" + }, + { + "code": "CONTRACT_RETIRED_TRANSITION", + "details": {}, + "message": "Contract transition to retired status requires governance review.", + "path": "status", + "severity": "WARNING" + } + ], + "requiredVersionBump": "none", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/contract_id_change/base.yaml b/tests/fixtures/governance_scenarios/contract_id_change/base.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/contract_id_change/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/contract_id_change/candidate.yaml b/tests/fixtures/governance_scenarios/contract_id_change/candidate.yaml new file mode 100644 index 0000000..647ce9d --- /dev/null +++ b/tests/fixtures/governance_scenarios/contract_id_change/candidate.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: renamed-orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/contract_id_change/expected.json b/tests/fixtures/governance_scenarios/contract_id_change/expected.json new file mode 100644 index 0000000..60ce02b --- /dev/null +++ b/tests/fixtures/governance_scenarios/contract_id_change/expected.json @@ -0,0 +1,73 @@ +{ + "breaking": true, + "changes": [ + { + "after": "renamed-orders-contract", + "before": "orders-contract", + "breaking": true, + "changeType": "MODIFY", + "domain": "IDENTITY", + "entityType": "CONTRACT", + "evidence": [], + "field": "id", + "identity": [ + "orders-contract" + ], + "path": "id", + "reasonCodes": [ + "CONTRACT_ID_CHANGED" + ] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "BLOCK", + "decisionId": "750bd41f-1b63-5de6-a47c-47d9515c4d10", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": true, + "retiredViolation": false, + "valid": false, + "versionViolation": false, + "violations": [ + { + "code": "CONTRACT_ID_CHANGED", + "details": {}, + "message": "Contract ID mismatch. You changed the root ID of the contract, which is immutable. If you want to create a new contract, use 'semapact import --new' or change the ID back.", + "path": "id", + "severity": "ERROR" + } + ] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT", + "CONTRACT_ID_CHANGED" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Breaking lifecycle changes require a major version bump", + "path": null, + "severity": "INFO" + }, + { + "code": "CONTRACT_ID_CHANGED", + "details": {}, + "message": "Contract ID mismatch. You changed the root ID of the contract, which is immutable. If you want to create a new contract, use 'semapact import --new' or change the ID back.", + "path": "id", + "severity": "ERROR" + } + ], + "requiredVersionBump": "major", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/decimal_precision_reduction/base.yaml b/tests/fixtures/governance_scenarios/decimal_precision_reduction/base.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/decimal_precision_reduction/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/decimal_precision_reduction/candidate.yaml b/tests/fixtures/governance_scenarios/decimal_precision_reduction/candidate.yaml new file mode 100644 index 0000000..a10fb97 --- /dev/null +++ b/tests/fixtures/governance_scenarios/decimal_precision_reduction/candidate.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(8,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/decimal_precision_reduction/expected.json b/tests/fixtures/governance_scenarios/decimal_precision_reduction/expected.json new file mode 100644 index 0000000..ab4d9b7 --- /dev/null +++ b/tests/fixtures/governance_scenarios/decimal_precision_reduction/expected.json @@ -0,0 +1,74 @@ +{ + "breaking": true, + "changes": [ + { + "after": "decimal(8,2)", + "before": "decimal(10,2)", + "breaking": true, + "changeType": "MODIFY", + "domain": "STRUCTURE", + "entityType": "PROPERTY", + "evidence": [], + "field": "physicalType", + "identity": [ + "orders", + "amount" + ], + "path": "schema[orders].properties[amount].physicalType", + "reasonCodes": [ + "DECIMAL_PRECISION_REDUCED" + ] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "REVIEW", + "decisionId": "2300dfb4-71cc-5601-8b7c-eeb1cdad2306", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": false, + "versionViolation": false, + "violations": [ + { + "code": "DECIMAL_PRECISION_REDUCED", + "details": {}, + "message": "Decimal precision reduced from 'decimal(10,2)' to 'decimal(8,2)'", + "path": "schema[orders].properties[amount].physicalType", + "severity": "WARNING" + } + ] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT", + "DECIMAL_PRECISION_REDUCED" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Breaking lifecycle changes require a major version bump", + "path": null, + "severity": "INFO" + }, + { + "code": "DECIMAL_PRECISION_REDUCED", + "details": {}, + "message": "Decimal precision reduced from 'decimal(10,2)' to 'decimal(8,2)'", + "path": "schema[orders].properties[amount].physicalType", + "severity": "WARNING" + } + ], + "requiredVersionBump": "major", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/decimal_scale_reduction/base.yaml b/tests/fixtures/governance_scenarios/decimal_scale_reduction/base.yaml new file mode 100644 index 0000000..e44d4b8 --- /dev/null +++ b/tests/fixtures/governance_scenarios/decimal_scale_reduction/base.yaml @@ -0,0 +1,26 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract with 4-decimal precision +schema: + - id: orders + name: orders + physicalName: tbl_orders + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,4) + required: false diff --git a/tests/fixtures/governance_scenarios/decimal_scale_reduction/candidate.yaml b/tests/fixtures/governance_scenarios/decimal_scale_reduction/candidate.yaml new file mode 100644 index 0000000..1306f82 --- /dev/null +++ b/tests/fixtures/governance_scenarios/decimal_scale_reduction/candidate.yaml @@ -0,0 +1,26 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract with 4-decimal precision +schema: + - id: orders + name: orders + physicalName: tbl_orders + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false diff --git a/tests/fixtures/governance_scenarios/decimal_scale_reduction/expected.json b/tests/fixtures/governance_scenarios/decimal_scale_reduction/expected.json new file mode 100644 index 0000000..9e26be9 --- /dev/null +++ b/tests/fixtures/governance_scenarios/decimal_scale_reduction/expected.json @@ -0,0 +1,74 @@ +{ + "breaking": true, + "changes": [ + { + "after": "decimal(10,2)", + "before": "decimal(10,4)", + "breaking": true, + "changeType": "MODIFY", + "domain": "STRUCTURE", + "entityType": "PROPERTY", + "evidence": [], + "field": "physicalType", + "identity": [ + "orders", + "amount" + ], + "path": "schema[orders].properties[amount].physicalType", + "reasonCodes": [ + "DECIMAL_SCALE_REDUCED" + ] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "REVIEW", + "decisionId": "d0c6689c-2f79-5a9f-999d-6b1a2e75fefc", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": false, + "versionViolation": false, + "violations": [ + { + "code": "DECIMAL_SCALE_REDUCED", + "details": {}, + "message": "Decimal scale reduced from 'decimal(10,4)' to 'decimal(10,2)'", + "path": "schema[orders].properties[amount].physicalType", + "severity": "WARNING" + } + ] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT", + "DECIMAL_SCALE_REDUCED" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Breaking lifecycle changes require a major version bump", + "path": null, + "severity": "INFO" + }, + { + "code": "DECIMAL_SCALE_REDUCED", + "details": {}, + "message": "Decimal scale reduced from 'decimal(10,4)' to 'decimal(10,2)'", + "path": "schema[orders].properties[amount].physicalType", + "severity": "WARNING" + } + ], + "requiredVersionBump": "major", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/decimal_widening/base.yaml b/tests/fixtures/governance_scenarios/decimal_widening/base.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/decimal_widening/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/decimal_widening/candidate.yaml b/tests/fixtures/governance_scenarios/decimal_widening/candidate.yaml new file mode 100644 index 0000000..627e5b5 --- /dev/null +++ b/tests/fixtures/governance_scenarios/decimal_widening/candidate.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(14,4) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/decimal_widening/expected.json b/tests/fixtures/governance_scenarios/decimal_widening/expected.json new file mode 100644 index 0000000..92a01c8 --- /dev/null +++ b/tests/fixtures/governance_scenarios/decimal_widening/expected.json @@ -0,0 +1,56 @@ +{ + "breaking": false, + "changes": [ + { + "after": "decimal(14,4)", + "before": "decimal(10,2)", + "breaking": false, + "changeType": "MODIFY", + "domain": "STRUCTURE", + "entityType": "PROPERTY", + "evidence": [], + "field": "physicalType", + "identity": [ + "orders", + "amount" + ], + "path": "schema[orders].properties[amount].physicalType", + "reasonCodes": [] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "REVIEW", + "decisionId": "04ce4d23-79dd-54cd-ae92-c7e65744412c", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": true, + "versionViolation": false, + "violations": [] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Non-breaking structural or quality changes require a minor version bump", + "path": null, + "severity": "INFO" + } + ], + "requiredVersionBump": "minor", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/deprecated_entity_change/base.yaml b/tests/fixtures/governance_scenarios/deprecated_entity_change/base.yaml new file mode 100644 index 0000000..763147b --- /dev/null +++ b/tests/fixtures/governance_scenarios/deprecated_entity_change/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Contract with deprecated property +schema: + - id: orders + name: orders + physicalName: tbl_orders + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + - id: legacy_code + name: legacy_code + physicalName: col_legacy + logicalType: string + physicalType: varchar(255) + required: false + customProperties: + - property: lifecycleStatus + value: deprecated diff --git a/tests/fixtures/governance_scenarios/deprecated_entity_change/candidate.yaml b/tests/fixtures/governance_scenarios/deprecated_entity_change/candidate.yaml new file mode 100644 index 0000000..0a411f7 --- /dev/null +++ b/tests/fixtures/governance_scenarios/deprecated_entity_change/candidate.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Contract with deprecated property +schema: + - id: orders + name: orders + physicalName: tbl_orders + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + - id: legacy_code + name: legacy_code + physicalName: col_legacy + logicalType: integer + physicalType: bigint + required: true + customProperties: + - property: lifecycleStatus + value: deprecated diff --git a/tests/fixtures/governance_scenarios/deprecated_entity_change/expected.json b/tests/fixtures/governance_scenarios/deprecated_entity_change/expected.json new file mode 100644 index 0000000..0bdb866 --- /dev/null +++ b/tests/fixtures/governance_scenarios/deprecated_entity_change/expected.json @@ -0,0 +1,88 @@ +{ + "breaking": false, + "changes": [ + { + "after": "integer", + "before": "string", + "breaking": false, + "changeType": "MODIFY", + "domain": "STRUCTURE", + "entityType": "PROPERTY", + "evidence": [], + "field": "logicalType", + "identity": [ + "orders", + "legacy_code" + ], + "path": "schema[orders].properties[legacy_code].logicalType", + "reasonCodes": [] + }, + { + "after": "bigint", + "before": "varchar(255)", + "breaking": false, + "changeType": "MODIFY", + "domain": "STRUCTURE", + "entityType": "PROPERTY", + "evidence": [], + "field": "physicalType", + "identity": [ + "orders", + "legacy_code" + ], + "path": "schema[orders].properties[legacy_code].physicalType", + "reasonCodes": [] + }, + { + "after": true, + "before": false, + "breaking": false, + "changeType": "MODIFY", + "domain": "STRUCTURE", + "entityType": "PROPERTY", + "evidence": [], + "field": "required", + "identity": [ + "orders", + "legacy_code" + ], + "path": "schema[orders].properties[legacy_code].required", + "reasonCodes": [] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "REVIEW", + "decisionId": "17fec941-afae-54ce-9764-4d2efab07418", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": true, + "versionViolation": false, + "violations": [] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Non-breaking structural or quality changes require a minor version bump", + "path": null, + "severity": "INFO" + } + ], + "requiredVersionBump": "minor", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/descriptive_metadata_only/base.yaml b/tests/fixtures/governance_scenarios/descriptive_metadata_only/base.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/descriptive_metadata_only/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/descriptive_metadata_only/candidate.yaml b/tests/fixtures/governance_scenarios/descriptive_metadata_only/candidate.yaml new file mode 100644 index 0000000..4994280 --- /dev/null +++ b/tests/fixtures/governance_scenarios/descriptive_metadata_only/candidate.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Updated descriptive documentation for orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description update + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier updated notes + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount in USD diff --git a/tests/fixtures/governance_scenarios/descriptive_metadata_only/expected.json b/tests/fixtures/governance_scenarios/descriptive_metadata_only/expected.json new file mode 100644 index 0000000..bc8e2c3 --- /dev/null +++ b/tests/fixtures/governance_scenarios/descriptive_metadata_only/expected.json @@ -0,0 +1,106 @@ +{ + "breaking": false, + "changes": [ + { + "after": "Orders table description update", + "before": "Orders table description", + "breaking": false, + "changeType": "MODIFY", + "domain": "METADATA", + "entityType": "SCHEMA", + "evidence": [], + "field": "description", + "identity": [ + "orders" + ], + "path": "schema[orders].description", + "reasonCodes": [] + }, + { + "after": "Order total amount in USD", + "before": "Order total amount", + "breaking": false, + "changeType": "MODIFY", + "domain": "METADATA", + "entityType": "PROPERTY", + "evidence": [], + "field": "description", + "identity": [ + "orders", + "amount" + ], + "path": "schema[orders].properties[amount].description", + "reasonCodes": [] + }, + { + "after": "Primary identifier updated notes", + "before": "Primary identifier", + "breaking": false, + "changeType": "MODIFY", + "domain": "METADATA", + "entityType": "PROPERTY", + "evidence": [], + "field": "description", + "identity": [ + "orders", + "id" + ], + "path": "schema[orders].properties[id].description", + "reasonCodes": [] + }, + { + "after": { + "purpose": "Updated descriptive documentation for orders contract" + }, + "before": { + "purpose": "Authoritative orders contract" + }, + "breaking": false, + "changeType": "MODIFY", + "domain": "METADATA", + "entityType": "CONTRACT", + "evidence": [], + "field": "description", + "identity": [ + "orders-contract" + ], + "path": "description", + "reasonCodes": [] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "ALLOW", + "decisionId": "0a749876-f87f-5348-8b7b-4694ffd3aba9", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": true, + "versionViolation": false, + "violations": [] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Only descriptive metadata changed; no required version bump", + "path": null, + "severity": "INFO" + } + ], + "requiredVersionBump": "none", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/draft_entity_change/base.yaml b/tests/fixtures/governance_scenarios/draft_entity_change/base.yaml new file mode 100644 index 0000000..4e8c467 --- /dev/null +++ b/tests/fixtures/governance_scenarios/draft_entity_change/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Contract with draft property +schema: + - id: orders + name: orders + physicalName: tbl_orders + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + - id: experimental_feature + name: experimental_feature + physicalName: col_exp + logicalType: string + physicalType: varchar(255) + required: false + customProperties: + - property: lifecycleStatus + value: draft diff --git a/tests/fixtures/governance_scenarios/draft_entity_change/candidate.yaml b/tests/fixtures/governance_scenarios/draft_entity_change/candidate.yaml new file mode 100644 index 0000000..5ba97e0 --- /dev/null +++ b/tests/fixtures/governance_scenarios/draft_entity_change/candidate.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Contract with draft property +schema: + - id: orders + name: orders + physicalName: tbl_orders + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + - id: experimental_feature + name: experimental_feature + physicalName: col_exp + logicalType: integer + physicalType: bigint + required: true + customProperties: + - property: lifecycleStatus + value: draft diff --git a/tests/fixtures/governance_scenarios/draft_entity_change/expected.json b/tests/fixtures/governance_scenarios/draft_entity_change/expected.json new file mode 100644 index 0000000..1a3dbf8 --- /dev/null +++ b/tests/fixtures/governance_scenarios/draft_entity_change/expected.json @@ -0,0 +1,88 @@ +{ + "breaking": false, + "changes": [ + { + "after": "integer", + "before": "string", + "breaking": false, + "changeType": "MODIFY", + "domain": "STRUCTURE", + "entityType": "PROPERTY", + "evidence": [], + "field": "logicalType", + "identity": [ + "orders", + "experimental_feature" + ], + "path": "schema[orders].properties[experimental_feature].logicalType", + "reasonCodes": [] + }, + { + "after": "bigint", + "before": "varchar(255)", + "breaking": false, + "changeType": "MODIFY", + "domain": "STRUCTURE", + "entityType": "PROPERTY", + "evidence": [], + "field": "physicalType", + "identity": [ + "orders", + "experimental_feature" + ], + "path": "schema[orders].properties[experimental_feature].physicalType", + "reasonCodes": [] + }, + { + "after": true, + "before": false, + "breaking": false, + "changeType": "MODIFY", + "domain": "STRUCTURE", + "entityType": "PROPERTY", + "evidence": [], + "field": "required", + "identity": [ + "orders", + "experimental_feature" + ], + "path": "schema[orders].properties[experimental_feature].required", + "reasonCodes": [] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "REVIEW", + "decisionId": "87f536c6-c55c-5111-b573-366b5a5fe71a", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": true, + "versionViolation": false, + "violations": [] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Non-breaking structural or quality changes require a minor version bump", + "path": null, + "severity": "INFO" + } + ], + "requiredVersionBump": "minor", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/enum_reduction/base.yaml b/tests/fixtures/governance_scenarios/enum_reduction/base.yaml new file mode 100644 index 0000000..8e81990 --- /dev/null +++ b/tests/fixtures/governance_scenarios/enum_reduction/base.yaml @@ -0,0 +1,31 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Contract with enum status +schema: + - id: orders + name: orders + physicalName: tbl_orders + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + - id: status + name: status + physicalName: col_status + logicalType: string + physicalType: varchar(32) + required: true + enum: + - PENDING + - PROCESSING + - COMPLETED + - CANCELLED diff --git a/tests/fixtures/governance_scenarios/enum_reduction/candidate.yaml b/tests/fixtures/governance_scenarios/enum_reduction/candidate.yaml new file mode 100644 index 0000000..4cd354d --- /dev/null +++ b/tests/fixtures/governance_scenarios/enum_reduction/candidate.yaml @@ -0,0 +1,30 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Contract with enum status +schema: + - id: orders + name: orders + physicalName: tbl_orders + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + - id: status + name: status + physicalName: col_status + logicalType: string + physicalType: varchar(32) + required: true + enum: + - PENDING + - PROCESSING + - COMPLETED diff --git a/tests/fixtures/governance_scenarios/enum_reduction/expected.json b/tests/fixtures/governance_scenarios/enum_reduction/expected.json new file mode 100644 index 0000000..1b17e05 --- /dev/null +++ b/tests/fixtures/governance_scenarios/enum_reduction/expected.json @@ -0,0 +1,39 @@ +{ + "breaking": false, + "changes": [], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "ALLOW", + "decisionId": "59d98075-e496-52c7-9a72-6045ccb21b0d", + "evidence": { + "hasChanges": false, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": true, + "versionViolation": false, + "violations": [] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "No contract changes detected", + "path": null, + "severity": "INFO" + } + ], + "requiredVersionBump": "none", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/logical_type_change/base.yaml b/tests/fixtures/governance_scenarios/logical_type_change/base.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/logical_type_change/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/logical_type_change/candidate.yaml b/tests/fixtures/governance_scenarios/logical_type_change/candidate.yaml new file mode 100644 index 0000000..9d22e79 --- /dev/null +++ b/tests/fixtures/governance_scenarios/logical_type_change/candidate.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: string + physicalType: varchar(64) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/logical_type_change/expected.json b/tests/fixtures/governance_scenarios/logical_type_change/expected.json new file mode 100644 index 0000000..d967e1b --- /dev/null +++ b/tests/fixtures/governance_scenarios/logical_type_change/expected.json @@ -0,0 +1,90 @@ +{ + "breaking": true, + "changes": [ + { + "after": "string", + "before": "number", + "breaking": true, + "changeType": "MODIFY", + "domain": "STRUCTURE", + "entityType": "PROPERTY", + "evidence": [], + "field": "logicalType", + "identity": [ + "orders", + "amount" + ], + "path": "schema[orders].properties[amount].logicalType", + "reasonCodes": [ + "LOGICAL_TYPE_CHANGED" + ] + }, + { + "after": "varchar(64)", + "before": "decimal(10,2)", + "breaking": false, + "changeType": "MODIFY", + "domain": "STRUCTURE", + "entityType": "PROPERTY", + "evidence": [], + "field": "physicalType", + "identity": [ + "orders", + "amount" + ], + "path": "schema[orders].properties[amount].physicalType", + "reasonCodes": [] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "REVIEW", + "decisionId": "106e6a6f-aee7-500e-bc67-1da9acba847b", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": false, + "versionViolation": false, + "violations": [ + { + "code": "LOGICAL_TYPE_CHANGED", + "details": {}, + "message": "Logical type changed from 'number' to 'string'", + "path": "schema[orders].properties[amount].logicalType", + "severity": "WARNING" + } + ] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT", + "LOGICAL_TYPE_CHANGED" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Breaking lifecycle changes require a major version bump", + "path": null, + "severity": "INFO" + }, + { + "code": "LOGICAL_TYPE_CHANGED", + "details": {}, + "message": "Logical type changed from 'number' to 'string'", + "path": "schema[orders].properties[amount].logicalType", + "severity": "WARNING" + } + ], + "requiredVersionBump": "major", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/manual_version_change/base.yaml b/tests/fixtures/governance_scenarios/manual_version_change/base.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/manual_version_change/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/manual_version_change/candidate.yaml b/tests/fixtures/governance_scenarios/manual_version_change/candidate.yaml new file mode 100644 index 0000000..1fe60f5 --- /dev/null +++ b/tests/fixtures/governance_scenarios/manual_version_change/candidate.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 2.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/manual_version_change/expected.json b/tests/fixtures/governance_scenarios/manual_version_change/expected.json new file mode 100644 index 0000000..cf3cc1c --- /dev/null +++ b/tests/fixtures/governance_scenarios/manual_version_change/expected.json @@ -0,0 +1,73 @@ +{ + "breaking": true, + "changes": [ + { + "after": "2.0.0", + "before": "1.0.0", + "breaking": true, + "changeType": "MODIFY", + "domain": "VERSION", + "entityType": "CONTRACT", + "evidence": [], + "field": "version", + "identity": [ + "orders-contract" + ], + "path": "version", + "reasonCodes": [ + "CONTRACT_VERSION_MANUALLY_CHANGED" + ] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "BLOCK", + "decisionId": "642819bc-96eb-551c-abd0-ed2ba0187d27", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": false, + "versionViolation": true, + "violations": [ + { + "code": "CONTRACT_VERSION_MANUALLY_CHANGED", + "details": {}, + "message": "Contract version mismatch. Contract versions are release-managed and cannot be manually updated during normal import/merge. Please revert the version change and use 'semapact release prepare'.", + "path": "version", + "severity": "ERROR" + } + ] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT", + "CONTRACT_VERSION_MANUALLY_CHANGED" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Breaking lifecycle changes require a major version bump", + "path": null, + "severity": "INFO" + }, + { + "code": "CONTRACT_VERSION_MANUALLY_CHANGED", + "details": {}, + "message": "Contract version mismatch. Contract versions are release-managed and cannot be manually updated during normal import/merge. Please revert the version change and use 'semapact release prepare'.", + "path": "version", + "severity": "ERROR" + } + ], + "requiredVersionBump": "major", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/merge_conflict/base.yaml b/tests/fixtures/governance_scenarios/merge_conflict/base.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/merge_conflict/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/merge_conflict/candidate.yaml b/tests/fixtures/governance_scenarios/merge_conflict/candidate.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/merge_conflict/candidate.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/merge_conflict/expected.json b/tests/fixtures/governance_scenarios/merge_conflict/expected.json new file mode 100644 index 0000000..c9a2591 --- /dev/null +++ b/tests/fixtures/governance_scenarios/merge_conflict/expected.json @@ -0,0 +1,51 @@ +{ + "breaking": false, + "changes": [], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "REVIEW", + "decisionId": "1678a050-8f17-5739-bfb2-83ab7aea05ad", + "evidence": { + "hasChanges": false, + "mergeConflictsCount": 1 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": true, + "versionViolation": false, + "violations": [] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT", + "MERGE_CONFLICT" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "No contract changes detected", + "path": null, + "severity": "INFO" + }, + { + "code": "MERGE_CONFLICT", + "details": { + "property_name": "amount", + "rule": "description_conflict", + "schema_id": "orders" + }, + "message": "Conflicting description updates from upstream branches", + "path": "schema[orders].properties[amount]", + "severity": "WARNING" + } + ], + "requiredVersionBump": "none", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/no_change/base.yaml b/tests/fixtures/governance_scenarios/no_change/base.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/no_change/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/no_change/candidate.yaml b/tests/fixtures/governance_scenarios/no_change/candidate.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/no_change/candidate.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/no_change/expected.json b/tests/fixtures/governance_scenarios/no_change/expected.json new file mode 100644 index 0000000..3c5908c --- /dev/null +++ b/tests/fixtures/governance_scenarios/no_change/expected.json @@ -0,0 +1,39 @@ +{ + "breaking": false, + "changes": [], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "ALLOW", + "decisionId": "e496a148-d48f-528f-b007-3a9dd1d7f060", + "evidence": { + "hasChanges": false, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": true, + "versionViolation": false, + "violations": [] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "No contract changes detected", + "path": null, + "severity": "INFO" + } + ], + "requiredVersionBump": "none", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/physical_name_identity_stability/base.yaml b/tests/fixtures/governance_scenarios/physical_name_identity_stability/base.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/physical_name_identity_stability/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/physical_name_identity_stability/candidate.yaml b/tests/fixtures/governance_scenarios/physical_name_identity_stability/candidate.yaml new file mode 100644 index 0000000..0b15c1b --- /dev/null +++ b/tests/fixtures/governance_scenarios/physical_name_identity_stability/candidate.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders_renamed_in_db + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id_renamed_in_db + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/physical_name_identity_stability/expected.json b/tests/fixtures/governance_scenarios/physical_name_identity_stability/expected.json new file mode 100644 index 0000000..d6ebcbb --- /dev/null +++ b/tests/fixtures/governance_scenarios/physical_name_identity_stability/expected.json @@ -0,0 +1,71 @@ +{ + "breaking": false, + "changes": [ + { + "after": "tbl_orders_renamed_in_db", + "before": "tbl_orders", + "breaking": false, + "changeType": "MODIFY", + "domain": "STRUCTURE", + "entityType": "SCHEMA", + "evidence": [], + "field": "physicalName", + "identity": [ + "orders" + ], + "path": "schema[orders].physicalName", + "reasonCodes": [] + }, + { + "after": "col_id_renamed_in_db", + "before": "col_id", + "breaking": false, + "changeType": "MODIFY", + "domain": "STRUCTURE", + "entityType": "PROPERTY", + "evidence": [], + "field": "physicalName", + "identity": [ + "orders", + "id" + ], + "path": "schema[orders].properties[id].physicalName", + "reasonCodes": [] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "REVIEW", + "decisionId": "a9b4d271-2c80-56db-a119-c87110dc40f4", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": true, + "versionViolation": false, + "violations": [] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Non-breaking structural or quality changes require a minor version bump", + "path": null, + "severity": "INFO" + } + ], + "requiredVersionBump": "minor", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/physical_type_narrowing/base.yaml b/tests/fixtures/governance_scenarios/physical_type_narrowing/base.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/physical_type_narrowing/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/physical_type_narrowing/candidate.yaml b/tests/fixtures/governance_scenarios/physical_type_narrowing/candidate.yaml new file mode 100644 index 0000000..36c55ef --- /dev/null +++ b/tests/fixtures/governance_scenarios/physical_type_narrowing/candidate.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(32) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/physical_type_narrowing/expected.json b/tests/fixtures/governance_scenarios/physical_type_narrowing/expected.json new file mode 100644 index 0000000..735f108 --- /dev/null +++ b/tests/fixtures/governance_scenarios/physical_type_narrowing/expected.json @@ -0,0 +1,74 @@ +{ + "breaking": true, + "changes": [ + { + "after": "varchar(32)", + "before": "varchar(64)", + "breaking": true, + "changeType": "MODIFY", + "domain": "STRUCTURE", + "entityType": "PROPERTY", + "evidence": [], + "field": "physicalType", + "identity": [ + "orders", + "id" + ], + "path": "schema[orders].properties[id].physicalType", + "reasonCodes": [ + "PHYSICAL_TYPE_NARROWED" + ] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "REVIEW", + "decisionId": "4f94025b-d059-5dd6-a97f-80a2f124761b", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": false, + "versionViolation": false, + "violations": [ + { + "code": "PHYSICAL_TYPE_NARROWED", + "details": {}, + "message": "Physical type narrowed from 'varchar(64)' to 'varchar(32)'", + "path": "schema[orders].properties[id].physicalType", + "severity": "WARNING" + } + ] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT", + "PHYSICAL_TYPE_NARROWED" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Breaking lifecycle changes require a major version bump", + "path": null, + "severity": "INFO" + }, + { + "code": "PHYSICAL_TYPE_NARROWED", + "details": {}, + "message": "Physical type narrowed from 'varchar(64)' to 'varchar(32)'", + "path": "schema[orders].properties[id].physicalType", + "severity": "WARNING" + } + ], + "requiredVersionBump": "major", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/property_addition/base.yaml b/tests/fixtures/governance_scenarios/property_addition/base.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/property_addition/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/property_addition/candidate.yaml b/tests/fixtures/governance_scenarios/property_addition/candidate.yaml new file mode 100644 index 0000000..82cbc29 --- /dev/null +++ b/tests/fixtures/governance_scenarios/property_addition/candidate.yaml @@ -0,0 +1,36 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount + - id: customer_id + name: customer_id + physicalName: col_customer_id + logicalType: string + physicalType: varchar(64) + required: false + description: Foreign reference to customer diff --git a/tests/fixtures/governance_scenarios/property_addition/expected.json b/tests/fixtures/governance_scenarios/property_addition/expected.json new file mode 100644 index 0000000..1cfada2 --- /dev/null +++ b/tests/fixtures/governance_scenarios/property_addition/expected.json @@ -0,0 +1,71 @@ +{ + "breaking": false, + "changes": [ + { + "after": { + "description": "Foreign reference to customer", + "id": "customer_id", + "logicalType": "string", + "name": "customer_id", + "physicalName": "col_customer_id", + "physicalType": "varchar(64)", + "required": false + }, + "before": null, + "breaking": false, + "changeType": "ADD", + "domain": "STRUCTURE", + "entityType": "PROPERTY", + "evidence": [], + "field": null, + "identity": [ + "orders", + "customer_id" + ], + "path": "schema[orders].properties[customer_id]", + "reasonCodes": [] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "REVIEW", + "decisionId": "6aca9196-551a-52f4-a814-e1a86f6ba3f3", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": true, + "versionViolation": false, + "violations": [] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Non-breaking structural or quality changes require a minor version bump", + "path": null, + "severity": "INFO" + }, + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Schema or property additions require a minor version bump", + "path": null, + "severity": "INFO" + } + ], + "requiredVersionBump": "minor", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/property_removal/base.yaml b/tests/fixtures/governance_scenarios/property_removal/base.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/property_removal/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/property_removal/candidate.yaml b/tests/fixtures/governance_scenarios/property_removal/candidate.yaml new file mode 100644 index 0000000..53e1308 --- /dev/null +++ b/tests/fixtures/governance_scenarios/property_removal/candidate.yaml @@ -0,0 +1,22 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier diff --git a/tests/fixtures/governance_scenarios/property_removal/expected.json b/tests/fixtures/governance_scenarios/property_removal/expected.json new file mode 100644 index 0000000..b12a340 --- /dev/null +++ b/tests/fixtures/governance_scenarios/property_removal/expected.json @@ -0,0 +1,82 @@ +{ + "breaking": true, + "changes": [ + { + "after": null, + "before": { + "description": "Order total amount", + "id": "amount", + "logicalType": "number", + "name": "amount", + "physicalName": "col_amount", + "physicalType": "decimal(10,2)", + "required": false + }, + "breaking": true, + "changeType": "REMOVE", + "domain": "STRUCTURE", + "entityType": "PROPERTY", + "evidence": [], + "field": null, + "identity": [ + "orders", + "amount" + ], + "path": "schema[orders].properties[amount]", + "reasonCodes": [ + "PROPERTY_REMOVED" + ] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "REVIEW", + "decisionId": "2ac271d9-2c86-578a-aafb-394ce63f3a4b", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": false, + "versionViolation": false, + "violations": [ + { + "code": "PROPERTY_REMOVED", + "details": {}, + "message": "Property removed from active lifecycle scope", + "path": "schema[orders].properties[amount]", + "severity": "WARNING" + } + ] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT", + "PROPERTY_REMOVED" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Breaking lifecycle changes require a major version bump", + "path": null, + "severity": "INFO" + }, + { + "code": "PROPERTY_REMOVED", + "details": {}, + "message": "Property removed from active lifecycle scope", + "path": "schema[orders].properties[amount]", + "severity": "WARNING" + } + ], + "requiredVersionBump": "major", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/relationship_removal/base.yaml b/tests/fixtures/governance_scenarios/relationship_removal/base.yaml new file mode 100644 index 0000000..55c7062 --- /dev/null +++ b/tests/fixtures/governance_scenarios/relationship_removal/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Contract with relationship +schema: + - id: orders + name: orders + physicalName: tbl_orders + relationships: + - type: foreignKey + to: customers.id + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + - id: customer_id + name: customer_id + physicalName: col_customer_id + logicalType: string + physicalType: varchar(64) + required: true diff --git a/tests/fixtures/governance_scenarios/relationship_removal/candidate.yaml b/tests/fixtures/governance_scenarios/relationship_removal/candidate.yaml new file mode 100644 index 0000000..0df4d47 --- /dev/null +++ b/tests/fixtures/governance_scenarios/relationship_removal/candidate.yaml @@ -0,0 +1,26 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Contract with relationship +schema: + - id: orders + name: orders + physicalName: tbl_orders + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + - id: customer_id + name: customer_id + physicalName: col_customer_id + logicalType: string + physicalType: varchar(64) + required: true diff --git a/tests/fixtures/governance_scenarios/relationship_removal/expected.json b/tests/fixtures/governance_scenarios/relationship_removal/expected.json new file mode 100644 index 0000000..453a76d --- /dev/null +++ b/tests/fixtures/governance_scenarios/relationship_removal/expected.json @@ -0,0 +1,77 @@ +{ + "breaking": true, + "changes": [ + { + "after": null, + "before": { + "to": "customers.id", + "type": "foreignKey" + }, + "breaking": true, + "changeType": "REMOVE", + "domain": "RELATIONSHIP", + "entityType": "RELATIONSHIP", + "evidence": [], + "field": null, + "identity": [ + "orders", + "foreignKey:->customers.id" + ], + "path": "schema[orders].relationships", + "reasonCodes": [ + "RELATIONSHIP_REMOVED" + ] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "REVIEW", + "decisionId": "1c53c03d-c05c-5416-9406-63703c8d5594", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": false, + "versionViolation": false, + "violations": [ + { + "code": "RELATIONSHIP_REMOVED", + "details": {}, + "message": "Relationship 'foreignKey:->customers.id' removed from active lifecycle scope. Downstream joins may fail.", + "path": "schema[orders].relationships", + "severity": "WARNING" + } + ] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT", + "RELATIONSHIP_REMOVED" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Breaking lifecycle changes require a major version bump", + "path": null, + "severity": "INFO" + }, + { + "code": "RELATIONSHIP_REMOVED", + "details": {}, + "message": "Relationship 'foreignKey:->customers.id' removed from active lifecycle scope. Downstream joins may fail.", + "path": "schema[orders].relationships", + "severity": "WARNING" + } + ], + "requiredVersionBump": "major", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/required_tightening/base.yaml b/tests/fixtures/governance_scenarios/required_tightening/base.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/required_tightening/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/required_tightening/candidate.yaml b/tests/fixtures/governance_scenarios/required_tightening/candidate.yaml new file mode 100644 index 0000000..50150ed --- /dev/null +++ b/tests/fixtures/governance_scenarios/required_tightening/candidate.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: true + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/required_tightening/expected.json b/tests/fixtures/governance_scenarios/required_tightening/expected.json new file mode 100644 index 0000000..8fe443c --- /dev/null +++ b/tests/fixtures/governance_scenarios/required_tightening/expected.json @@ -0,0 +1,74 @@ +{ + "breaking": true, + "changes": [ + { + "after": true, + "before": false, + "breaking": true, + "changeType": "MODIFY", + "domain": "STRUCTURE", + "entityType": "PROPERTY", + "evidence": [], + "field": "required", + "identity": [ + "orders", + "amount" + ], + "path": "schema[orders].properties[amount].required", + "reasonCodes": [ + "REQUIRED_TIGHTENED" + ] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "REVIEW", + "decisionId": "4ea6efa1-1081-56c1-8b31-a2645a4feabc", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": false, + "versionViolation": false, + "violations": [ + { + "code": "REQUIRED_TIGHTENED", + "details": {}, + "message": "Required flag tightened from False to True", + "path": "schema[orders].properties[amount].required", + "severity": "WARNING" + } + ] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT", + "REQUIRED_TIGHTENED" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Breaking lifecycle changes require a major version bump", + "path": null, + "severity": "INFO" + }, + { + "code": "REQUIRED_TIGHTENED", + "details": {}, + "message": "Required flag tightened from False to True", + "path": "schema[orders].properties[amount].required", + "severity": "WARNING" + } + ], + "requiredVersionBump": "major", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/retired_contract_mutation/base.yaml b/tests/fixtures/governance_scenarios/retired_contract_mutation/base.yaml new file mode 100644 index 0000000..0f99b97 --- /dev/null +++ b/tests/fixtures/governance_scenarios/retired_contract_mutation/base.yaml @@ -0,0 +1,26 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: retired +domain: commerce +description: + purpose: Retired orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false diff --git a/tests/fixtures/governance_scenarios/retired_contract_mutation/candidate.yaml b/tests/fixtures/governance_scenarios/retired_contract_mutation/candidate.yaml new file mode 100644 index 0000000..3ab6e24 --- /dev/null +++ b/tests/fixtures/governance_scenarios/retired_contract_mutation/candidate.yaml @@ -0,0 +1,26 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: retired +domain: commerce +description: + purpose: Retired orders contract - forbidden modification +schema: + - id: orders + name: orders + physicalName: tbl_orders + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false diff --git a/tests/fixtures/governance_scenarios/retired_contract_mutation/expected.json b/tests/fixtures/governance_scenarios/retired_contract_mutation/expected.json new file mode 100644 index 0000000..990a553 --- /dev/null +++ b/tests/fixtures/governance_scenarios/retired_contract_mutation/expected.json @@ -0,0 +1,75 @@ +{ + "breaking": false, + "changes": [ + { + "after": { + "purpose": "Retired orders contract - forbidden modification" + }, + "before": { + "purpose": "Retired orders contract" + }, + "breaking": false, + "changeType": "MODIFY", + "domain": "METADATA", + "entityType": "CONTRACT", + "evidence": [], + "field": "description", + "identity": [ + "orders-contract" + ], + "path": "description", + "reasonCodes": [] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "BLOCK", + "decisionId": "cbc08d31-312b-548a-9619-51dddd337642", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": true, + "valid": false, + "versionViolation": false, + "violations": [ + { + "code": "RETIRED_CONTRACT_MODIFIED", + "details": {}, + "message": "Cannot modify a retired contract.", + "path": "status", + "severity": "ERROR" + } + ] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT", + "RETIRED_CONTRACT_MODIFIED" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Only descriptive metadata changed; no required version bump", + "path": null, + "severity": "INFO" + }, + { + "code": "RETIRED_CONTRACT_MODIFIED", + "details": {}, + "message": "Cannot modify a retired contract.", + "path": "status", + "severity": "ERROR" + } + ], + "requiredVersionBump": "none", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/schema_addition/base.yaml b/tests/fixtures/governance_scenarios/schema_addition/base.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/schema_addition/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/schema_addition/candidate.yaml b/tests/fixtures/governance_scenarios/schema_addition/candidate.yaml new file mode 100644 index 0000000..0f2e327 --- /dev/null +++ b/tests/fixtures/governance_scenarios/schema_addition/candidate.yaml @@ -0,0 +1,41 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount + - id: line_items + name: line_items + physicalName: tbl_line_items + description: Line items table + properties: + - id: item_id + name: item_id + physicalName: col_item_id + logicalType: string + physicalType: varchar(64) + required: true + description: Line item identifier diff --git a/tests/fixtures/governance_scenarios/schema_addition/expected.json b/tests/fixtures/governance_scenarios/schema_addition/expected.json new file mode 100644 index 0000000..7f1827b --- /dev/null +++ b/tests/fixtures/governance_scenarios/schema_addition/expected.json @@ -0,0 +1,78 @@ +{ + "breaking": false, + "changes": [ + { + "after": { + "description": "Line items table", + "id": "line_items", + "name": "line_items", + "physicalName": "tbl_line_items", + "properties": [ + { + "description": "Line item identifier", + "id": "item_id", + "logicalType": "string", + "name": "item_id", + "physicalName": "col_item_id", + "physicalType": "varchar(64)", + "required": true + } + ] + }, + "before": null, + "breaking": false, + "changeType": "ADD", + "domain": "STRUCTURE", + "entityType": "SCHEMA", + "evidence": [], + "field": null, + "identity": [ + "line_items" + ], + "path": "schema[line_items]", + "reasonCodes": [] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "REVIEW", + "decisionId": "54299462-9f11-563c-a152-f70551d6c5ea", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": true, + "versionViolation": false, + "violations": [] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Non-breaking structural or quality changes require a minor version bump", + "path": null, + "severity": "INFO" + }, + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Schema or property additions require a minor version bump", + "path": null, + "severity": "INFO" + } + ], + "requiredVersionBump": "minor", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/schema_removal/base.yaml b/tests/fixtures/governance_scenarios/schema_removal/base.yaml new file mode 100644 index 0000000..533a802 --- /dev/null +++ b/tests/fixtures/governance_scenarios/schema_removal/base.yaml @@ -0,0 +1,30 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Multi-schema contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + - id: audit_log + name: audit_log + physicalName: tbl_audit_log + properties: + - id: log_id + name: log_id + physicalName: col_log_id + logicalType: string + physicalType: varchar(64) + required: true diff --git a/tests/fixtures/governance_scenarios/schema_removal/candidate.yaml b/tests/fixtures/governance_scenarios/schema_removal/candidate.yaml new file mode 100644 index 0000000..d44036a --- /dev/null +++ b/tests/fixtures/governance_scenarios/schema_removal/candidate.yaml @@ -0,0 +1,20 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Multi-schema contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true diff --git a/tests/fixtures/governance_scenarios/schema_removal/expected.json b/tests/fixtures/governance_scenarios/schema_removal/expected.json new file mode 100644 index 0000000..d315120 --- /dev/null +++ b/tests/fixtures/governance_scenarios/schema_removal/expected.json @@ -0,0 +1,87 @@ +{ + "breaking": true, + "changes": [ + { + "after": null, + "before": { + "id": "audit_log", + "name": "audit_log", + "physicalName": "tbl_audit_log", + "properties": [ + { + "id": "log_id", + "logicalType": "string", + "name": "log_id", + "physicalName": "col_log_id", + "physicalType": "varchar(64)", + "required": true + } + ] + }, + "breaking": true, + "changeType": "REMOVE", + "domain": "STRUCTURE", + "entityType": "SCHEMA", + "evidence": [], + "field": null, + "identity": [ + "audit_log" + ], + "path": "schema[audit_log]", + "reasonCodes": [ + "SCHEMA_REMOVED" + ] + } + ], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "REVIEW", + "decisionId": "e3aeba92-dbbf-5ea6-8f3f-5cbbf0bf2449", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": false, + "versionViolation": false, + "violations": [ + { + "code": "SCHEMA_REMOVED", + "details": {}, + "message": "Schema removed from active contract", + "path": "schema[audit_log]", + "severity": "WARNING" + } + ] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT", + "SCHEMA_REMOVED" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "Breaking lifecycle changes require a major version bump", + "path": null, + "severity": "INFO" + }, + { + "code": "SCHEMA_REMOVED", + "details": {}, + "message": "Schema removed from active contract", + "path": "schema[audit_log]", + "severity": "WARNING" + } + ], + "requiredVersionBump": "major", + "schemaVersion": "1", + "validation": { + "issues": [], + "valid": true + } +} diff --git a/tests/fixtures/governance_scenarios/validation_failure/base.yaml b/tests/fixtures/governance_scenarios/validation_failure/base.yaml new file mode 100644 index 0000000..9fb9d06 --- /dev/null +++ b/tests/fixtures/governance_scenarios/validation_failure/base.yaml @@ -0,0 +1,29 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + physicalName: tbl_orders + description: Orders table description + properties: + - id: id + name: id + physicalName: col_id + logicalType: string + physicalType: varchar(64) + required: true + description: Primary identifier + - id: amount + name: amount + physicalName: col_amount + logicalType: number + physicalType: decimal(10,2) + required: false + description: Order total amount diff --git a/tests/fixtures/governance_scenarios/validation_failure/candidate.yaml b/tests/fixtures/governance_scenarios/validation_failure/candidate.yaml new file mode 100644 index 0000000..d6119fe --- /dev/null +++ b/tests/fixtures/governance_scenarios/validation_failure/candidate.yaml @@ -0,0 +1,23 @@ +apiVersion: v3.1.0 +kind: DataContract +id: orders-contract +name: orders-contract +version: 1.0.0 +status: active +domain: commerce +description: + purpose: Authoritative orders contract +schema: + - id: orders + name: orders + properties: + - id: id + name: '' + logicalType: string + physicalType: varchar(64) + required: true + - id: amount + name: amount + logicalType: number + physicalType: decimal(10,2) + required: false diff --git a/tests/fixtures/governance_scenarios/validation_failure/expected.json b/tests/fixtures/governance_scenarios/validation_failure/expected.json new file mode 100644 index 0000000..01eb8f5 --- /dev/null +++ b/tests/fixtures/governance_scenarios/validation_failure/expected.json @@ -0,0 +1,55 @@ +{ + "breaking": false, + "changes": [], + "context": { + "effectiveDate": "2026-01-01" + }, + "contractId": "orders-contract", + "decision": "BLOCK", + "decisionId": "d887c18a-5d1f-57de-b374-7bb1fe5a4ae8", + "evidence": { + "hasChanges": true, + "mergeConflictsCount": 0 + }, + "policy": { + "idViolation": false, + "retiredViolation": false, + "valid": false, + "versionViolation": false, + "violations": [] + }, + "reasonCodes": [ + "CHANGE_ASSESSMENT", + "VALIDATION_FAILED" + ], + "reasons": [ + { + "code": "CHANGE_ASSESSMENT", + "details": {}, + "message": "No contract changes detected", + "path": null, + "severity": "INFO" + }, + { + "code": "VALIDATION_FAILED", + "details": {}, + "message": "Property name cannot be empty or whitespace-only", + "path": "schema", + "severity": "ERROR" + } + ], + "requiredVersionBump": "none", + "schemaVersion": "1", + "validation": { + "issues": [ + { + "code": "VALIDATION_FAILED", + "details": {}, + "message": "Property name cannot be empty or whitespace-only", + "path": "schema", + "severity": "ERROR" + } + ], + "valid": false + } +} diff --git a/tests/test_governance_golden_scenarios.py b/tests/test_governance_golden_scenarios.py new file mode 100644 index 0000000..9ced013 --- /dev/null +++ b/tests/test_governance_golden_scenarios.py @@ -0,0 +1,680 @@ +"""Deterministic golden regression test suite for SemaPact governance decisions. + +Freezes authoritative M0 governance semantics across lifecycle, breaking-change +classification, version bump recommendation, reason codes, canonical identity, +deterministic decision IDs, and public GovernanceDecision (v1) JSON serialization. +""" + +from __future__ import annotations + +import hashlib +from dataclasses import dataclass +from datetime import date +from pathlib import Path +import pytest +import yaml + +from open_data_contract_standard.model import OpenDataContractStandard +from semapact.change_context import ChangeContext +from semapact.core.release import RequiredBump +from semapact.governance import ( + DecisionResult, + evaluate_governance_decision, + serialize_public_governance_decision, + to_public_governance_decision, +) +from semapact.governance_codes import GovernanceReasonCode +from semapact.lifecycle.merge_engine import MergeConflict + +FIXTURES_DIR = Path(__file__).parent / "fixtures" / "governance_scenarios" +TEST_CONTEXT = ChangeContext(effective_date=date(2026, 1, 1)) + + +@dataclass(frozen=True) +class GovernanceGoldenScenario: + """Descriptor for a single golden regression scenario.""" + + name: str + expected_decision: DecisionResult + expected_bump: RequiredBump + expected_breaking: bool + expected_reason_codes: frozenset[GovernanceReasonCode] + merge_conflicts: tuple[MergeConflict, ...] = () + expected_policy_valid: bool | None = None + expected_validation_valid: bool | None = None + + +# Authoritative semantic expectation matrix for all 23 golden scenarios +SCENARIO_MATRIX: tuple[GovernanceGoldenScenario, ...] = ( + # 1. No change + GovernanceGoldenScenario( + name="no_change", + expected_decision=DecisionResult.ALLOW, + expected_bump="none", + expected_breaking=False, + expected_reason_codes=frozenset({GovernanceReasonCode.CHANGE_ASSESSMENT}), + expected_policy_valid=True, + expected_validation_valid=True, + ), + # 2. Descriptive metadata only + GovernanceGoldenScenario( + name="descriptive_metadata_only", + expected_decision=DecisionResult.ALLOW, + expected_bump="none", + expected_breaking=False, + expected_reason_codes=frozenset({GovernanceReasonCode.CHANGE_ASSESSMENT}), + expected_policy_valid=True, + expected_validation_valid=True, + ), + # 3. Property addition + GovernanceGoldenScenario( + name="property_addition", + expected_decision=DecisionResult.REVIEW, + expected_bump="minor", + expected_breaking=False, + expected_reason_codes=frozenset({GovernanceReasonCode.CHANGE_ASSESSMENT}), + expected_policy_valid=True, + expected_validation_valid=True, + ), + # 4. Schema addition + GovernanceGoldenScenario( + name="schema_addition", + expected_decision=DecisionResult.REVIEW, + expected_bump="minor", + expected_breaking=False, + expected_reason_codes=frozenset({GovernanceReasonCode.CHANGE_ASSESSMENT}), + expected_policy_valid=True, + expected_validation_valid=True, + ), + # 5. Property removal + GovernanceGoldenScenario( + name="property_removal", + expected_decision=DecisionResult.REVIEW, + expected_bump="major", + expected_breaking=True, + expected_reason_codes=frozenset({ + GovernanceReasonCode.CHANGE_ASSESSMENT, + GovernanceReasonCode.PROPERTY_REMOVED, + }), + expected_policy_valid=False, + expected_validation_valid=True, + ), + # 6. Schema removal + GovernanceGoldenScenario( + name="schema_removal", + expected_decision=DecisionResult.REVIEW, + expected_bump="major", + expected_breaking=True, + expected_reason_codes=frozenset({ + GovernanceReasonCode.CHANGE_ASSESSMENT, + GovernanceReasonCode.SCHEMA_REMOVED, + }), + expected_policy_valid=False, + expected_validation_valid=True, + ), + # 7. Logical type change + GovernanceGoldenScenario( + name="logical_type_change", + expected_decision=DecisionResult.REVIEW, + expected_bump="major", + expected_breaking=True, + expected_reason_codes=frozenset({ + GovernanceReasonCode.CHANGE_ASSESSMENT, + GovernanceReasonCode.LOGICAL_TYPE_CHANGED, + }), + expected_policy_valid=False, + expected_validation_valid=True, + ), + # 8. Physical type narrowing + GovernanceGoldenScenario( + name="physical_type_narrowing", + expected_decision=DecisionResult.REVIEW, + expected_bump="major", + expected_breaking=True, + expected_reason_codes=frozenset({ + GovernanceReasonCode.CHANGE_ASSESSMENT, + GovernanceReasonCode.PHYSICAL_TYPE_NARROWED, + }), + expected_policy_valid=False, + expected_validation_valid=True, + ), + # 9. Decimal precision reduction + GovernanceGoldenScenario( + name="decimal_precision_reduction", + expected_decision=DecisionResult.REVIEW, + expected_bump="major", + expected_breaking=True, + expected_reason_codes=frozenset({ + GovernanceReasonCode.CHANGE_ASSESSMENT, + GovernanceReasonCode.DECIMAL_PRECISION_REDUCED, + }), + expected_policy_valid=False, + expected_validation_valid=True, + ), + # 10. Decimal scale reduction + GovernanceGoldenScenario( + name="decimal_scale_reduction", + expected_decision=DecisionResult.REVIEW, + expected_bump="major", + expected_breaking=True, + expected_reason_codes=frozenset({ + GovernanceReasonCode.CHANGE_ASSESSMENT, + GovernanceReasonCode.DECIMAL_SCALE_REDUCED, + }), + expected_policy_valid=False, + expected_validation_valid=True, + ), + # 11. Decimal widening + GovernanceGoldenScenario( + name="decimal_widening", + expected_decision=DecisionResult.REVIEW, + expected_bump="minor", + expected_breaking=False, + expected_reason_codes=frozenset({GovernanceReasonCode.CHANGE_ASSESSMENT}), + expected_policy_valid=True, + expected_validation_valid=True, + ), + # 12. Required tightening + GovernanceGoldenScenario( + name="required_tightening", + expected_decision=DecisionResult.REVIEW, + expected_bump="major", + expected_breaking=True, + expected_reason_codes=frozenset({ + GovernanceReasonCode.CHANGE_ASSESSMENT, + GovernanceReasonCode.REQUIRED_TIGHTENED, + }), + expected_policy_valid=False, + expected_validation_valid=True, + ), + # 13. Enum reduction (authoritative ODCS model ignores extra enum in YAML) + GovernanceGoldenScenario( + name="enum_reduction", + expected_decision=DecisionResult.ALLOW, + expected_bump="none", + expected_breaking=False, + expected_reason_codes=frozenset({GovernanceReasonCode.CHANGE_ASSESSMENT}), + expected_policy_valid=True, + expected_validation_valid=True, + ), + # 14. Relationship removal + GovernanceGoldenScenario( + name="relationship_removal", + expected_decision=DecisionResult.REVIEW, + expected_bump="major", + expected_breaking=True, + expected_reason_codes=frozenset({ + GovernanceReasonCode.CHANGE_ASSESSMENT, + GovernanceReasonCode.RELATIONSHIP_REMOVED, + }), + expected_policy_valid=False, + expected_validation_valid=True, + ), + # 15. Draft entity change + GovernanceGoldenScenario( + name="draft_entity_change", + expected_decision=DecisionResult.REVIEW, + expected_bump="minor", + expected_breaking=False, + expected_reason_codes=frozenset({GovernanceReasonCode.CHANGE_ASSESSMENT}), + expected_policy_valid=True, + expected_validation_valid=True, + ), + # 16. Deprecated entity change + GovernanceGoldenScenario( + name="deprecated_entity_change", + expected_decision=DecisionResult.REVIEW, + expected_bump="minor", + expected_breaking=False, + expected_reason_codes=frozenset({GovernanceReasonCode.CHANGE_ASSESSMENT}), + expected_policy_valid=True, + expected_validation_valid=True, + ), + # 17. Active to retired transition + GovernanceGoldenScenario( + name="active_to_retired_transition", + expected_decision=DecisionResult.REVIEW, + expected_bump="none", + expected_breaking=False, + expected_reason_codes=frozenset({ + GovernanceReasonCode.CHANGE_ASSESSMENT, + GovernanceReasonCode.CONTRACT_RETIRED_TRANSITION, + }), + expected_policy_valid=True, + expected_validation_valid=True, + ), + # 18. Retired contract mutation + GovernanceGoldenScenario( + name="retired_contract_mutation", + expected_decision=DecisionResult.BLOCK, + expected_bump="none", + expected_breaking=False, + expected_reason_codes=frozenset({ + GovernanceReasonCode.CHANGE_ASSESSMENT, + GovernanceReasonCode.RETIRED_CONTRACT_MODIFIED, + }), + expected_policy_valid=False, + expected_validation_valid=True, + ), + # 19. Contract ID change + GovernanceGoldenScenario( + name="contract_id_change", + expected_decision=DecisionResult.BLOCK, + expected_bump="major", + expected_breaking=True, + expected_reason_codes=frozenset({ + GovernanceReasonCode.CHANGE_ASSESSMENT, + GovernanceReasonCode.CONTRACT_ID_CHANGED, + }), + expected_policy_valid=False, + expected_validation_valid=True, + ), + # 20. Manual version change + GovernanceGoldenScenario( + name="manual_version_change", + expected_decision=DecisionResult.BLOCK, + expected_bump="major", + expected_breaking=True, + expected_reason_codes=frozenset({ + GovernanceReasonCode.CHANGE_ASSESSMENT, + GovernanceReasonCode.CONTRACT_VERSION_MANUALLY_CHANGED, + }), + expected_policy_valid=False, + expected_validation_valid=True, + ), + # 21. Physical name identity stability + GovernanceGoldenScenario( + name="physical_name_identity_stability", + expected_decision=DecisionResult.REVIEW, + expected_bump="minor", + expected_breaking=False, + expected_reason_codes=frozenset({GovernanceReasonCode.CHANGE_ASSESSMENT}), + expected_policy_valid=True, + expected_validation_valid=True, + ), + # 22. Validation failure + GovernanceGoldenScenario( + name="validation_failure", + expected_decision=DecisionResult.BLOCK, + expected_bump="none", + expected_breaking=False, + expected_reason_codes=frozenset({ + GovernanceReasonCode.CHANGE_ASSESSMENT, + GovernanceReasonCode.VALIDATION_FAILED, + }), + expected_policy_valid=False, + expected_validation_valid=False, + ), + # 23. Merge conflict + GovernanceGoldenScenario( + name="merge_conflict", + expected_decision=DecisionResult.REVIEW, + expected_bump="none", + expected_breaking=False, + expected_reason_codes=frozenset({ + GovernanceReasonCode.CHANGE_ASSESSMENT, + GovernanceReasonCode.MERGE_CONFLICT, + }), + merge_conflicts=( + MergeConflict( + path="schema[orders].properties[amount]", + message="Conflicting description updates from upstream branches", + rule="description_conflict", + schema_id="orders", + property_name="amount", + ), + ), + expected_policy_valid=True, + expected_validation_valid=True, + ), +) + + +def _load_contract_from_yaml(yaml_path: Path) -> OpenDataContractStandard: + """Load an OpenDataContractStandard instance from a YAML fixture path.""" + content = yaml_path.read_text(encoding="utf-8") + payload = yaml.safe_load(content) + return OpenDataContractStandard.model_validate(payload) + + +# ============================================================================== +# Phase 4 & 5: Parameterized Golden Scenario Runner & Domain Assertions +# ============================================================================== + + +@pytest.mark.parametrize("scenario", SCENARIO_MATRIX, ids=lambda s: s.name) +def test_governance_golden_scenarios(scenario: GovernanceGoldenScenario) -> None: + """Evaluate golden scenario, verify domain invariants, and compare byte-exact public JSON.""" + scenario_dir = FIXTURES_DIR / scenario.name + assert scenario_dir.exists(), f"Scenario directory missing: {scenario_dir}" + + base_path = scenario_dir / "base.yaml" + cand_path = scenario_dir / "candidate.yaml" + expected_path = scenario_dir / "expected.json" + + assert base_path.exists(), f"base.yaml missing for {scenario.name}" + assert cand_path.exists(), f"candidate.yaml missing for {scenario.name}" + assert expected_path.exists(), f"expected.json missing for {scenario.name}" + + base_contract = _load_contract_from_yaml(base_path) + cand_contract = _load_contract_from_yaml(cand_path) + + # 1. Evaluate internal governance decision + decision = evaluate_governance_decision( + base_contract, + cand_contract, + context=TEST_CONTEXT, + merge_conflicts=scenario.merge_conflicts, + ) + + # 2. Direct domain-level assertions (Phase 5) + assert decision.decision == scenario.expected_decision, ( + f"[{scenario.name}] decision mismatch: got {decision.decision}, expected {scenario.expected_decision}" + ) + assert decision.breaking == scenario.expected_breaking, ( + f"[{scenario.name}] breaking flag mismatch: got {decision.breaking}, expected {scenario.expected_breaking}" + ) + assert decision.required_version_bump == scenario.expected_bump, ( + f"[{scenario.name}] required_version_bump mismatch: got {decision.required_version_bump}, expected {scenario.expected_bump}" + ) + + actual_reason_codes = frozenset(r.code for r in decision.reasons) + assert scenario.expected_reason_codes.issubset(actual_reason_codes), ( + f"[{scenario.name}] missing expected reason codes: " + f"expected {scenario.expected_reason_codes}, got {actual_reason_codes}" + ) + + if scenario.expected_policy_valid is not None: + assert decision.policy.valid == scenario.expected_policy_valid, ( + f"[{scenario.name}] policy.valid mismatch: got {decision.policy.valid}, expected {scenario.expected_policy_valid}" + ) + + if scenario.expected_validation_valid is not None: + assert decision.validation.valid == scenario.expected_validation_valid, ( + f"[{scenario.name}] validation.valid mismatch: got {decision.validation.valid}, expected {scenario.expected_validation_valid}" + ) + + # 3. Public projection and read-only byte-exact golden comparison (Phase 3 & 4) + public_decision = to_public_governance_decision(decision) + actual_json = serialize_public_governance_decision(public_decision, indent=2) + "\n" + expected_json = expected_path.read_text(encoding="utf-8") + + assert actual_json == expected_json, ( + f"[{scenario.name}] Golden JSON mismatch:\n" + f"--- Actual ---\n{actual_json}\n" + f"--- Expected ---\n{expected_json}" + ) + + +# ============================================================================== +# Phase 6: Lifecycle Regression Coverage +# ============================================================================== + + +def test_lifecycle_draft_entity_skips_breaking_checks() -> None: + """Modifying/tightening a property marked as draft in active contract skips breaking checks.""" + scenario_dir = FIXTURES_DIR / "draft_entity_change" + base = _load_contract_from_yaml(scenario_dir / "base.yaml") + cand = _load_contract_from_yaml(scenario_dir / "candidate.yaml") + + decision = evaluate_governance_decision(base, cand, context=TEST_CONTEXT) + + assert decision.decision == DecisionResult.REVIEW + assert decision.breaking is False + assert decision.required_version_bump == "minor" + assert len(decision.policy.breaking_changes) == 0 + + +def test_lifecycle_deprecated_entity_skips_breaking_checks() -> None: + """Modifying/tightening a property marked as deprecated in active contract skips breaking checks.""" + scenario_dir = FIXTURES_DIR / "deprecated_entity_change" + base = _load_contract_from_yaml(scenario_dir / "base.yaml") + cand = _load_contract_from_yaml(scenario_dir / "candidate.yaml") + + decision = evaluate_governance_decision(base, cand, context=TEST_CONTEXT) + + assert decision.decision == DecisionResult.REVIEW + assert decision.breaking is False + assert decision.required_version_bump == "minor" + assert len(decision.policy.breaking_changes) == 0 + + +def test_lifecycle_active_to_retired_transition_reviewable() -> None: + """Active contract transitioning to retired is reviewable (not blocked).""" + scenario_dir = FIXTURES_DIR / "active_to_retired_transition" + base = _load_contract_from_yaml(scenario_dir / "base.yaml") + cand = _load_contract_from_yaml(scenario_dir / "candidate.yaml") + + decision = evaluate_governance_decision(base, cand, context=TEST_CONTEXT) + + assert decision.decision == DecisionResult.REVIEW + assert decision.policy.retired_violation is False + assert any( + r.code == GovernanceReasonCode.CONTRACT_RETIRED_TRANSITION + for r in decision.reasons + ) + + +def test_lifecycle_retired_contract_mutation_blocked() -> None: + """Mutating an already retired contract is strictly blocked with RETIRED_CONTRACT_MODIFIED.""" + scenario_dir = FIXTURES_DIR / "retired_contract_mutation" + base = _load_contract_from_yaml(scenario_dir / "base.yaml") + cand = _load_contract_from_yaml(scenario_dir / "candidate.yaml") + + decision = evaluate_governance_decision(base, cand, context=TEST_CONTEXT) + + assert decision.decision == DecisionResult.BLOCK + assert decision.policy.retired_violation is True + assert any( + r.code == GovernanceReasonCode.RETIRED_CONTRACT_MODIFIED + for r in decision.reasons + ) + + +# ============================================================================== +# Phase 7: Canonical Identity Stability +# ============================================================================== + + +def test_physical_name_identity_stability() -> None: + """Changing physicalName does not alter canonical GovernanceChange.identity.""" + scenario_dir = FIXTURES_DIR / "physical_name_identity_stability" + base = _load_contract_from_yaml(scenario_dir / "base.yaml") + cand = _load_contract_from_yaml(scenario_dir / "candidate.yaml") + + decision = evaluate_governance_decision(base, cand, context=TEST_CONTEXT) + + assert decision.decision == DecisionResult.REVIEW + assert decision.breaking is False + + schema_changes = [c for c in decision.changes if c.entity_type.value == "SCHEMA"] + property_changes = [c for c in decision.changes if c.entity_type.value == "PROPERTY"] + + # Canonical identity must remain lowercase schema and property names + assert all(c.identity == ("orders",) for c in schema_changes) + assert all(c.identity == ("orders", "id") for c in property_changes) + + +# ============================================================================== +# Phase 8: Reason-Code Coverage Invariant +# ============================================================================== + + +def test_all_producible_p0_reason_codes_covered() -> None: + """Verify that all producible P0 reason codes have regression scenario coverage.""" + covered_codes: set[GovernanceReasonCode] = set() + for scenario in SCENARIO_MATRIX: + covered_codes.update(scenario.expected_reason_codes) + + # Registered reason codes in semapact + all_codes = set(GovernanceReasonCode) + + # ENUM_VALUES_REMOVED cannot be produced from ODCS YAML because upstream + # open_data_contract_standard Pydantic model drops 'enum' on SchemaProperty. + deferred_codes = {GovernanceReasonCode.ENUM_VALUES_REMOVED} + + producible_codes = all_codes - deferred_codes + missing_codes = producible_codes - covered_codes + + assert not missing_codes, f"Missing regression coverage for P0 reason codes: {missing_codes}" + + +# ============================================================================== +# Phase 9: Determinism Tests +# ============================================================================== + + +def test_repeated_evaluation_determinism() -> None: + """Repeated evaluation on identical inputs produces identical decision_id and byte-exact JSON.""" + scenario_dir = FIXTURES_DIR / "decimal_precision_reduction" + base = _load_contract_from_yaml(scenario_dir / "base.yaml") + cand = _load_contract_from_yaml(scenario_dir / "candidate.yaml") + + first_dec = evaluate_governance_decision(base, cand, context=TEST_CONTEXT) + first_json = serialize_public_governance_decision( + to_public_governance_decision(first_dec), indent=2 + ) + + for _ in range(10): + subsequent_dec = evaluate_governance_decision(base, cand, context=TEST_CONTEXT) + subsequent_json = serialize_public_governance_decision( + to_public_governance_decision(subsequent_dec), indent=2 + ) + + assert subsequent_dec.decision_id == first_dec.decision_id + assert subsequent_dec.decision == first_dec.decision + assert subsequent_json == first_json + assert subsequent_json.encode("utf-8") == first_json.encode("utf-8") + + +def test_non_semantic_key_ordering_determinism() -> None: + """Differing key insertion orders in contract dictionaries yield identical fingerprints and decision_ids.""" + base_dict_1 = { + "apiVersion": "v3.1.0", + "kind": "DataContract", + "id": "orders-contract", + "name": "orders-contract", + "version": "1.0.0", + "status": "active", + "schema": [ + { + "id": "orders", + "name": "orders", + "properties": [ + { + "id": "id", + "name": "id", + "logicalType": "string", + "physicalType": "varchar(64)", + "required": True, + } + ], + } + ], + } + + # Same content, different key insertion order + base_dict_2 = { + "status": "active", + "version": "1.0.0", + "name": "orders-contract", + "id": "orders-contract", + "kind": "DataContract", + "apiVersion": "v3.1.0", + "schema": [ + { + "properties": [ + { + "required": True, + "physicalType": "varchar(64)", + "logicalType": "string", + "name": "id", + "id": "id", + } + ], + "name": "orders", + "id": "orders", + } + ], + } + + base_1 = OpenDataContractStandard.model_validate(base_dict_1) + base_2 = OpenDataContractStandard.model_validate(base_dict_2) + + dec_1 = evaluate_governance_decision(base_1, base_1, context=TEST_CONTEXT) + dec_2 = evaluate_governance_decision(base_2, base_2, context=TEST_CONTEXT) + + assert dec_1.decision_id == dec_2.decision_id + assert to_public_governance_decision(dec_1).to_canonical_json( + indent=2 + ) == to_public_governance_decision(dec_2).to_canonical_json(indent=2) + + +def test_merge_conflict_ordering_determinism() -> None: + """Merge conflicts provided in different input orders are canonically sorted and produce identical decision_id.""" + base = _load_contract_from_yaml(FIXTURES_DIR / "no_change" / "base.yaml") + + c1 = MergeConflict( + path="schema[orders].properties[id]", + message="Conflict A", + rule="rule_a", + schema_id="orders", + property_name="id", + ) + c2 = MergeConflict( + path="schema[orders].properties[amount]", + message="Conflict B", + rule="rule_b", + schema_id="orders", + property_name="amount", + ) + + dec_forward = evaluate_governance_decision( + base, base, context=TEST_CONTEXT, merge_conflicts=(c1, c2) + ) + dec_reverse = evaluate_governance_decision( + base, base, context=TEST_CONTEXT, merge_conflicts=(c2, c1) + ) + + assert dec_forward.decision_id == dec_reverse.decision_id + assert dec_forward.reasons == dec_reverse.reasons + assert to_public_governance_decision(dec_forward).to_canonical_json( + indent=2 + ) == to_public_governance_decision(dec_reverse).to_canonical_json(indent=2) + + +# ============================================================================== +# Read-Only Verification +# ============================================================================== + + +def test_fixtures_are_read_only(monkeypatch: pytest.MonkeyPatch) -> None: + """Confirm fixtures directory contents are strictly unchanged after running test suite.""" + hashes_before: dict[str, str] = {} + for f in FIXTURES_DIR.rglob("*"): + if f.is_file(): + hashes_before[str(f.relative_to(FIXTURES_DIR))] = hashlib.sha256( + f.read_bytes() + ).hexdigest() + + # Re-run all scenarios in memory + for scenario in SCENARIO_MATRIX: + s_dir = FIXTURES_DIR / scenario.name + b = _load_contract_from_yaml(s_dir / "base.yaml") + c = _load_contract_from_yaml(s_dir / "candidate.yaml") + d = evaluate_governance_decision( + b, c, context=TEST_CONTEXT, merge_conflicts=scenario.merge_conflicts + ) + pub = to_public_governance_decision(d) + actual = serialize_public_governance_decision(pub, indent=2) + "\n" + expected = (s_dir / "expected.json").read_text(encoding="utf-8") + assert actual == expected + + hashes_after: dict[str, str] = {} + for f in FIXTURES_DIR.rglob("*"): + if f.is_file(): + hashes_after[str(f.relative_to(FIXTURES_DIR))] = hashlib.sha256( + f.read_bytes() + ).hexdigest() + + assert hashes_before == hashes_after From c866c0b8bfb81a930562833183442ab9597e669f Mon Sep 17 00:00:00 2001 From: ElliotSun Date: Sat, 29 Aug 2026 15:12:15 +1000 Subject: [PATCH 2/2] test(governance): harden identity regression checks and exact reason code matching - Lock existence and exact identity sets in physical name stability test - Enforce exact reason codes equality in domain-level scenario assertions - Remove unused monkeypatch parameter from test_fixtures_are_read_only --- tests/test_governance_golden_scenarios.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/test_governance_golden_scenarios.py b/tests/test_governance_golden_scenarios.py index 9ced013..56c22e0 100644 --- a/tests/test_governance_golden_scenarios.py +++ b/tests/test_governance_golden_scenarios.py @@ -379,8 +379,8 @@ def test_governance_golden_scenarios(scenario: GovernanceGoldenScenario) -> None ) actual_reason_codes = frozenset(r.code for r in decision.reasons) - assert scenario.expected_reason_codes.issubset(actual_reason_codes), ( - f"[{scenario.name}] missing expected reason codes: " + assert actual_reason_codes == scenario.expected_reason_codes, ( + f"[{scenario.name}] reason codes mismatch: " f"expected {scenario.expected_reason_codes}, got {actual_reason_codes}" ) @@ -491,8 +491,10 @@ def test_physical_name_identity_stability() -> None: property_changes = [c for c in decision.changes if c.entity_type.value == "PROPERTY"] # Canonical identity must remain lowercase schema and property names - assert all(c.identity == ("orders",) for c in schema_changes) - assert all(c.identity == ("orders", "id") for c in property_changes) + assert schema_changes + assert property_changes + assert {c.identity for c in schema_changes} == {("orders",)} + assert {c.identity for c in property_changes} == {("orders", "id")} # ============================================================================== @@ -648,7 +650,7 @@ def test_merge_conflict_ordering_determinism() -> None: # ============================================================================== -def test_fixtures_are_read_only(monkeypatch: pytest.MonkeyPatch) -> None: +def test_fixtures_are_read_only() -> None: """Confirm fixtures directory contents are strictly unchanged after running test suite.""" hashes_before: dict[str, str] = {} for f in FIXTURES_DIR.rglob("*"):