@josephyan/qingflow-cli 0.2.0-beta.59 → 0.2.0-beta.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -236,8 +236,14 @@ class SolutionExecutor:
236
236
  existing_role_id = existing.get("role_id")
237
237
  if existing_role_id:
238
238
  return
239
- page = self.role_tools.role_search(profile=profile, keyword=role.name, page_num=1, page_size=50).get("page") or {}
240
- role_list = page.get("list") if isinstance(page, dict) else []
239
+ try:
240
+ page = self.role_tools.role_search(profile=profile, keyword=role.name, page_num=1, page_size=50).get("page") or {}
241
+ role_list = page.get("list") if isinstance(page, dict) else []
242
+ except Exception as exc: # noqa: BLE001
243
+ api_error = _coerce_qingflow_error(exc)
244
+ if api_error is None or not _is_permission_restricted_error(api_error):
245
+ raise
246
+ role_list = []
241
247
  matched_role = next(
242
248
  (
243
249
  item
@@ -312,7 +318,18 @@ class SolutionExecutor:
312
318
  if not isinstance(app_key, str) or not app_key:
313
319
  raise ValueError(f"missing app_key for package attach on entity '{entity.entity_id}'")
314
320
 
315
- package_detail = self.package_tools.package_get(profile=profile, tag_id=tag_id, include_raw=True)
321
+ try:
322
+ package_detail = self.package_tools.package_get(profile=profile, tag_id=tag_id, include_raw=True)
323
+ except Exception as exc: # noqa: BLE001
324
+ api_error = _coerce_qingflow_error(exc)
325
+ if api_error is None or not _is_permission_restricted_error(api_error):
326
+ raise
327
+ raise _required_state_read_blocked_error(
328
+ resource="package_attach",
329
+ message=f"package attach requires readable package state before sorting items for tag '{tag_id}'",
330
+ error=api_error,
331
+ details={"tag_id": tag_id, "app_key": app_key},
332
+ ) from exc
316
333
  package_result = package_detail.get("result") if isinstance(package_detail.get("result"), dict) else {}
317
334
  tag_items = [deepcopy(item) for item in package_result.get("tagItems", []) if isinstance(item, dict)]
318
335
  if any(_package_item_app_key(item) == app_key for item in tag_items):
@@ -675,7 +692,18 @@ class SolutionExecutor:
675
692
  if dash_key:
676
693
  store.set_artifact("portal", "dash_key", dash_key)
677
694
  if dash_key:
678
- base_payload = self.portal_tools.portal_get(profile=profile, dash_key=dash_key, being_draft=True).get("result") or {}
695
+ try:
696
+ base_payload = self.portal_tools.portal_get(profile=profile, dash_key=dash_key, being_draft=True).get("result") or {}
697
+ except Exception as exc: # noqa: BLE001
698
+ api_error = _coerce_qingflow_error(exc)
699
+ if api_error is None or not _is_permission_restricted_error(api_error):
700
+ raise
701
+ raise _required_state_read_blocked_error(
702
+ resource="portal",
703
+ message=f"portal update requires readable draft state for dash '{dash_key}'",
704
+ error=api_error,
705
+ details={"dash_key": dash_key},
706
+ ) from exc
679
707
  update_payload = self._resolve_portal_payload(compiled.portal_plan["update_payload"], store, base_payload=base_payload)
680
708
  self.portal_tools.portal_update(profile=profile, dash_key=dash_key, payload=update_payload)
681
709
  self._refresh_portal_artifact(profile=profile, store=store, being_draft=True, artifact_key="draft_result")
@@ -2123,6 +2151,37 @@ def _coerce_qingflow_error(error: Exception) -> QingflowApiError | None:
2123
2151
  )
2124
2152
 
2125
2153
 
2154
+ def _is_permission_restricted_error(error: QingflowApiError) -> bool:
2155
+ return error.backend_code in {40002, 40027}
2156
+
2157
+
2158
+ def _required_state_read_blocked_error(
2159
+ *,
2160
+ resource: str,
2161
+ message: str,
2162
+ error: QingflowApiError,
2163
+ details: dict[str, Any] | None = None,
2164
+ ) -> QingflowApiError:
2165
+ merged_details = deepcopy(details) if isinstance(details, dict) else {}
2166
+ merged_details["state_read_blocked"] = {
2167
+ "resource": resource,
2168
+ "transport_error": {
2169
+ "http_status": error.http_status,
2170
+ "backend_code": error.backend_code,
2171
+ "category": error.category,
2172
+ "request_id": error.request_id,
2173
+ },
2174
+ }
2175
+ return QingflowApiError(
2176
+ category=error.category,
2177
+ message=message,
2178
+ backend_code=error.backend_code,
2179
+ request_id=error.request_id,
2180
+ http_status=error.http_status,
2181
+ details=merged_details,
2182
+ )
2183
+
2184
+
2126
2185
  def _portal_component_position(
2127
2186
  source_type: Any,
2128
2187
  *,
@@ -1855,10 +1855,30 @@ class SolutionTools(ToolBase):
1855
1855
  try:
1856
1856
  result = packages.package_get(profile=profile, tag_id=package_tag_id, include_raw=False)
1857
1857
  except (QingflowApiError, RuntimeError) as exc:
1858
+ error = _coerce_solution_api_error(exc)
1859
+ if error.backend_code in {40002, 40027}:
1860
+ return {
1861
+ "status": "resolved",
1862
+ "matched_via": "tag_id",
1863
+ "tag_id": package_tag_id,
1864
+ "tag_name": None,
1865
+ "candidates": [],
1866
+ "metadata_unverified": True,
1867
+ "lookup_permission_blocked": {
1868
+ "scope": "package",
1869
+ "target": {"tag_id": package_tag_id},
1870
+ "transport_error": {
1871
+ "http_status": error.http_status,
1872
+ "backend_code": error.backend_code,
1873
+ "category": error.category,
1874
+ "request_id": error.request_id,
1875
+ },
1876
+ },
1877
+ }
1858
1878
  return _builder_package_resolution_failed(
1859
1879
  package_name=normalized_name,
1860
1880
  package_tag_id=package_tag_id,
1861
- error=_coerce_solution_api_error(exc),
1881
+ error=error,
1862
1882
  retried=False,
1863
1883
  )
1864
1884
  summary = result.get("result") if isinstance(result.get("result"), dict) else {}