Skip to content

Commit 6946e88

Browse files
authored
Create test_export_package_readiness.py
1 parent 5203798 commit 6946e88

1 file changed

Lines changed: 345 additions & 0 deletions

File tree

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
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_readiness import (
17+
EXPORT_PACKAGE_CAPABILITY_ID,
18+
ExportPackageLayerReadinessEvaluator,
19+
ExportPackageReadinessDecision,
20+
ExportPackageReadinessFinding,
21+
ExportPackageReadinessFindingSeverity,
22+
ExportPackageReadinessFindingSource,
23+
)
24+
from ix_autonomy_assurance_case_runtime.prototype_readiness import (
25+
PrototypeClaimLevel,
26+
)
27+
28+
29+
def _artifact(
30+
*,
31+
artifact_id: str,
32+
kind: ExportArtifactKind,
33+
evidence_bundle_ids: tuple[str, ...],
34+
provenance_manifest_ids: tuple[str, ...],
35+
contains_sensitive_fields: bool = False,
36+
) -> ExportArtifactReference:
37+
return ExportArtifactReference(
38+
artifact_id=artifact_id,
39+
kind=kind,
40+
title=f"{kind.value} artifact",
41+
source_record_id=f"source-{artifact_id}",
42+
evidence_bundle_ids=evidence_bundle_ids,
43+
provenance_manifest_ids=provenance_manifest_ids,
44+
tags=("export", kind.value),
45+
contains_sensitive_fields=contains_sensitive_fields,
46+
)
47+
48+
49+
def _required_artifacts() -> tuple[ExportArtifactReference, ...]:
50+
return (
51+
_artifact(
52+
artifact_id="artifact-assurance-case-001",
53+
kind=ExportArtifactKind.ASSURANCE_CASE,
54+
evidence_bundle_ids=("ev-assurance-case-001",),
55+
provenance_manifest_ids=("manifest-assurance-case-001",),
56+
),
57+
_artifact(
58+
artifact_id="artifact-traceability-001",
59+
kind=ExportArtifactKind.TRACEABILITY_GRAPH,
60+
evidence_bundle_ids=("ev-traceability-001",),
61+
provenance_manifest_ids=("manifest-traceability-001",),
62+
),
63+
_artifact(
64+
artifact_id="artifact-evidence-bundle-001",
65+
kind=ExportArtifactKind.EVIDENCE_BUNDLE,
66+
evidence_bundle_ids=("ev-evidence-bundle-001",),
67+
provenance_manifest_ids=("manifest-evidence-bundle-001",),
68+
),
69+
_artifact(
70+
artifact_id="artifact-readiness-report-001",
71+
kind=ExportArtifactKind.READINESS_REPORT,
72+
evidence_bundle_ids=("ev-readiness-report-001",),
73+
provenance_manifest_ids=("manifest-readiness-report-001",),
74+
),
75+
_artifact(
76+
artifact_id="artifact-review-workflow-001",
77+
kind=ExportArtifactKind.REVIEW_WORKFLOW,
78+
evidence_bundle_ids=("ev-review-workflow-001",),
79+
provenance_manifest_ids=("manifest-review-workflow-001",),
80+
contains_sensitive_fields=True,
81+
),
82+
_artifact(
83+
artifact_id="artifact-run-ledger-001",
84+
kind=ExportArtifactKind.RUN_LEDGER,
85+
evidence_bundle_ids=("ev-run-ledger-001",),
86+
provenance_manifest_ids=("manifest-run-ledger-001",),
87+
),
88+
)
89+
90+
91+
def _minimal_runtime_artifact() -> ExportArtifactReference:
92+
return _artifact(
93+
artifact_id="artifact-run-ledger-001",
94+
kind=ExportArtifactKind.RUN_LEDGER,
95+
evidence_bundle_ids=("ev-run-ledger-001",),
96+
provenance_manifest_ids=("manifest-run-ledger-001",),
97+
)
98+
99+
100+
def _redaction_rule() -> ExportRedactionRule:
101+
return ExportRedactionRule(
102+
rule_id="redact-review-fields",
103+
target_artifact_kinds=(ExportArtifactKind.REVIEW_WORKFLOW,),
104+
field_path="review.actor.display_name",
105+
rationale="Reviewer display details are not required for external package review.",
106+
evidence_bundle_ids=("ev-redaction-001",),
107+
)
108+
109+
110+
def _manifest(
111+
*,
112+
status: ExportPackageStatus = ExportPackageStatus.READY_TO_EXPORT,
113+
package_format: ExportPackageFormat = ExportPackageFormat.JSON,
114+
audience: ExportPackageAudience = ExportPackageAudience.FEDERAL_EVALUATION,
115+
artifacts: tuple[ExportArtifactReference, ...] | None = None,
116+
redaction_rules: tuple[ExportRedactionRule, ...] | None = None,
117+
provenance_manifest_ids: tuple[str, ...] = ("manifest-export-001",),
118+
disclaimer: str | None = None,
119+
) -> ExportPackageManifest:
120+
return ExportPackageManifest(
121+
package_id="export-case-runtime-001",
122+
case_id="case-runtime-001",
123+
title="Runtime assurance export package",
124+
status=status,
125+
package_format=package_format,
126+
audience=audience,
127+
created_at_utc="2026-05-12T14:00:00Z",
128+
artifacts=artifacts if artifacts is not None else _required_artifacts(),
129+
evidence_bundle_ids=("ev-export-manifest-001",),
130+
redaction_rules=redaction_rules if redaction_rules is not None else (_redaction_rule(),),
131+
provenance_manifest_ids=provenance_manifest_ids,
132+
notes=("Local prototype export package.",),
133+
disclaimer=disclaimer
134+
or (
135+
"Local prototype export package only; not an official certification, "
136+
"authority-to-operate decision, deployment approval, or agency acceptance package."
137+
),
138+
)
139+
140+
141+
def _bundle(bundle_id: str, *, hashed: bool = True) -> EvidenceBundle:
142+
bundle = EvidenceBundle(
143+
bundle_id=bundle_id,
144+
case_id="case-runtime-001",
145+
records=(
146+
EvidenceRecord(
147+
evidence_id=f"record-{bundle_id}",
148+
kind="export-package",
149+
source="unit-test",
150+
payload={"bundle_id": bundle_id},
151+
status=EvidenceStatus.ACCEPTED,
152+
),
153+
),
154+
)
155+
if hashed:
156+
return bundle.with_computed_hashes()
157+
return bundle
158+
159+
160+
def _all_evidence_bundle_ids() -> tuple[str, ...]:
161+
return (
162+
"ev-export-manifest-001",
163+
"ev-assurance-case-001",
164+
"ev-traceability-001",
165+
"ev-evidence-bundle-001",
166+
"ev-readiness-report-001",
167+
"ev-review-workflow-001",
168+
"ev-run-ledger-001",
169+
"ev-redaction-001",
170+
)
171+
172+
173+
def _bundles(*, unhashed: str | None = None) -> tuple[EvidenceBundle, ...]:
174+
return tuple(
175+
_bundle(bundle_id, hashed=bundle_id != unhashed)
176+
for bundle_id in _all_evidence_bundle_ids()
177+
)
178+
179+
180+
def _all_provenance_manifest_ids() -> tuple[str, ...]:
181+
return (
182+
"manifest-export-001",
183+
"manifest-assurance-case-001",
184+
"manifest-traceability-001",
185+
"manifest-evidence-bundle-001",
186+
"manifest-readiness-report-001",
187+
"manifest-review-workflow-001",
188+
"manifest-run-ledger-001",
189+
)
190+
191+
192+
def _evaluator(
193+
*,
194+
evidence_bundles: tuple[EvidenceBundle, ...] | None = None,
195+
provenance_manifest_ids: tuple[str, ...] = _all_provenance_manifest_ids(),
196+
) -> ExportPackageLayerReadinessEvaluator:
197+
return ExportPackageLayerReadinessEvaluator(
198+
evidence_bundles=_bundles() if evidence_bundles is None else evidence_bundles,
199+
provenance_manifest_ids=provenance_manifest_ids,
200+
)
201+
202+
203+
def test_export_package_readiness_completes_clean_export_manifest() -> None:
204+
report = _evaluator().evaluate(_manifest())
205+
206+
assert report.decision is ExportPackageReadinessDecision.COMPLETE
207+
assert report.is_complete()
208+
assert report.completed_capability_ids() == (EXPORT_PACKAGE_CAPABILITY_ID,)
209+
assert report.blocker_count == 0
210+
assert report.warning_count == 0
211+
assert report.summary() == (
212+
"export-package-readiness: complete "
213+
"(6 artifact(s), 1 redaction rule(s), 8 evidence bundle(s), "
214+
"7 provenance manifest(s), 0 blocker(s), 0 warning(s), "
215+
"capability=audit-report-export)"
216+
)
217+
218+
219+
def test_export_package_readiness_feeds_prototype_claim_gate() -> None:
220+
report = _evaluator().evaluate(_manifest())
221+
222+
prototype_report = report.prototype_readiness_report(
223+
PrototypeClaimLevel.SERIOUS_OPEN_SOURCE_PROTOTYPE,
224+
existing_completed_capability_ids=(
225+
"registry-layer",
226+
"policy-pack-engine",
227+
"framework-crosswalks",
228+
"signed-provenance",
229+
"telemetry-adapters",
230+
"scenario-campaign-runner",
231+
"monitoring-incidents",
232+
"review-workflow",
233+
),
234+
)
235+
236+
assert EXPORT_PACKAGE_CAPABILITY_ID in prototype_report.completed_capability_ids
237+
238+
239+
def test_export_package_readiness_limited_when_validation_has_warnings() -> None:
240+
manifest = _manifest(
241+
artifacts=(_minimal_runtime_artifact(),),
242+
redaction_rules=(_redaction_rule(),),
243+
)
244+
report = _evaluator(
245+
evidence_bundles=(
246+
_bundle("ev-export-manifest-001"),
247+
_bundle("ev-run-ledger-001"),
248+
_bundle("ev-redaction-001"),
249+
),
250+
provenance_manifest_ids=("manifest-export-001", "manifest-run-ledger-001"),
251+
).evaluate(manifest)
252+
253+
assert report.decision is ExportPackageReadinessDecision.LIMITED
254+
assert report.warning_count == 5
255+
assert not report.is_complete()
256+
257+
258+
def test_export_package_readiness_blocks_non_export_ready_manifest() -> None:
259+
report = _evaluator().evaluate(
260+
_manifest(
261+
status=ExportPackageStatus.READY_FOR_REVIEW,
262+
audience=ExportPackageAudience.LOCAL_REVIEW,
263+
redaction_rules=(),
264+
)
265+
)
266+
267+
assert report.decision is ExportPackageReadinessDecision.BLOCKED
268+
assert any(
269+
finding.finding_id == "package-export-case-runtime-001-not-export-ready"
270+
for finding in report.findings_for_package("export-case-runtime-001")
271+
)
272+
273+
274+
def test_export_package_readiness_blocks_missing_runtime_artifacts() -> None:
275+
report = _evaluator(
276+
evidence_bundles=tuple(
277+
_bundle(bundle_id)
278+
for bundle_id in (
279+
"ev-export-manifest-001",
280+
"ev-assurance-case-001",
281+
"ev-traceability-001",
282+
"ev-evidence-bundle-001",
283+
"ev-readiness-report-001",
284+
"ev-review-workflow-001",
285+
"ev-redaction-001",
286+
)
287+
),
288+
provenance_manifest_ids=(
289+
"manifest-export-001",
290+
"manifest-assurance-case-001",
291+
"manifest-traceability-001",
292+
"manifest-evidence-bundle-001",
293+
"manifest-readiness-report-001",
294+
"manifest-review-workflow-001",
295+
),
296+
).evaluate(_manifest(artifacts=_required_artifacts()[:-1]))
297+
298+
assert report.decision is ExportPackageReadinessDecision.BLOCKED
299+
assert any(
300+
finding.finding_id == "package-export-case-runtime-001-no-runtime-artifacts"
301+
for finding in report.findings_for_package("export-case-runtime-001")
302+
)
303+
304+
305+
def test_export_package_readiness_blocks_missing_evidence() -> None:
306+
report = _evaluator(evidence_bundles=()).evaluate(_manifest())
307+
308+
assert report.decision is ExportPackageReadinessDecision.BLOCKED
309+
assert report.findings_for_evidence_bundle("ev-export-manifest-001")
310+
311+
312+
def test_export_package_readiness_blocks_missing_provenance() -> None:
313+
report = _evaluator(provenance_manifest_ids=("manifest-export-001",)).evaluate(_manifest())
314+
315+
assert report.decision is ExportPackageReadinessDecision.BLOCKED
316+
assert report.findings_for_provenance_manifest("manifest-run-ledger-001")
317+
318+
319+
def test_export_package_readiness_limited_for_evidence_warnings() -> None:
320+
report = _evaluator(evidence_bundles=_bundles(unhashed="ev-run-ledger-001")).evaluate(
321+
_manifest()
322+
)
323+
324+
assert report.decision is ExportPackageReadinessDecision.LIMITED
325+
assert report.warning_count == 2
326+
assert report.findings_for_evidence_bundle("ev-run-ledger-001")
327+
328+
329+
def test_export_package_readiness_finding_validates_optional_identifiers() -> None:
330+
with pytest.raises(ContractValueError, match="needs a message"):
331+
ExportPackageReadinessFinding(
332+
finding_id="finding-export-readiness-001",
333+
severity=ExportPackageReadinessFindingSeverity.BLOCKER,
334+
source=ExportPackageReadinessFindingSource.READINESS,
335+
message="",
336+
)
337+
338+
with pytest.raises(ContractValueError, match="artifact_id must not be blank"):
339+
ExportPackageReadinessFinding(
340+
finding_id="finding-export-readiness-001",
341+
severity=ExportPackageReadinessFindingSeverity.BLOCKER,
342+
source=ExportPackageReadinessFindingSource.ARTIFACT,
343+
message="Bad artifact.",
344+
artifact_id="",
345+
)

0 commit comments

Comments
 (0)