Skip to content

Commit fe8817e

Browse files
runningcodeclaude
andcommitted
fix(artifacts): Use distribution endpoint for skip reporting (EME-422)
The update_artifact endpoint silently ignored distribution_state and distribution_skip_reason fields. Use the dedicated distribution endpoint that accepts error_code and error_message instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e003ab1 commit fe8817e

File tree

4 files changed

+42
-35
lines changed

4 files changed

+42
-35
lines changed

src/launchpad/artifact_processor.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from launchpad.artifacts.artifact_factory import ArtifactFactory
3535
from launchpad.constants import (
3636
ArtifactType,
37-
DistributionState,
37+
InstallableAppErrorCode,
3838
PreprodFeature,
3939
ProcessingErrorCode,
4040
ProcessingErrorMessage,
@@ -424,16 +424,13 @@ def _update_distribution_skip(
424424
artifact_id: str,
425425
skip_reason: str,
426426
) -> None:
427-
"""Update artifact with distribution skip state."""
427+
"""Report distribution skip via the dedicated distribution endpoint."""
428428
try:
429-
self._sentry_client.update_artifact(
429+
self._sentry_client.update_distribution_error(
430430
org=organization_id,
431-
project=project_id,
432431
artifact_id=artifact_id,
433-
data={
434-
"distribution_state": DistributionState.NOT_RAN.value,
435-
"distribution_skip_reason": skip_reason,
436-
},
432+
error_code=InstallableAppErrorCode.SKIPPED.value,
433+
error_message=skip_reason,
437434
)
438435
except SentryClientError:
439436
logger.exception(f"Failed to update distribution skip for artifact {artifact_id}")

src/launchpad/constants.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ class PreprodFeature(Enum):
3232
BUILD_DISTRIBUTION = "build_distribution"
3333

3434

35-
# Matches PreprodArtifact.DistributionState in sentry
36-
class DistributionState(Enum):
37-
PENDING = 0
38-
COMPLETED = 1
39-
NOT_RAN = 2
35+
# Matches InstallableApp.ErrorCode in sentry
36+
class InstallableAppErrorCode(Enum):
37+
UNKNOWN = 0
38+
NO_QUOTA = 1
39+
SKIPPED = 2
40+
PROCESSING_ERROR = 3
4041

4142

4243
# Health check threshold - consider unhealthy if file not touched in 60 seconds

src/launchpad/sentry_client.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,24 @@ def update_artifact(self, org: str, project: str, artifact_id: str, data: Dict[s
252252
endpoint = f"/api/0/internal/{org}/{project}/files/preprodartifacts/{artifact_id}/update/"
253253
return self._make_json_request("PUT", endpoint, UpdateResponse, data=data)
254254

255+
def update_distribution_error(self, org: str, artifact_id: str, error_code: int, error_message: str) -> None:
256+
"""Report distribution error via the dedicated distribution endpoint."""
257+
endpoint = f"/api/0/organizations/{org}/preprodartifacts/{artifact_id}/distribution/"
258+
url = self._build_url(endpoint)
259+
body = json.dumps({"error_code": error_code, "error_message": error_message}).encode("utf-8")
260+
261+
logger.debug(f"PUT {url}")
262+
response = self.session.request(
263+
method="PUT",
264+
url=url,
265+
data=body,
266+
auth=self.auth,
267+
timeout=30,
268+
)
269+
270+
if response.status_code != 200:
271+
raise SentryClientError(response=response)
272+
255273
def upload_size_analysis_file(
256274
self,
257275
org: str,

tests/unit/artifacts/test_artifact_processor.py

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from launchpad.artifacts.apple.zipped_xcarchive import ZippedXCArchive
1010
from launchpad.artifacts.artifact import Artifact
1111
from launchpad.constants import (
12-
DistributionState,
12+
InstallableAppErrorCode,
1313
ProcessingErrorCode,
1414
ProcessingErrorMessage,
1515
)
@@ -142,7 +142,7 @@ def test_processing_error_message_enum_values(self):
142142

143143
def test_do_distribution_unknown_artifact_type_skips(self):
144144
mock_sentry_client = Mock(spec=SentryClient)
145-
mock_sentry_client.update_artifact.return_value = None
145+
mock_sentry_client.update_distribution_error.return_value = None
146146
self.processor._sentry_client = mock_sentry_client
147147

148148
unknown_artifact = Mock(spec=Artifact)
@@ -152,19 +152,16 @@ def test_do_distribution_unknown_artifact_type_skips(self):
152152
"test-org-id", "test-project-id", "test-artifact-id", unknown_artifact, mock_info
153153
)
154154

155-
mock_sentry_client.update_artifact.assert_called_once_with(
155+
mock_sentry_client.update_distribution_error.assert_called_once_with(
156156
org="test-org-id",
157-
project="test-project-id",
158157
artifact_id="test-artifact-id",
159-
data={
160-
"distribution_state": DistributionState.NOT_RAN.value,
161-
"distribution_skip_reason": "unsupported",
162-
},
158+
error_code=InstallableAppErrorCode.SKIPPED.value,
159+
error_message="unsupported",
163160
)
164161

165162
def test_do_distribution_invalid_code_signature_skips(self):
166163
mock_sentry_client = Mock(spec=SentryClient)
167-
mock_sentry_client.update_artifact.return_value = None
164+
mock_sentry_client.update_distribution_error.return_value = None
168165
self.processor._sentry_client = mock_sentry_client
169166

170167
artifact = Mock(spec=ZippedXCArchive)
@@ -174,20 +171,17 @@ def test_do_distribution_invalid_code_signature_skips(self):
174171

175172
self.processor._do_distribution("test-org-id", "test-project-id", "test-artifact-id", artifact, mock_info)
176173

177-
mock_sentry_client.update_artifact.assert_called_once_with(
174+
mock_sentry_client.update_distribution_error.assert_called_once_with(
178175
org="test-org-id",
179-
project="test-project-id",
180176
artifact_id="test-artifact-id",
181-
data={
182-
"distribution_state": DistributionState.NOT_RAN.value,
183-
"distribution_skip_reason": "invalid_signature",
184-
},
177+
error_code=InstallableAppErrorCode.SKIPPED.value,
178+
error_message="invalid_signature",
185179
)
186180
mock_sentry_client.upload_installable_app.assert_not_called()
187181

188182
def test_do_distribution_simulator_build_skips(self):
189183
mock_sentry_client = Mock(spec=SentryClient)
190-
mock_sentry_client.update_artifact.return_value = None
184+
mock_sentry_client.update_distribution_error.return_value = None
191185
self.processor._sentry_client = mock_sentry_client
192186

193187
artifact = Mock(spec=ZippedXCArchive)
@@ -197,14 +191,11 @@ def test_do_distribution_simulator_build_skips(self):
197191

198192
self.processor._do_distribution("test-org-id", "test-project-id", "test-artifact-id", artifact, mock_info)
199193

200-
mock_sentry_client.update_artifact.assert_called_once_with(
194+
mock_sentry_client.update_distribution_error.assert_called_once_with(
201195
org="test-org-id",
202-
project="test-project-id",
203196
artifact_id="test-artifact-id",
204-
data={
205-
"distribution_state": DistributionState.NOT_RAN.value,
206-
"distribution_skip_reason": "simulator",
207-
},
197+
error_code=InstallableAppErrorCode.SKIPPED.value,
198+
error_message="simulator",
208199
)
209200
mock_sentry_client.upload_installable_app.assert_not_called()
210201

0 commit comments

Comments
 (0)