Skip to content

Commit a1afd72

Browse files
Pigbibicodex
andcommitted
harden trusted identity invariants
Co-Authored-By: Codex <noreply@openai.com>
1 parent 827598f commit a1afd72

2 files changed

Lines changed: 47 additions & 32 deletions

File tree

src/political_event_tracking_research/trusted_workflow_identity.py

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -42,42 +42,24 @@ def _error(code: str) -> TrustedWorkflowIdentityError:
4242
return TrustedWorkflowIdentityError(code)
4343

4444

45+
@dataclass(frozen=True, slots=True, init=False)
4546
class TrustedWorkflowIdentity:
46-
"""Immutable fixed identity; construction is intentionally not public."""
47+
"""Immutable fixed identity; construction has no override parameters."""
4748

48-
__slots__ = ("_repository", "_workflow_path", "_workflow_ref")
49+
repository: str
50+
workflow_path: str
51+
workflow_ref: str
4952

50-
def __new__(cls, *_: object) -> TrustedWorkflowIdentity:
51-
raise TypeError("use trusted_workflow_identity()")
52-
53-
@classmethod
54-
def _create(cls) -> TrustedWorkflowIdentity:
55-
instance = object.__new__(cls)
56-
object.__setattr__(instance, "_repository", TRUSTED_REPOSITORY)
57-
object.__setattr__(instance, "_workflow_path", TRUSTED_WORKFLOW_PATH)
58-
object.__setattr__(instance, "_workflow_ref", TRUSTED_WORKFLOW_REF)
59-
return instance
60-
61-
@property
62-
def repository(self) -> str:
63-
return self._repository
64-
65-
@property
66-
def workflow_path(self) -> str:
67-
return self._workflow_path
68-
69-
@property
70-
def workflow_ref(self) -> str:
71-
return self._workflow_ref
72-
73-
74-
_TRUSTED_IDENTITY = TrustedWorkflowIdentity._create()
53+
def __init__(self) -> None:
54+
object.__setattr__(self, "repository", TRUSTED_REPOSITORY)
55+
object.__setattr__(self, "workflow_path", TRUSTED_WORKFLOW_PATH)
56+
object.__setattr__(self, "workflow_ref", TRUSTED_WORKFLOW_REF)
7557

7658

7759
def trusted_workflow_identity() -> TrustedWorkflowIdentity:
7860
"""Return the only supported repository/workflow identity."""
7961

80-
return _TRUSTED_IDENTITY
62+
return TrustedWorkflowIdentity()
8163

8264

8365
def _reviewed_sha(value: object) -> str:
@@ -94,7 +76,12 @@ class TrustedWorkflowEvidence:
9476
reviewed_workflow_sha: str
9577

9678
def __post_init__(self) -> None:
97-
if type(self.identity) is not TrustedWorkflowIdentity or self.identity is not _TRUSTED_IDENTITY:
79+
if (
80+
type(self.identity) is not TrustedWorkflowIdentity
81+
or self.identity.repository != TRUSTED_REPOSITORY
82+
or self.identity.workflow_path != TRUSTED_WORKFLOW_PATH
83+
or self.identity.workflow_ref != TRUSTED_WORKFLOW_REF
84+
):
9885
raise _error("trusted_workflow_identity_mismatch")
9986
_reviewed_sha(self.reviewed_workflow_sha)
10087

@@ -106,7 +93,7 @@ def validate_trusted_workflow_identity(
10693

10794
if type(workflow_ref) is not str or workflow_ref != TRUSTED_WORKFLOW_REF:
10895
raise _error("trusted_workflow_identity_mismatch")
109-
return TrustedWorkflowEvidence(_TRUSTED_IDENTITY, _reviewed_sha(reviewed_workflow_sha))
96+
return TrustedWorkflowEvidence(TrustedWorkflowIdentity(), _reviewed_sha(reviewed_workflow_sha))
11097

11198

11299
def _canonical_bytes(value: dict[str, object]) -> bytes:
@@ -119,8 +106,14 @@ def _canonical_bytes(value: dict[str, object]) -> bytes:
119106

120107

121108
def serialize_trusted_workflow_evidence(evidence: TrustedWorkflowEvidence) -> bytes:
122-
if type(evidence) is not TrustedWorkflowEvidence or evidence.identity is not _TRUSTED_IDENTITY:
109+
if type(evidence) is not TrustedWorkflowEvidence:
123110
raise _error("trusted_workflow_evidence_invalid")
111+
if (
112+
evidence.identity.repository != TRUSTED_REPOSITORY
113+
or evidence.identity.workflow_path != TRUSTED_WORKFLOW_PATH
114+
or evidence.identity.workflow_ref != TRUSTED_WORKFLOW_REF
115+
):
116+
raise _error("trusted_workflow_identity_mismatch")
124117
return _canonical_bytes(
125118
{
126119
"identity_version": IDENTITY_VERSION,
@@ -160,8 +153,11 @@ def pairs(items: list[tuple[str, object]]) -> dict[str, object]:
160153
result[key] = item
161154
return result
162155

156+
def reject_constant(_: str) -> None:
157+
raise _error("trusted_workflow_wire_invalid")
158+
163159
try:
164-
value = json.loads(raw.decode("ascii"), object_pairs_hook=pairs, parse_constant=lambda _: (_ for _ in ()).throw(_error("trusted_workflow_wire_invalid")))
160+
value = json.loads(raw.decode("ascii"), object_pairs_hook=pairs, parse_constant=reject_constant)
165161
except TrustedWorkflowIdentityError:
166162
raise
167163
except (UnicodeError, json.JSONDecodeError, TypeError, ValueError, RecursionError):

tests/test_trusted_workflow_identity.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import json
4+
from dataclasses import FrozenInstanceError, replace
45

56
import pytest
67

@@ -11,6 +12,7 @@
1112
TRUSTED_WORKFLOW_PATH,
1213
TRUSTED_WORKFLOW_REF,
1314
TrustedWorkflowIdentityError,
15+
TrustedWorkflowEvidence,
1416
parse_trusted_workflow_evidence,
1517
serialize_trusted_workflow_evidence,
1618
trusted_workflow_identity,
@@ -38,6 +40,23 @@ def test_fixed_identity_has_no_runtime_override() -> None:
3840
assert identity.workflow_ref == TRUSTED_WORKFLOW_REF
3941
with pytest.raises(TypeError):
4042
module.TrustedWorkflowIdentity(TRUSTED_REPOSITORY, "other.yml", TRUSTED_WORKFLOW_REF) # type: ignore[call-arg]
43+
with pytest.raises(FrozenInstanceError):
44+
identity.repository = "QuantStrategyLab/Other" # type: ignore[misc]
45+
assert trusted_workflow_identity().repository == TRUSTED_REPOSITORY
46+
47+
48+
def test_evidence_constructor_and_replace_revalidate_invariants() -> None:
49+
evidence = validate_trusted_workflow_identity(TRUSTED_WORKFLOW_REF, SHA)
50+
assert replace(evidence, reviewed_workflow_sha="b" * 40).reviewed_workflow_sha == "b" * 40
51+
with pytest.raises(TrustedWorkflowIdentityError, match="reviewed_workflow_sha_invalid"):
52+
replace(evidence, reviewed_workflow_sha="not-a-sha")
53+
54+
forged_identity = object.__new__(module.TrustedWorkflowIdentity)
55+
object.__setattr__(forged_identity, "repository", "QuantStrategyLab/Other")
56+
object.__setattr__(forged_identity, "workflow_path", TRUSTED_WORKFLOW_PATH)
57+
object.__setattr__(forged_identity, "workflow_ref", TRUSTED_WORKFLOW_REF)
58+
with pytest.raises(TrustedWorkflowIdentityError, match="trusted_workflow_identity_mismatch"):
59+
TrustedWorkflowEvidence(forged_identity, SHA)
4160

4261

4362
def test_runtime_sha_and_fixed_workflow_identity_round_trip() -> None:

0 commit comments

Comments
 (0)