Skip to content

Commit bc1f376

Browse files
Pigbibicodex
andcommitted
fix: validate allocation reuse policy
Co-Authored-By: Codex <noreply@openai.com>
1 parent 07835cb commit bc1f376

2 files changed

Lines changed: 96 additions & 2 deletions

File tree

‎src/quant_advisor_research/allocation_context.py‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
V3_VARIANT,
2323
V3IdentityBinding,
2424
V3IdentityIndex,
25+
V3_SCHEMA_VERSION,
2526
parse_v3_index,
2627
)
2728
from .period_contract import canonical_period_identity
@@ -127,12 +128,19 @@ def _binding_payload(binding: V3IdentityBinding) -> dict[str, object]:
127128

128129

129130
def _validated_index(index: object) -> V3IdentityIndex:
130-
if not isinstance(index, V3IdentityIndex) or type(index.schema_version) is not int:
131+
if (
132+
not isinstance(index, V3IdentityIndex)
133+
or type(index.schema_version) is not int
134+
or index.schema_version != V3_SCHEMA_VERSION
135+
):
131136
raise _error("identity_inventory_invalid")
132137
try:
133138
if type(index.bindings) is not tuple:
134139
raise _error("identity_inventory_invalid")
135-
payload = {"schema_version": 3, "reports": [_binding_payload(binding) for binding in index.bindings]}
140+
payload = {
141+
"schema_version": index.schema_version,
142+
"reports": [_binding_payload(binding) for binding in index.bindings],
143+
}
136144
validated = parse_v3_index(payload)
137145
if len(validated.bindings) != len(index.bindings):
138146
raise _error("identity_inventory_invalid")
@@ -258,6 +266,21 @@ def _validate_display(display: object) -> DisplayPlacement:
258266
return display
259267

260268

269+
def _validate_reuse_policy(
270+
binding: V3IdentityBinding,
271+
requested: RequestedArtifactSet,
272+
display_placement: object,
273+
) -> None:
274+
display = _validate_display(display_placement)
275+
if (
276+
(binding.markdown_name is not None) != requested.include_markdown
277+
or (binding.manifest_name is not None) != requested.include_manifest
278+
or binding.display_primary != display.display_primary
279+
or binding.display_order != display.display_order
280+
):
281+
raise _error("identity_reuse_mismatch")
282+
283+
261284
def _exact_key(binding: V3IdentityBinding) -> tuple[str, ...]:
262285
return (
263286
binding.period_key, binding.as_of, binding.cadence, binding.report_schema_version,
@@ -317,6 +340,7 @@ def allocate_v3_identity(
317340
raise _error("identity_integrity_conflict")
318341
if exact_matches:
319342
binding = exact_matches[0]
343+
_validate_reuse_policy(binding, requested, display_placement)
320344
return V3AllocationPlan(binding, context.mode, True)
321345
if context.mode is AllocationMode.EXACT_ARTIFACT_REUSE:
322346
raise _error("identity_reuse_mismatch" if same_artifact else "identity_reuse_not_found")

‎tests/test_allocation_context.py‎

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ def test_exact_artifact_reuse_is_immutable_and_requires_full_match() -> None:
189189
inventory=inventory,
190190
context=AllocationContext.exact_artifact_reuse(),
191191
requested_artifacts=RequestedArtifactSet(False, False),
192+
display_placement=DisplayPlacement(True, 2),
192193
)
193194

194195
assert result.reused_existing is True
@@ -204,9 +205,77 @@ def test_exact_artifact_reuse_is_immutable_and_requires_full_match() -> None:
204205
inventory=inventory,
205206
context=AllocationContext.exact_artifact_reuse(),
206207
requested_artifacts=REQUESTED,
208+
display_placement=DISPLAY,
209+
)
210+
211+
212+
@pytest.mark.parametrize(
213+
("requested", "display"),
214+
[
215+
(RequestedArtifactSet(True, False), DisplayPlacement(True, 2)),
216+
(RequestedArtifactSet(False, True), DisplayPlacement(True, 2)),
217+
(RequestedArtifactSet(False, False), DisplayPlacement(False, 2)),
218+
(RequestedArtifactSet(False, False), DisplayPlacement(True, 3)),
219+
],
220+
)
221+
def test_exact_reuse_rejects_publication_policy_mismatch(
222+
requested: RequestedArtifactSet, display: DisplayPlacement
223+
) -> None:
224+
report = build_report()
225+
binding = binding_for(report, display_primary=True, display_order=2)
226+
227+
with pytest.raises(IdentityMetadataError, match="identity_reuse_mismatch"):
228+
allocate_v3_identity(
229+
report,
230+
inventory=inventory_for((binding, report)),
231+
context=AllocationContext.exact_artifact_reuse(),
232+
requested_artifacts=requested,
233+
display_placement=display,
234+
)
235+
236+
237+
@pytest.mark.parametrize(
238+
"display",
239+
[None, DisplayPlacement(True, True), DisplayPlacement(True, -1)],
240+
)
241+
def test_exact_reuse_requires_valid_matching_display_placement(display: object) -> None:
242+
report = build_report()
243+
binding = binding_for(report, display_primary=True, display_order=2)
244+
245+
with pytest.raises(IdentityMetadataError, match="display_placement"):
246+
allocate_v3_identity(
247+
report,
248+
inventory=inventory_for((binding, report)),
249+
context=AllocationContext.exact_artifact_reuse(),
250+
requested_artifacts=REQUESTED,
251+
display_placement=display, # type: ignore[arg-type]
252+
)
253+
254+
255+
@pytest.mark.parametrize("schema_version", [2, 4, True, "3"])
256+
def test_inventory_rejects_declared_schema_version_other_than_v3(schema_version: object) -> None:
257+
report = build_report()
258+
binding = binding_for(report)
259+
260+
with pytest.raises(IdentityMetadataError, match="identity_inventory_invalid"):
261+
make_complete_source_inventory(
262+
V3IdentityIndex(schema_version, (binding,)), # type: ignore[arg-type]
263+
{binding.json_name: report},
207264
)
208265

209266

267+
def test_inventory_accepts_declared_v3_schema_version() -> None:
268+
report = build_report()
269+
binding = binding_for(report)
270+
271+
inventory = make_complete_source_inventory(
272+
V3IdentityIndex(3, (binding,)),
273+
{binding.json_name: report},
274+
)
275+
276+
assert inventory.index.schema_version == 3
277+
278+
210279
def test_semantic_same_artifact_different_is_not_reused() -> None:
211280
report = build_report()
212281
changed = dict(report)
@@ -279,6 +348,7 @@ def test_display_and_identity_ownership_are_independent() -> None:
279348
inventory=inventory,
280349
context=AllocationContext.exact_artifact_reuse(),
281350
requested_artifacts=REQUESTED,
351+
display_placement=DisplayPlacement(False, 0),
282352
)
283353

284354
assert result.binding.canonical_identity is True

0 commit comments

Comments
 (0)