test(performance): enforce semantic artifact authority boundary
CI / lite (pull_request) Successful in 9s
CI / lite (pull_request) Successful in 9s
This commit is contained in:
@@ -319,7 +319,16 @@ def test_present_evidence_is_deterministic_content_addressed_and_three_party_clo
|
||||
assert first.benchmark_series_digest is not None
|
||||
assert first.canonical_bytes() == first.to_json().encode("utf-8")
|
||||
assert not first.canonical_bytes().endswith(b"\n")
|
||||
assert _sha256(first.canonical_bytes()) == first.document_sha256
|
||||
document_payload = first.to_dict()
|
||||
document_payload.pop("document_sha256")
|
||||
expected_document = json.dumps(
|
||||
document_payload,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
).encode("utf-8")
|
||||
assert _sha256(expected_document) == first.document_sha256
|
||||
assert PerformanceEvidenceV1.from_dict(
|
||||
first.to_dict(),
|
||||
artifact=artifact,
|
||||
@@ -490,6 +499,125 @@ def test_performance_table_row_and_benchmark_digest_mismatches_fail_closed() ->
|
||||
)
|
||||
|
||||
|
||||
def test_manifest_closure_covers_non_performance_artifact_tables() -> None:
|
||||
_, artifact, run_ref, manifest = _case("estimable")
|
||||
nav = artifact.nav
|
||||
nav.loc[0, "nav"] += 0.01
|
||||
changed_artifact = replace(artifact, _nav=nav)
|
||||
|
||||
with pytest.raises(PerformanceEvidenceError) as rejected:
|
||||
build_performance_evidence(changed_artifact, run_ref, manifest)
|
||||
|
||||
_assert_error(
|
||||
rejected,
|
||||
PerformanceEvidenceErrorCode.EVIDENCE_MISMATCH,
|
||||
"$.artifact.tables.nav.content_digest",
|
||||
)
|
||||
|
||||
|
||||
def test_row_and_benchmark_mutations_change_their_digests_and_document_identity() -> None:
|
||||
original, artifact, run_ref, _ = _case("estimable")
|
||||
performance = artifact.performance
|
||||
performance.loc[0, "sharpe"] += 0.01
|
||||
changed_performance_artifact = replace(artifact, _performance=performance)
|
||||
changed_performance_manifest = build_backtest_evidence_manifest(
|
||||
run_ref,
|
||||
changed_performance_artifact,
|
||||
artifact_available_at="2026-01-08T02:05:00Z",
|
||||
)
|
||||
changed_performance = build_performance_evidence(
|
||||
changed_performance_artifact,
|
||||
run_ref,
|
||||
changed_performance_manifest,
|
||||
)
|
||||
assert changed_performance.performance_row_digest != original.performance_row_digest
|
||||
assert changed_performance.performance_evidence_id != original.performance_evidence_id
|
||||
|
||||
nav = artifact.nav
|
||||
nav.loc[0, "benchmark_nav"] += 0.01
|
||||
changed_benchmark_artifact = replace(artifact, _nav=nav)
|
||||
changed_benchmark_manifest = build_backtest_evidence_manifest(
|
||||
run_ref,
|
||||
changed_benchmark_artifact,
|
||||
artifact_available_at="2026-01-08T02:05:00Z",
|
||||
)
|
||||
changed_benchmark = build_performance_evidence(
|
||||
changed_benchmark_artifact,
|
||||
run_ref,
|
||||
changed_benchmark_manifest,
|
||||
)
|
||||
assert changed_benchmark.benchmark_series_digest != original.benchmark_series_digest
|
||||
assert changed_benchmark.performance_row_digest == original.performance_row_digest
|
||||
assert changed_benchmark.performance_evidence_id != original.performance_evidence_id
|
||||
|
||||
|
||||
def test_relative_metric_null_reasons_cannot_be_invented() -> None:
|
||||
_, artifact, run_ref, _ = _case("estimable")
|
||||
performance = artifact.performance
|
||||
performance.loc[0, "alpha"] = float("nan")
|
||||
changed_artifact = replace(artifact, _performance=performance)
|
||||
changed_manifest = build_backtest_evidence_manifest(
|
||||
run_ref,
|
||||
changed_artifact,
|
||||
artifact_available_at="2026-01-08T02:05:00Z",
|
||||
)
|
||||
with pytest.raises(PerformanceEvidenceError) as false_alpha_domain:
|
||||
build_performance_evidence(changed_artifact, run_ref, changed_manifest)
|
||||
_assert_error(
|
||||
false_alpha_domain,
|
||||
PerformanceEvidenceErrorCode.METRIC_INVALID,
|
||||
"$.metrics.alpha.value",
|
||||
)
|
||||
|
||||
_, absent_artifact, absent_run_ref, _ = _case("absent")
|
||||
absent_performance = absent_artifact.performance
|
||||
absent_performance.loc[0, "tracking_error"] = 0.0
|
||||
changed_absent = replace(absent_artifact, _performance=absent_performance)
|
||||
changed_absent_manifest = build_backtest_evidence_manifest(
|
||||
absent_run_ref,
|
||||
changed_absent,
|
||||
artifact_available_at="2026-01-08T02:05:00Z",
|
||||
)
|
||||
with pytest.raises(PerformanceEvidenceError) as false_absence:
|
||||
build_performance_evidence(
|
||||
changed_absent,
|
||||
absent_run_ref,
|
||||
changed_absent_manifest,
|
||||
)
|
||||
_assert_error(
|
||||
false_absence,
|
||||
PerformanceEvidenceErrorCode.BENCHMARK_INVALID,
|
||||
"$.metrics.tracking_error.availability",
|
||||
)
|
||||
|
||||
|
||||
def test_artifact_builder_enforces_strict_benchmark_session_alignment() -> None:
|
||||
run_ref = _run_ref()
|
||||
result = _backtest_result()
|
||||
misaligned = pd.Series(
|
||||
[0.0, 0.01, -0.01, 0.02],
|
||||
index=result.returns.index.shift(1, freq="B"),
|
||||
)
|
||||
with pytest.raises(ValueError, match="matching indexes"):
|
||||
build_research_run_artifact(
|
||||
result,
|
||||
run_id=run_ref.run_id,
|
||||
strategy_id=run_ref.strategy_id,
|
||||
strategy_name="Alpha Top 1",
|
||||
strategy_version=run_ref.strategy_version,
|
||||
engine_version="1.2.0",
|
||||
code_revision=run_ref.code_revision,
|
||||
data_snapshot_id=run_ref.dataset_snapshot_id,
|
||||
calendar="CN-A",
|
||||
timezone="Asia/Shanghai",
|
||||
started_at="2026-01-08T10:00:00+08:00",
|
||||
finished_at="2026-01-08T10:01:00+08:00",
|
||||
parameters=PARAMETERS,
|
||||
benchmark_id="000300.SH",
|
||||
benchmark_returns=misaligned,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("column", "value", "path"),
|
||||
[
|
||||
@@ -500,7 +628,6 @@ def test_performance_table_row_and_benchmark_digest_mismatches_fail_closed() ->
|
||||
("win_rate", 1.01, "$.metrics.win_rate.value"),
|
||||
("tracking_error", -0.01, "$.metrics.tracking_error.value"),
|
||||
("n_trades", True, "$.metrics.trade_count.value"),
|
||||
("n_days", 2**53, "$.metrics.day_count.value"),
|
||||
],
|
||||
)
|
||||
def test_metric_domains_reject_invalid_source_values(
|
||||
@@ -598,4 +725,3 @@ def test_public_mapping_has_no_raw_inputs_storage_or_runtime_authority() -> None
|
||||
assert not (keys(payload) & forbidden_keys)
|
||||
for token in ("postgres://", "mysql://", "s3://", "credential", "broker"):
|
||||
assert token not in serialized
|
||||
|
||||
|
||||
Reference in New Issue
Block a user