Skip to content

Commit eda1a45

Browse files
authored
Implement tests for ExportPackageValidator
Add tests for ExportPackageValidator to ensure correct validation of export packages, including handling of artifacts, evidence bundles, and provenance manifests.
1 parent 68a1ed5 commit eda1a45

1 file changed

Lines changed: 279 additions & 0 deletions

File tree

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from ix_autonomy_assurance_case_runtime.contracts import ContractValueError, EvidenceStatus
6+
from ix_autonomy_assurance_case_runtime.evidence import EvidenceBundle, EvidenceRecord
7+
from ix_autonomy_assurance_case_runtime.export_package import (
8+
ExportArtifactKind,
9+
ExportArtifactReference,
10+
ExportPackageAudience,
11+
ExportPackageFormat,
12+
ExportPackageManifest,
13+
ExportPackageStatus,
14+
ExportRedactionRule,
15+
)
16+
from ix_autonomy_assurance_case_runtime.export_package_validation import (
17+
ExportPackageValidationFinding,
18+
ExportPackageValidationFindingSeverity,
19+
ExportPackageValidationFindingSource,
20+
ExportPackageValidator,
21+
)
22+
23+
24+
def _artifact(
25+
*,
26+
artifact_id: str = "artifact-run-ledger-001",
27+
kind: ExportArtifactKind = ExportArtifactKind.RUN_LEDGER,
28+
evidence_bundle_ids: tuple[str, ...] = ("ev-run-ledger-001",),
29+
provenance_manifest_ids: tuple[str, ...] = ("manifest-run-ledger-001",),
30+
contains_sensitive_fields: bool = False,
31+
) -> ExportArtifactReference:
32+
return ExportArtifactReference(
33+
artifact_id=artifact_id,
34+
kind=kind,
35+
title="Run ledger artifact",
36+
source_record_id="run-ledger-001",
37+
evidence_bundle_ids=evidence_bundle_ids,
38+
provenance_manifest_ids=provenance_manifest_ids,
39+
tags=("runtime", "ledger"),
40+
contains_sensitive_fields=contains_sensitive_fields,
41+
)
42+
43+
44+
def _redaction_rule(
45+
*,
46+
rule_id: str = "redact-operator-ids",
47+
target_artifact_kinds: tuple[ExportArtifactKind, ...] = (ExportArtifactKind.RUN_LEDGER,),
48+
) -> ExportRedactionRule:
49+
return ExportRedactionRule(
50+
rule_id=rule_id,
51+
target_artifact_kinds=target_artifact_kinds,
52+
field_path="records[*].operator_id",
53+
rationale="Operator identifiers are not required for open technical review.",
54+
evidence_bundle_ids=("ev-redaction-001",),
55+
)
56+
57+
58+
def _manifest(
59+
*,
60+
status: ExportPackageStatus = ExportPackageStatus.READY_TO_EXPORT,
61+
package_format: ExportPackageFormat = ExportPackageFormat.JSON,
62+
audience: ExportPackageAudience = ExportPackageAudience.FEDERAL_EVALUATION,
63+
artifacts: tuple[ExportArtifactReference, ...] | None = None,
64+
redaction_rules: tuple[ExportRedactionRule, ...] | None = None,
65+
provenance_manifest_ids: tuple[str, ...] = ("manifest-export-001",),
66+
disclaimer: str | None = None,
67+
) -> ExportPackageManifest:
68+
return ExportPackageManifest(
69+
package_id="export-case-runtime-001",
70+
case_id="case-runtime-001",
71+
title="Runtime assurance export package",
72+
status=status,
73+
package_format=package_format,
74+
audience=audience,
75+
created_at_utc="2026-05-12T14:00:00Z",
76+
artifacts=artifacts if artifacts is not None else (_artifact(),),
77+
evidence_bundle_ids=("ev-export-manifest-001",),
78+
redaction_rules=redaction_rules if redaction_rules is not None else (_redaction_rule(),),
79+
provenance_manifest_ids=provenance_manifest_ids,
80+
notes=("Local prototype export package.",),
81+
disclaimer=disclaimer
82+
or (
83+
"Local prototype export package only; not an official certification, "
84+
"authority-to-operate decision, deployment approval, or agency acceptance package."
85+
),
86+
)
87+
88+
89+
def _bundle(bundle_id: str, *, hashed: bool = True) -> EvidenceBundle:
90+
bundle = EvidenceBundle(
91+
bundle_id=bundle_id,
92+
case_id="case-runtime-001",
93+
records=(
94+
EvidenceRecord(
95+
evidence_id=f"record-{bundle_id}",
96+
kind="export-package",
97+
source="unit-test",
98+
payload={"bundle_id": bundle_id},
99+
status=EvidenceStatus.ACCEPTED,
100+
),
101+
),
102+
)
103+
if hashed:
104+
return bundle.with_computed_hashes()
105+
return bundle
106+
107+
108+
def _bundles(*, unhashed: str | None = None) -> tuple[EvidenceBundle, ...]:
109+
bundle_ids = (
110+
"ev-export-manifest-001",
111+
"ev-run-ledger-001",
112+
"ev-redaction-001",
113+
)
114+
return tuple(_bundle(bundle_id, hashed=bundle_id != unhashed) for bundle_id in bundle_ids)
115+
116+
117+
def _validator(
118+
*,
119+
evidence_bundles: tuple[EvidenceBundle, ...] | None = None,
120+
provenance_manifest_ids: tuple[str, ...] = (
121+
"manifest-export-001",
122+
"manifest-run-ledger-001",
123+
),
124+
) -> ExportPackageValidator:
125+
return ExportPackageValidator(
126+
evidence_bundles=_bundles() if evidence_bundles is None else evidence_bundles,
127+
provenance_manifest_ids=provenance_manifest_ids,
128+
)
129+
130+
131+
def test_export_package_validator_accepts_grounded_export_manifest() -> None:
132+
report = _validator().validate(_manifest())
133+
134+
assert report.is_export_ready()
135+
assert report.blocker_count == 0
136+
assert report.warning_count == 5
137+
assert report.summary() == (
138+
"export-package-validation: export-case-runtime-001 "
139+
"(1 artifact(s), 1 redaction rule(s), 3 evidence bundle(s), "
140+
"2 provenance manifest(s), 0 blocker(s), 5 warning(s))"
141+
)
142+
143+
144+
def test_export_package_validator_blocks_non_exportable_status() -> None:
145+
report = _validator().validate(
146+
_manifest(
147+
status=ExportPackageStatus.READY_FOR_REVIEW,
148+
audience=ExportPackageAudience.LOCAL_REVIEW,
149+
redaction_rules=(),
150+
)
151+
)
152+
153+
assert not report.is_export_ready()
154+
assert any(
155+
finding.finding_id == "package-export-case-runtime-001-status-not-exportable"
156+
for finding in report.findings
157+
)
158+
159+
160+
def test_export_package_validator_blocks_sensitive_artifact_without_matching_redaction() -> None:
161+
sensitive_artifact = _artifact(contains_sensitive_fields=True)
162+
report = _validator().validate(
163+
_manifest(
164+
artifacts=(sensitive_artifact,),
165+
redaction_rules=(
166+
_redaction_rule(
167+
rule_id="redact-policy-fields",
168+
target_artifact_kinds=(ExportArtifactKind.POLICY_PACK,),
169+
),
170+
),
171+
)
172+
)
173+
174+
assert not report.is_export_ready()
175+
assert report.findings_for_artifact("artifact-run-ledger-001")[0].source is (
176+
ExportPackageValidationFindingSource.REDACTION
177+
)
178+
179+
180+
def test_export_package_validator_blocks_runtime_artifact_without_provenance() -> None:
181+
report = _validator().validate(
182+
_manifest(
183+
artifacts=(
184+
_artifact(
185+
provenance_manifest_ids=(),
186+
),
187+
)
188+
)
189+
)
190+
191+
assert not report.is_export_ready()
192+
assert any(
193+
finding.finding_id == "artifact-artifact-run-ledger-001-runtime-no-provenance"
194+
for finding in report.findings_for_artifact("artifact-run-ledger-001")
195+
)
196+
197+
198+
def test_export_package_validator_blocks_missing_evidence_bundle() -> None:
199+
report = _validator(evidence_bundles=()).validate(_manifest())
200+
201+
assert not report.is_export_ready()
202+
assert report.findings_for_evidence_bundle("ev-export-manifest-001")[0].source is (
203+
ExportPackageValidationFindingSource.EVIDENCE
204+
)
205+
206+
207+
def test_export_package_validator_warns_for_unhashed_evidence_bundle() -> None:
208+
report = _validator(evidence_bundles=_bundles(unhashed="ev-run-ledger-001")).validate(
209+
_manifest()
210+
)
211+
212+
assert report.is_export_ready()
213+
assert report.warning_count == 7
214+
assert report.findings_for_evidence_bundle("ev-run-ledger-001")
215+
216+
217+
def test_export_package_validator_blocks_missing_provenance_manifest() -> None:
218+
report = _validator(provenance_manifest_ids=("manifest-export-001",)).validate(_manifest())
219+
220+
assert not report.is_export_ready()
221+
assert report.findings_for_provenance_manifest("manifest-run-ledger-001")[0].source is (
222+
ExportPackageValidationFindingSource.PROVENANCE
223+
)
224+
225+
226+
def test_export_package_validator_blocks_missing_manifest_level_provenance() -> None:
227+
report = _validator(provenance_manifest_ids=("manifest-run-ledger-001",)).validate(
228+
_manifest(provenance_manifest_ids=())
229+
)
230+
231+
assert not report.is_export_ready()
232+
assert any(
233+
finding.finding_id == "package-export-case-runtime-001-no-provenance"
234+
for finding in report.findings
235+
)
236+
237+
238+
def test_export_package_validator_blocks_weak_disclaimer() -> None:
239+
report = _validator().validate(
240+
_manifest(disclaimer="Export package for review.")
241+
)
242+
243+
assert not report.is_export_ready()
244+
assert any(
245+
finding.source is ExportPackageValidationFindingSource.DISCLAIMER
246+
for finding in report.findings
247+
)
248+
249+
250+
def test_export_package_validator_rejects_duplicate_inputs() -> None:
251+
bundle = _bundle("ev-export-manifest-001")
252+
253+
with pytest.raises(ContractValueError, match="Duplicate export package evidence"):
254+
ExportPackageValidator(evidence_bundles=(bundle, bundle))
255+
256+
with pytest.raises(ContractValueError, match="provenance_manifest_ids"):
257+
ExportPackageValidator(
258+
evidence_bundles=(),
259+
provenance_manifest_ids=("manifest-001", "manifest-001"),
260+
)
261+
262+
263+
def test_export_package_validation_finding_validates_optional_identifiers() -> None:
264+
with pytest.raises(ContractValueError, match="needs a message"):
265+
ExportPackageValidationFinding(
266+
finding_id="finding-export-validation-001",
267+
severity=ExportPackageValidationFindingSeverity.BLOCKER,
268+
source=ExportPackageValidationFindingSource.PACKAGE,
269+
message="",
270+
)
271+
272+
with pytest.raises(ContractValueError, match="artifact_id must not be blank"):
273+
ExportPackageValidationFinding(
274+
finding_id="finding-export-validation-001",
275+
severity=ExportPackageValidationFindingSeverity.BLOCKER,
276+
source=ExportPackageValidationFindingSource.ARTIFACT,
277+
message="Bad artifact.",
278+
artifact_id="",
279+
)

0 commit comments

Comments
 (0)