@josephyan/qingflow-cli 1.1.3 → 1.1.5

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.
Files changed (154) hide show
  1. package/README.md +7 -3
  2. package/docs/local-agent-install.md +57 -6
  3. package/entry_point.py +1 -1
  4. package/npm/bin/qingflow-skills.mjs +5 -0
  5. package/npm/bin/qingflow.mjs +1 -34
  6. package/npm/lib/runtime.mjs +21 -101
  7. package/npm/scripts/postinstall.mjs +1 -10
  8. package/package.json +3 -2
  9. package/pyproject.toml +1 -1
  10. package/skills/qingflow-cli/SKILL.md +58 -44
  11. package/skills/qingflow-cli/manifest.yaml +1 -1
  12. package/skills/qingflow-cli/reference/00-INDEX.md +35 -0
  13. package/skills/qingflow-cli/reference/builder/10-build-single-app.md +38 -0
  14. package/skills/qingflow-cli/reference/builder/20-build-complete-system.md +39 -0
  15. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_SCHEMA_APPLY_FIELD_TYPES_AND_SCENARIOS.md → builder/30-schema-fields.md} +52 -10
  16. package/skills/qingflow-cli/reference/builder/40-layout.md +52 -0
  17. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_VIEWS_WORKFLOW.md → builder/50-views.md} +39 -15
  18. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_CHARTS_WORKFLOW.md → builder/60-charts.md} +36 -13
  19. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_PORTAL_WORKFLOW.md → builder/70-portal.md} +36 -13
  20. package/skills/qingflow-cli/reference/builder/80-buttons-associated-resources.md +41 -0
  21. package/skills/qingflow-cli/reference/builder/90-workflow.md +34 -0
  22. package/skills/qingflow-cli/reference/builder/99-publish-verify.md +46 -0
  23. package/skills/qingflow-cli/reference/builder/README.md +41 -0
  24. package/skills/qingflow-cli/reference/builder/code-integrations/README.md +130 -0
  25. package/skills/qingflow-cli/reference/builder/code-integrations/code-block.md +66 -0
  26. package/skills/qingflow-cli/reference/builder/code-integrations/q-linker.md +77 -0
  27. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_APP_DELIVERY_WORKFLOW.md → builder/reference/app-delivery-sop.md} +26 -16
  28. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/README.md +293 -0
  29. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/build-complete-system.md +809 -0
  30. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/build-single-app.md +830 -0
  31. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/complete-system-development-guide.md +123 -0
  32. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/create-app.md +182 -0
  33. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/environments.md +63 -0
  34. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/flow-actors-and-permissions.md +142 -0
  35. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/gotchas.md +108 -0
  36. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/match-rules.md +114 -0
  37. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/public-surface-sync.md +75 -0
  38. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/single-app-development-guide.md +58 -0
  39. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/solution-playbooks.md +52 -0
  40. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/tool-selection.md +107 -0
  41. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-flow.md +7 -0
  42. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-layout.md +7 -0
  43. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-schema.md +7 -0
  44. package/skills/qingflow-cli/reference/builder/reference/legacy-playbooks/update-views.md +7 -0
  45. package/skills/qingflow-cli/reference/builder/workflow/01-overview.md +45 -0
  46. package/skills/qingflow-cli/reference/builder/workflow/02-update-mode.md +53 -0
  47. package/skills/qingflow-cli/reference/builder/workflow/03-flow-patterns.md +57 -0
  48. package/skills/qingflow-cli/reference/builder/workflow/04-stage1-business-modeling.md +131 -0
  49. package/skills/qingflow-cli/reference/builder/workflow/05-stage2-members-roles.md +29 -0
  50. package/skills/qingflow-cli/reference/builder/workflow/06-stage3-build-spec.md +165 -0
  51. package/skills/qingflow-cli/reference/builder/workflow/07-stage4-validate-spec.md +33 -0
  52. package/skills/qingflow-cli/reference/builder/workflow/08-stage5-apply-verify.md +51 -0
  53. package/skills/qingflow-cli/reference/builder/workflow/09-stage6-summary.md +88 -0
  54. package/skills/qingflow-cli/reference/builder/workflow/10-node-config-reference.md +93 -0
  55. package/skills/qingflow-cli/reference/builder/workflow/11-troubleshooting.md +15 -0
  56. package/skills/qingflow-cli/reference/builder/workflow/README.md +88 -0
  57. package/skills/qingflow-cli/reference/builder/workflow/workflow-schema.json +1754 -0
  58. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_ADMIN_CHEATSHEET.md → core/QINGFLOW_CLI_ADMIN_CHEATSHEET.md} +3 -3
  59. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md → core/QINGFLOW_CLI_DATA_RETRIEVAL_WORKFLOW.md} +6 -6
  60. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_EXPLORATION_REPORT.md → core/QINGFLOW_CLI_EXPLORATION_REPORT.md} +2 -2
  61. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_FIELD_DATA_TYPES.md → core/QINGFLOW_CLI_FIELD_DATA_TYPES.md} +11 -11
  62. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_MEMBER_CHEATSHEET.md → core/QINGFLOW_CLI_MEMBER_CHEATSHEET.md} +4 -4
  63. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md → core/QINGFLOW_CLI_ONE_SHOT_CHEATSHEET.md} +4 -4
  64. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_CREATE_WORKFLOW.md} +3 -3
  65. package/skills/qingflow-cli/reference/record/QINGFLOW_CLI_RECORD_DELETE_WORKFLOW.md +31 -0
  66. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_IMPORT_WORKFLOW.md} +4 -4
  67. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md → record/QINGFLOW_CLI_RECORD_UPDATE_WORKFLOW.md} +7 -7
  68. package/skills/qingflow-cli/reference/record/analysis/README.md +130 -0
  69. package/skills/qingflow-cli/reference/record/analysis/analysis-gotchas.md +91 -0
  70. package/skills/qingflow-cli/reference/record/analysis/analysis-patterns.md +112 -0
  71. package/skills/qingflow-cli/reference/record/analysis/business-context.md +74 -0
  72. package/skills/qingflow-cli/reference/record/analysis/confidence-reporting.md +69 -0
  73. package/skills/qingflow-cli/reference/record/analysis/data-access-playbook.md +106 -0
  74. package/skills/qingflow-cli/reference/record/analysis/pandas-recipes.md +172 -0
  75. package/skills/qingflow-cli/reference/record/analysis/report-format.md +76 -0
  76. package/skills/qingflow-cli/reference/record/insert/README.md +75 -0
  77. package/skills/qingflow-cli/reference/{QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md → task/QINGFLOW_CLI_TASK_CONTEXT_WORKFLOW.md} +5 -5
  78. package/skills/qingflow-cli/reference/task/ops/README.md +131 -0
  79. package/skills/qingflow-cli/reference/task/ops/environments.md +43 -0
  80. package/skills/qingflow-cli/reference/task/ops/workflow-usage.md +26 -0
  81. package/skills/qingflow-cli/scripts/validate_system_build_summary.py +124 -0
  82. package/skills/qingflow-cli/scripts/workflow/diff_flow_spec.py +275 -0
  83. package/skills/qingflow-cli/scripts/workflow/validate_flow_spec.py +605 -0
  84. package/skills/qingflow-mcp-setup/SKILL.md +115 -0
  85. package/skills/qingflow-mcp-setup/agents/openai.yaml +4 -0
  86. package/skills/qingflow-mcp-setup/references/claude-desktop.md +34 -0
  87. package/skills/qingflow-mcp-setup/references/environments.md +62 -0
  88. package/skills/qingflow-mcp-setup/references/generic-stdio.md +32 -0
  89. package/skills/qingflow-mcp-setup/scripts/check_local_server.sh +38 -0
  90. package/src/qingflow_mcp/__init__.py +1 -1
  91. package/src/qingflow_mcp/__main__.py +6 -2
  92. package/src/qingflow_mcp/builder_facade/models.py +287 -25
  93. package/src/qingflow_mcp/builder_facade/service.py +4195 -856
  94. package/src/qingflow_mcp/cli/commands/builder.py +316 -247
  95. package/src/qingflow_mcp/cli/commands/chart.py +1 -1
  96. package/src/qingflow_mcp/cli/commands/common.py +12 -3
  97. package/src/qingflow_mcp/cli/commands/exports.py +2 -2
  98. package/src/qingflow_mcp/cli/commands/imports.py +3 -3
  99. package/src/qingflow_mcp/cli/commands/portal.py +2 -2
  100. package/src/qingflow_mcp/cli/commands/record.py +101 -27
  101. package/src/qingflow_mcp/cli/commands/task.py +28 -47
  102. package/src/qingflow_mcp/cli/commands/view.py +1 -1
  103. package/src/qingflow_mcp/cli/context.py +0 -3
  104. package/src/qingflow_mcp/cli/formatters.py +784 -16
  105. package/src/qingflow_mcp/cli/main.py +117 -33
  106. package/src/qingflow_mcp/errors.py +43 -2
  107. package/src/qingflow_mcp/public_surface.py +26 -17
  108. package/src/qingflow_mcp/response_trim.py +81 -17
  109. package/src/qingflow_mcp/server.py +14 -12
  110. package/src/qingflow_mcp/server_app_builder.py +65 -21
  111. package/src/qingflow_mcp/server_app_user.py +22 -16
  112. package/src/qingflow_mcp/session_store.py +11 -7
  113. package/src/qingflow_mcp/solution/compiler/__init__.py +3 -1
  114. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +173 -0
  115. package/src/qingflow_mcp/solution/executor.py +245 -18
  116. package/src/qingflow_mcp/tools/ai_builder_tools.py +1782 -399
  117. package/src/qingflow_mcp/tools/app_tools.py +184 -43
  118. package/src/qingflow_mcp/tools/approval_tools.py +197 -35
  119. package/src/qingflow_mcp/tools/auth_tools.py +92 -16
  120. package/src/qingflow_mcp/tools/code_block_tools.py +298 -40
  121. package/src/qingflow_mcp/tools/custom_button_tools.py +64 -10
  122. package/src/qingflow_mcp/tools/directory_tools.py +236 -72
  123. package/src/qingflow_mcp/tools/export_tools.py +244 -34
  124. package/src/qingflow_mcp/tools/feedback_tools.py +9 -0
  125. package/src/qingflow_mcp/tools/file_tools.py +9 -3
  126. package/src/qingflow_mcp/tools/import_tools.py +336 -49
  127. package/src/qingflow_mcp/tools/navigation_tools.py +91 -12
  128. package/src/qingflow_mcp/tools/package_tools.py +118 -6
  129. package/src/qingflow_mcp/tools/portal_tools.py +39 -3
  130. package/src/qingflow_mcp/tools/qingbi_report_tools.py +116 -7
  131. package/src/qingflow_mcp/tools/record_tools.py +1141 -356
  132. package/src/qingflow_mcp/tools/resource_read_tools.py +188 -39
  133. package/src/qingflow_mcp/tools/role_tools.py +80 -9
  134. package/src/qingflow_mcp/tools/solution_tools.py +59 -45
  135. package/src/qingflow_mcp/tools/task_context_tools.py +662 -158
  136. package/src/qingflow_mcp/tools/task_tools.py +113 -29
  137. package/src/qingflow_mcp/tools/view_tools.py +106 -3
  138. package/src/qingflow_mcp/tools/workflow_tools.py +48 -4
  139. package/src/qingflow_mcp/tools/workspace_tools.py +71 -3
  140. /package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_MATCH_RULES.md → builder/reference/match-rules.md} +0 -0
  141. /package/skills/qingflow-cli/reference/{QINGFLOW_CLI_BUILDER_WORKSPACE_ICONS.md → builder/reference/workspace-icons.md} +0 -0
  142. /package/skills/qingflow-cli/reference/{charts_remove.example.json → examples/charts/charts_remove.example.json} +0 -0
  143. /package/skills/qingflow-cli/reference/{charts_reorder.example.json → examples/charts/charts_reorder.example.json} +0 -0
  144. /package/skills/qingflow-cli/reference/{charts_upsert_bar.example.json → examples/charts/charts_upsert_bar.example.json} +0 -0
  145. /package/skills/qingflow-cli/reference/{charts_upsert_dashboard_starter.example.json → examples/charts/charts_upsert_dashboard_starter.example.json} +0 -0
  146. /package/skills/qingflow-cli/reference/{charts_upsert_minimal.example.json → examples/charts/charts_upsert_minimal.example.json} +0 -0
  147. /package/skills/qingflow-cli/reference/{portal_sections_all_types.example.json → examples/portal/portal_sections_all_types.example.json} +0 -0
  148. /package/skills/qingflow-cli/reference/{portal_sections_five_types.example.json → examples/portal/portal_sections_five_types.example.json} +0 -0
  149. /package/skills/qingflow-cli/reference/{portal_sections_standard_workbench.example.json → examples/portal/portal_sections_standard_workbench.example.json} +0 -0
  150. /package/skills/qingflow-cli/reference/{_batch_schema_complex.json → examples/schema/_batch_schema_complex.json} +0 -0
  151. /package/skills/qingflow-cli/reference/{_batch_schema_scalar.json → examples/schema/_batch_schema_scalar.json} +0 -0
  152. /package/skills/qingflow-cli/reference/{schema_add_fields_minimal.example.json → examples/schema/schema_add_fields_minimal.example.json} +0 -0
  153. /package/skills/qingflow-cli/reference/{schema_apply_add_fields_all_types.json → examples/schema/schema_apply_add_fields_all_types.json} +0 -0
  154. /package/skills/qingflow-cli/reference/{views_upsert_table_minimal.example.json → examples/views/views_upsert_table_minimal.example.json} +0 -0
@@ -5,7 +5,7 @@ import json
5
5
  import sys
6
6
  from typing import Any, Callable, TextIO
7
7
 
8
- from ..errors import QingflowApiError
8
+ from ..errors import QingflowApiError, backend_code_value_int, message_looks_like_invalid_token
9
9
  from ..public_surface import cli_public_tool_spec_from_namespace
10
10
  from ..response_trim import resolve_cli_tool_name, trim_error_response, trim_public_response
11
11
  from ..tools.ai_builder_tools import _attach_builder_apply_envelope
@@ -79,6 +79,23 @@ def run(
79
79
  return 2
80
80
  except SystemExit as exc:
81
81
  return int(exc.code or 0)
82
+ try:
83
+ _validate_conditional_args(args)
84
+ except _CliArgumentError as exc:
85
+ if _should_force_json_output_argv(normalized_argv):
86
+ payload = {
87
+ "category": "config",
88
+ "status": "failed",
89
+ "error_code": "ARGUMENT_ERROR",
90
+ "message": exc.message,
91
+ "details": {"usage": exc.usage.strip(), "prog": exc.prog},
92
+ }
93
+ payload = _maybe_attach_builder_apply_error_envelope_from_args(args, payload)
94
+ emit_json_result(payload, stream=out)
95
+ return 2
96
+ err.write(exc.usage)
97
+ err.write(f"{exc.prog}: error: {exc.message}\n")
98
+ return 2
82
99
  setattr(args, "_stdin", sys.stdin)
83
100
  setattr(args, "_stdout_stream", out)
84
101
  setattr(args, "_stderr_stream", err)
@@ -119,6 +136,50 @@ def run(
119
136
  return exit_code
120
137
 
121
138
 
139
+ def _handle_version(_args: argparse.Namespace, _context: CliContext) -> dict[str, Any]:
140
+ info = get_cli_version_info()
141
+ return {
142
+ "ok": True,
143
+ "status": "success",
144
+ **info,
145
+ }
146
+
147
+
148
+ def _emit_version(*, json_mode: bool, stdout: TextIO) -> int:
149
+ version = get_cli_version()
150
+ if json_mode:
151
+ emit_json_result(
152
+ {
153
+ "ok": True,
154
+ "status": "success",
155
+ **get_cli_version_info(),
156
+ },
157
+ stream=stdout,
158
+ )
159
+ else:
160
+ stdout.write(f"{version}\n")
161
+ return 0
162
+
163
+
164
+ def _validate_conditional_args(args: argparse.Namespace) -> None:
165
+ if (
166
+ getattr(args, "command", "") in {"builder", "build"}
167
+ and getattr(args, "builder_command", "") == "layout"
168
+ and getattr(args, "builder_layout_command", "") == "apply"
169
+ and not getattr(args, "apps_file", None)
170
+ and not getattr(args, "sections_file", None)
171
+ ):
172
+ raise _CliArgumentError(
173
+ prog="qingflow builder layout apply",
174
+ message="--sections-file is required",
175
+ usage="usage: qingflow builder layout apply --app-key APP_KEY --sections-file SECTIONS.json\n",
176
+ )
177
+
178
+
179
+ def _should_force_json_output_argv_for_version(argv: list[str]) -> bool:
180
+ return "--json" in argv
181
+
182
+
122
183
  def _normalize_global_args(argv: list[str]) -> list[str]:
123
184
  global_args: list[str] = []
124
185
  remaining: list[str] = []
@@ -150,35 +211,6 @@ def _normalize_global_args(argv: list[str]) -> list[str]:
150
211
  return global_args + remaining
151
212
 
152
213
 
153
- def _handle_version(_args: argparse.Namespace, _context: CliContext) -> dict[str, Any]:
154
- info = get_cli_version_info()
155
- return {
156
- "ok": True,
157
- "status": "success",
158
- **info,
159
- }
160
-
161
-
162
- def _emit_version(*, json_mode: bool, stdout: TextIO) -> int:
163
- version = get_cli_version()
164
- if json_mode:
165
- emit_json_result(
166
- {
167
- "ok": True,
168
- "status": "success",
169
- **get_cli_version_info(),
170
- },
171
- stream=stdout,
172
- )
173
- else:
174
- stdout.write(f"{version}\n")
175
- return 0
176
-
177
-
178
- def _should_force_json_output_argv_for_version(argv: list[str]) -> bool:
179
- return "--json" in argv
180
-
181
-
182
214
  def _should_force_json_output(args: argparse.Namespace) -> bool:
183
215
  if bool(getattr(args, "force_json_output", False)):
184
216
  return True
@@ -249,6 +281,8 @@ def _builder_apply_operation_from_args(args: argparse.Namespace) -> str | None:
249
281
  return "app_associated_resources_apply"
250
282
  if section == "portal" and getattr(args, "builder_portal_command", "") == "apply":
251
283
  return "portal_apply"
284
+ if section == "portal" and getattr(args, "builder_portal_command", "") == "delete":
285
+ return "portal_delete"
252
286
  if section == "schema" and getattr(args, "builder_schema_command", "") == "apply":
253
287
  return "app_schema_apply"
254
288
  if section == "layout" and getattr(args, "builder_layout_command", "") == "apply":
@@ -270,7 +304,7 @@ def _builder_apply_operation_from_argv(argv: list[str]) -> str | None:
270
304
  return None
271
305
  section = tokens[1]
272
306
  action = tokens[2]
273
- if action != "apply" and not (section == "publish" and action == "verify"):
307
+ if action != "apply" and not (section == "publish" and action == "verify") and not (section == "portal" and action == "delete"):
274
308
  return None
275
309
  mapping = {
276
310
  "package": "package_apply",
@@ -285,6 +319,8 @@ def _builder_apply_operation_from_argv(argv: list[str]) -> str | None:
285
319
  "charts": "app_charts_apply",
286
320
  "publish": "app_publish_verify",
287
321
  }
322
+ if section == "portal" and action == "delete":
323
+ return "portal_delete"
288
324
  return mapping.get(section)
289
325
 
290
326
 
@@ -364,6 +400,8 @@ def _emit_error(payload: dict[str, Any], *, json_mode: bool, stdout: TextIO, std
364
400
  f"Category: {payload.get('category') or 'error'}",
365
401
  f"Message: {payload.get('message') or 'Unknown error'}",
366
402
  ]
403
+ if payload.get("error_code"):
404
+ lines.append(f"Error Code: {payload.get('error_code')}")
367
405
  if payload.get("backend_code") is not None:
368
406
  lines.append(f"Backend Code: {payload.get('backend_code')}")
369
407
  if payload.get("request_id"):
@@ -378,23 +416,69 @@ def _emit_error(payload: dict[str, Any], *, json_mode: bool, stdout: TextIO, std
378
416
 
379
417
 
380
418
  def _error_exit_code(payload: dict[str, Any]) -> int:
381
- category = str(payload.get("category") or "").lower()
382
- if category in {"auth", "workspace"}:
419
+ if str(payload.get("error_code") or "").upper() == "ARGUMENT_ERROR":
420
+ return 2
421
+ if _is_auth_or_workspace_payload(payload):
383
422
  return 3
384
423
  return 4
385
424
 
386
425
 
426
+ def _is_auth_or_workspace_payload(payload: dict[str, Any]) -> bool:
427
+ category = str(payload.get("category") or "").lower()
428
+ error_code = str(payload.get("error_code") or "").upper()
429
+ http_status = backend_code_value_int(payload.get("http_status"))
430
+ if category in {"auth", "workspace"} or error_code in {"AUTH_REQUIRED", "WORKSPACE_NOT_SELECTED"}:
431
+ return True
432
+ if http_status == 401 or message_looks_like_invalid_token(payload.get("message")):
433
+ return True
434
+ return False
435
+
436
+
387
437
  def _result_exit_code(result: dict[str, Any]) -> int:
388
438
  if not isinstance(result, dict):
389
439
  return 0
440
+ if _is_executed_nonfatal_result(result):
441
+ return 0
442
+ if _is_auth_or_workspace_payload(result):
443
+ return 3
390
444
  if result.get("ok") is False:
391
445
  return 4
392
446
  status = str(result.get("status") or "").lower()
447
+ if status == "partial_success" and result.get("ok") is not True and not _has_readback_unavailable_verification(result):
448
+ return 4
393
449
  if status in {"failed", "blocked"}:
394
450
  return 4
395
451
  return 0
396
452
 
397
453
 
454
+ def _is_executed_nonfatal_result(result: dict[str, Any]) -> bool:
455
+ status = str(result.get("status") or "").lower()
456
+ executed = bool(
457
+ result.get("write_executed")
458
+ or result.get("write_may_have_succeeded")
459
+ or result.get("delete_executed")
460
+ or result.get("action_executed")
461
+ or result.get("export_executed")
462
+ )
463
+ return executed and status in {"partial_success", "verification_failed", "running", "queued", "unknown"}
464
+
465
+
466
+ def _has_readback_unavailable_verification(result: dict[str, Any]) -> bool:
467
+ verification = result.get("verification")
468
+ if not isinstance(verification, dict):
469
+ return False
470
+ return any(
471
+ bool(verification.get(key))
472
+ for key in (
473
+ "readback_unavailable",
474
+ "readback_pending",
475
+ "metadata_unverified",
476
+ "views_read_unavailable",
477
+ "readback_before_retry",
478
+ )
479
+ )
480
+
481
+
398
482
  def _emit_cli_effective_context_notice(args: argparse.Namespace, context: CliContext, *, stream: TextIO) -> None:
399
483
  spec = cli_public_tool_spec_from_namespace(args)
400
484
  if spec is None or not spec.cli_show_effective_context:
@@ -9,11 +9,24 @@ from .json_types import JSONObject, JSONScalar
9
9
  INVALID_TOKEN_MARKERS = (
10
10
  "invalid token",
11
11
  "token invalid",
12
+ "token expired",
13
+ "expired token",
12
14
  "token失效",
13
15
  "无效token",
14
16
  "登录失效",
17
+ "登录过期",
18
+ "会话过期",
19
+ "session expired",
20
+ "session invalid",
15
21
  "login token invalid",
16
22
  "access token invalid",
23
+ "not logged in",
24
+ "not login",
25
+ "please login",
26
+ "please log in",
27
+ "未登录",
28
+ "请登录",
29
+ "重新登录",
17
30
  )
18
31
 
19
32
 
@@ -36,8 +49,7 @@ class QingflowApiError(Exception):
36
49
  return self.as_json()
37
50
 
38
51
  def looks_like_invalid_token(self) -> bool:
39
- text = self.message.lower()
40
- return any(marker in text for marker in INVALID_TOKEN_MARKERS)
52
+ return message_looks_like_invalid_token(self.message)
41
53
 
42
54
  @classmethod
43
55
  def auth_required(cls, profile: str) -> "QingflowApiError":
@@ -64,3 +76,32 @@ class QingflowApiError(Exception):
64
76
 
65
77
  def raise_tool_error(error: QingflowApiError) -> None:
66
78
  raise RuntimeError(error.as_json())
79
+
80
+
81
+ def backend_code_value_int(code: JSONScalar) -> int | None:
82
+ if isinstance(code, bool) or code is None:
83
+ return None
84
+ if isinstance(code, int):
85
+ return code
86
+ if isinstance(code, str):
87
+ text = code.strip()
88
+ if text:
89
+ try:
90
+ return int(text)
91
+ except ValueError:
92
+ return None
93
+ return None
94
+
95
+
96
+ def backend_code_int(error: QingflowApiError) -> int | None:
97
+ return backend_code_value_int(error.backend_code)
98
+
99
+
100
+ def message_looks_like_invalid_token(message: object) -> bool:
101
+ text = str(message or "").lower()
102
+ return any(marker in text for marker in INVALID_TOKEN_MARKERS)
103
+
104
+
105
+ def is_auth_like_error(error: QingflowApiError) -> bool:
106
+ category = str(error.category or "").strip().lower()
107
+ return category == "auth" or error.http_status == 401 or error.looks_like_invalid_token()
@@ -51,35 +51,40 @@ USER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
51
51
  "record_schema_get",
52
52
  cli_route=("record", "schema", "applicant"),
53
53
  mcp_public=False,
54
+ cli_public=False,
54
55
  ),
55
56
  PublicToolSpec(
56
57
  USER_DOMAIN,
57
58
  "record_browse_schema_get",
58
59
  ("record_browse_schema_get_public",),
59
60
  ("record", "schema", "browse"),
61
+ cli_show_effective_context=True,
60
62
  ),
61
63
  PublicToolSpec(
62
64
  USER_DOMAIN,
63
65
  "record_insert_schema_get",
64
66
  ("record_insert_schema_get_public",),
65
67
  ("record", "schema", "insert"),
68
+ cli_show_effective_context=True,
66
69
  ),
67
70
  PublicToolSpec(
68
71
  USER_DOMAIN,
69
72
  "record_update_schema_get",
70
73
  ("record_update_schema_get_public",),
71
74
  ("record", "schema", "update"),
75
+ cli_show_effective_context=True,
72
76
  ),
73
- PublicToolSpec(USER_DOMAIN, "record_import_schema_get", ("record_import_schema_get",), ("record", "schema", "import")),
77
+ PublicToolSpec(USER_DOMAIN, "record_import_schema_get", ("record_import_schema_get",), ("record", "schema", "import"), cli_show_effective_context=True),
74
78
  PublicToolSpec(
75
79
  USER_DOMAIN,
76
80
  "record_code_block_schema_get",
77
81
  ("record_code_block_schema_get_public",),
78
82
  ("record", "schema", "code-block"),
83
+ cli_show_effective_context=True,
79
84
  ),
80
- PublicToolSpec(USER_DOMAIN, "record_member_candidates", ("record_member_candidates",), ("record", "member-candidates")),
81
- PublicToolSpec(USER_DOMAIN, "record_department_candidates", ("record_department_candidates",), ("record", "department-candidates")),
82
- PublicToolSpec(USER_DOMAIN, "record_analyze", ("record_analyze",), ("record", "analyze"), mcp_public=False),
85
+ PublicToolSpec(USER_DOMAIN, "record_member_candidates", ("record_member_candidates",), ("record", "member-candidates"), cli_show_effective_context=True),
86
+ PublicToolSpec(USER_DOMAIN, "record_department_candidates", ("record_department_candidates",), ("record", "department-candidates"), cli_show_effective_context=True),
87
+ PublicToolSpec(USER_DOMAIN, "record_analyze", ("record_analyze",), ("record", "analyze"), mcp_public=False, cli_public=False),
83
88
  PublicToolSpec(USER_DOMAIN, "record_list", ("record_list",), ("record", "list"), cli_show_effective_context=True),
84
89
  PublicToolSpec(USER_DOMAIN, "record_access", ("record_access",), ("record", "access"), cli_show_effective_context=True),
85
90
  PublicToolSpec(USER_DOMAIN, "record_get", ("record_get_public",), ("record", "get"), cli_show_effective_context=True),
@@ -87,11 +92,11 @@ USER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
87
92
  PublicToolSpec(USER_DOMAIN, "record_insert", ("record_insert_public",), ("record", "insert"), cli_show_effective_context=True, cli_context_write=True),
88
93
  PublicToolSpec(USER_DOMAIN, "record_update", ("record_update_public",), ("record", "update"), cli_show_effective_context=True, cli_context_write=True),
89
94
  PublicToolSpec(USER_DOMAIN, "record_delete", ("record_delete_public",), ("record", "delete"), cli_show_effective_context=True, cli_context_write=True),
90
- PublicToolSpec(USER_DOMAIN, "record_import_template_get", ("record_import_template_get",), ("import", "template")),
91
- PublicToolSpec(USER_DOMAIN, "record_import_verify", ("record_import_verify",), ("import", "verify")),
95
+ PublicToolSpec(USER_DOMAIN, "record_import_template_get", ("record_import_template_get",), ("import", "template"), cli_show_effective_context=True),
96
+ PublicToolSpec(USER_DOMAIN, "record_import_verify", ("record_import_verify",), ("import", "verify"), cli_show_effective_context=True),
92
97
  PublicToolSpec(USER_DOMAIN, "record_import_repair_local", ("record_import_repair_local",), ("import", "repair")),
93
- PublicToolSpec(USER_DOMAIN, "record_import_start", ("record_import_start",), ("import", "start")),
94
- PublicToolSpec(USER_DOMAIN, "record_import_status_get", ("record_import_status_get",), ("import", "status")),
98
+ PublicToolSpec(USER_DOMAIN, "record_import_start", ("record_import_start",), ("import", "start"), cli_show_effective_context=True, cli_context_write=True),
99
+ PublicToolSpec(USER_DOMAIN, "record_import_status_get", ("record_import_status_get",), ("import", "status"), cli_show_effective_context=True),
95
100
  PublicToolSpec(USER_DOMAIN, "record_export_start", ("record_export_start",), ("export", "start"), cli_show_effective_context=True),
96
101
  PublicToolSpec(USER_DOMAIN, "record_export_status_get", ("record_export_status_get",), ("export", "status"), cli_show_effective_context=True),
97
102
  PublicToolSpec(USER_DOMAIN, "record_export_get", ("record_export_get",), ("export", "get"), cli_show_effective_context=True),
@@ -109,12 +114,12 @@ USER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
109
114
  ),
110
115
  PublicToolSpec(USER_DOMAIN, "task_workflow_log_get", ("task_workflow_log_get",), ("task", "log"), cli_show_effective_context=True),
111
116
  PublicToolSpec(USER_DOMAIN, "directory_search", ("directory_search",), cli_public=False),
112
- PublicToolSpec(USER_DOMAIN, "directory_list_internal_users", ("directory_list_internal_users",), cli_public=False),
113
- PublicToolSpec(USER_DOMAIN, "directory_list_all_internal_users", ("directory_list_all_internal_users",), cli_public=False),
114
- PublicToolSpec(USER_DOMAIN, "directory_list_internal_departments", ("directory_list_internal_departments",), cli_public=False),
115
- PublicToolSpec(USER_DOMAIN, "directory_list_all_departments", ("directory_list_all_departments",), cli_public=False),
116
- PublicToolSpec(USER_DOMAIN, "directory_list_sub_departments", ("directory_list_sub_departments",), cli_public=False),
117
- PublicToolSpec(USER_DOMAIN, "directory_list_external_members", ("directory_list_external_members",), cli_public=False),
117
+ PublicToolSpec(USER_DOMAIN, "directory_list_internal_users", ("directory_list_internal_users",), mcp_public=False, cli_public=False),
118
+ PublicToolSpec(USER_DOMAIN, "directory_list_all_internal_users", ("directory_list_all_internal_users",), mcp_public=False, cli_public=False),
119
+ PublicToolSpec(USER_DOMAIN, "directory_list_internal_departments", ("directory_list_internal_departments",), mcp_public=False, cli_public=False),
120
+ PublicToolSpec(USER_DOMAIN, "directory_list_all_departments", ("directory_list_all_departments",), mcp_public=False, cli_public=False),
121
+ PublicToolSpec(USER_DOMAIN, "directory_list_sub_departments", ("directory_list_sub_departments",), mcp_public=False, cli_public=False),
122
+ PublicToolSpec(USER_DOMAIN, "directory_list_external_members", ("directory_list_external_members",), mcp_public=False, cli_public=False),
118
123
  )
119
124
 
120
125
 
@@ -138,9 +143,7 @@ BUILDER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
138
143
  PublicToolSpec(BUILDER_DOMAIN, "app_release_edit_lock_if_mine", ("app_release_edit_lock_if_mine",), ("builder", "app", "release-edit-lock-if-mine"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
139
144
  PublicToolSpec(BUILDER_DOMAIN, "app_resolve", ("app_resolve",), ("builder", "app", "resolve"), has_contract=True, cli_show_effective_context=True),
140
145
  PublicToolSpec(BUILDER_DOMAIN, "button_style_catalog_get", ("button_style_catalog_get",), ("builder", "button", "catalog"), has_contract=True, cli_show_effective_context=True),
141
- PublicToolSpec(BUILDER_DOMAIN, "app_get_buttons", ("app_get_buttons",), ("builder", "button", "get"), has_contract=True, cli_show_effective_context=True),
142
146
  PublicToolSpec(BUILDER_DOMAIN, "app_custom_buttons_apply", ("app_custom_buttons_apply",), ("builder", "button", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
143
- PublicToolSpec(BUILDER_DOMAIN, "app_get_associated_resources", ("app_get_associated_resources",), ("builder", "associated-resource", "get"), has_contract=True, cli_show_effective_context=True),
144
147
  PublicToolSpec(BUILDER_DOMAIN, "app_associated_resources_apply", ("app_associated_resources_apply",), ("builder", "associated-resource", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
145
148
  PublicToolSpec(BUILDER_DOMAIN, "app_get", ("app_get",), ("builder", "app", "get", "summary"), has_contract=True, cli_show_effective_context=True),
146
149
  PublicToolSpec(BUILDER_DOMAIN, "app_get_fields", ("app_get_fields",), ("builder", "app", "get", "fields"), has_contract=True, cli_show_effective_context=True),
@@ -149,6 +152,10 @@ BUILDER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
149
152
  PublicToolSpec(BUILDER_DOMAIN, "app_get_views", ("app_get_views",), ("builder", "app", "get", "views"), has_contract=True, cli_show_effective_context=True),
150
153
  PublicToolSpec(BUILDER_DOMAIN, "app_get_flow", ("app_get_flow",), ("builder", "app", "get", "flow"), has_contract=True, cli_show_effective_context=True),
151
154
  PublicToolSpec(BUILDER_DOMAIN, "app_get_charts", ("app_get_charts",), ("builder", "app", "get", "charts"), has_contract=True, cli_show_effective_context=True),
155
+ PublicToolSpec(BUILDER_DOMAIN, "app_get_buttons", ("app_get_buttons",), ("builder", "app", "get", "buttons"), has_contract=True, cli_show_effective_context=True),
156
+ PublicToolSpec(BUILDER_DOMAIN, "app_get_associated_resources", ("app_get_associated_resources",), ("builder", "app", "get", "associated-resources"), has_contract=True, cli_show_effective_context=True),
157
+ PublicToolSpec(BUILDER_DOMAIN, "app_get_buttons", ("app_get_buttons",), ("builder", "button", "get"), has_contract=True, cli_show_effective_context=True),
158
+ PublicToolSpec(BUILDER_DOMAIN, "app_get_associated_resources", ("app_get_associated_resources",), ("builder", "associated-resource", "get"), has_contract=True, cli_show_effective_context=True),
152
159
  PublicToolSpec(BUILDER_DOMAIN, "portal_list", ("portal_list",), ("builder", "portal", "list"), has_contract=True, cli_show_effective_context=True),
153
160
  PublicToolSpec(BUILDER_DOMAIN, "portal_get", ("portal_get",), ("builder", "portal", "get"), has_contract=True, cli_show_effective_context=True),
154
161
  PublicToolSpec(BUILDER_DOMAIN, "view_get", ("view_get",), ("builder", "view", "get"), has_contract=True, cli_show_effective_context=True),
@@ -161,7 +168,8 @@ BUILDER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
161
168
  PublicToolSpec(BUILDER_DOMAIN, "app_views_apply", ("app_views_apply",), ("builder", "views", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
162
169
  PublicToolSpec(BUILDER_DOMAIN, "app_charts_apply", ("app_charts_apply",), ("builder", "charts", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
163
170
  PublicToolSpec(BUILDER_DOMAIN, "portal_apply", ("portal_apply",), ("builder", "portal", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
164
- PublicToolSpec(BUILDER_DOMAIN, "app_publish_verify", ("app_publish_verify",), ("builder", "publish", "verify"), has_contract=True, cli_show_effective_context=True),
171
+ PublicToolSpec(BUILDER_DOMAIN, "portal_delete", ("portal_delete",), ("builder", "portal", "delete"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
172
+ PublicToolSpec(BUILDER_DOMAIN, "app_publish_verify", ("app_publish_verify",), ("builder", "publish", "verify"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
165
173
  )
166
174
 
167
175
 
@@ -239,6 +247,7 @@ def cli_route_from_namespace(args: Namespace) -> tuple[str, ...] | None:
239
247
  builder_command = getattr(args, "builder_command", None)
240
248
  if not isinstance(builder_command, str) or not builder_command:
241
249
  return None
250
+ builder_command = "associated-resource" if builder_command == "associated-resources" else builder_command
242
251
  if builder_command == "contract":
243
252
  return ("builder", "contract")
244
253
  if builder_command == "app":
@@ -47,7 +47,6 @@ COMMON_ERROR_DROP_TOP = {
47
47
  "allowed_values",
48
48
  "missing_fields",
49
49
  "noop",
50
- "ok",
51
50
  }
52
51
 
53
52
  SUCCESS_POLICY_BY_TOOL: dict[str, TransformFn] = {}
@@ -58,11 +57,18 @@ def trim_public_response(tool_name: str | None, payload: dict[str, Any]) -> dict
58
57
  return payload
59
58
  if _looks_like_failure_payload(payload):
60
59
  status = str(payload.get("status") or "").lower()
61
- if tool_name in {"user:record_insert", "user:record_update", "user:record_delete"} and status in {
62
- "blocked",
63
- "needs_confirmation",
64
- "partial_success",
65
- }:
60
+ if _is_executed_nonfatal_payload(payload) or (
61
+ tool_name in {"user:record_insert", "user:record_update", "user:record_delete"}
62
+ and status in {
63
+ "blocked",
64
+ "needs_confirmation",
65
+ "partial_success",
66
+ }
67
+ ) or (
68
+ tool_name == "user:record_import_verify"
69
+ and status == "failed"
70
+ and "can_import" in payload
71
+ ):
66
72
  return trim_success_response(tool_name, payload)
67
73
  return _trim_returned_failure(payload)
68
74
  return trim_success_response(tool_name, payload)
@@ -94,7 +100,7 @@ def trim_error_response(payload: dict[str, Any]) -> dict[str, Any]:
94
100
  details = trimmed.get("details")
95
101
  if isinstance(details, dict):
96
102
  preserved = {}
97
- for key in ("blocking_issues", "compiled_match_rules"):
103
+ for key in ("blocking_issues", "compiled_match_rules", "issues", "expected_shape"):
98
104
  if key in details:
99
105
  preserved[key] = details.get(key)
100
106
  compact_details = _compact_scalar_dict(details)
@@ -151,12 +157,26 @@ def _parse_runtime_error_payload(exc: RuntimeError) -> dict[str, Any] | None:
151
157
 
152
158
 
153
159
  def _looks_like_failure_payload(payload: dict[str, Any]) -> bool:
160
+ if _is_executed_nonfatal_payload(payload):
161
+ return False
154
162
  if payload.get("ok") is False:
155
163
  return True
156
164
  status = str(payload.get("status") or "").lower()
157
165
  return status in {"failed", "blocked"}
158
166
 
159
167
 
168
+ def _is_executed_nonfatal_payload(payload: dict[str, Any]) -> bool:
169
+ status = str(payload.get("status") or "").lower()
170
+ executed = bool(
171
+ payload.get("write_executed")
172
+ or payload.get("write_may_have_succeeded")
173
+ or payload.get("delete_executed")
174
+ or payload.get("action_executed")
175
+ or payload.get("export_executed")
176
+ )
177
+ return executed and status in {"partial_success", "verification_failed", "running", "queued", "unknown"}
178
+
179
+
160
180
  def _trim_returned_failure(payload: dict[str, Any]) -> dict[str, Any]:
161
181
  trimmed = deepcopy(payload)
162
182
  _drop_top_keys(trimmed, COMMON_ERROR_DROP_TOP)
@@ -164,7 +184,7 @@ def _trim_returned_failure(payload: dict[str, Any]) -> dict[str, Any]:
164
184
  details = trimmed.get("details")
165
185
  if isinstance(details, dict):
166
186
  preserved = {}
167
- for key in ("blocking_issues", "compiled_match_rules"):
187
+ for key in ("blocking_issues", "compiled_match_rules", "issues", "expected_shape"):
168
188
  if key in details:
169
189
  preserved[key] = details.get(key)
170
190
  compact_details = _compact_scalar_dict(details)
@@ -325,15 +345,22 @@ def _trim_import_schema(payload: JSONObject) -> None:
325
345
  columns = [item for item in payload.get("expected_columns", []) if isinstance(item, dict)]
326
346
  if columns is not None:
327
347
  payload["columns"] = [_compact_import_column(item) for item in columns]
348
+ import_capability = payload.get("import_capability") if isinstance(payload.get("import_capability"), dict) else {}
349
+ if import_capability:
350
+ verification = payload.get("verification") if isinstance(payload.get("verification"), dict) else {}
351
+ if not isinstance(payload.get("verification"), dict):
352
+ payload["verification"] = verification
353
+ verification.setdefault("import_auth_source", import_capability.get("auth_source"))
354
+ verification.setdefault("import_capability_can_import", import_capability.get("can_import"))
328
355
  payload.pop("expected_columns", None)
329
356
  payload.pop("schema_fingerprint", None)
330
357
  payload.pop("import_capability", None)
331
358
  payload.pop("request_route", None)
332
- payload.pop("verification", None)
333
359
 
334
360
  if _looks_like_import_verify(payload):
335
361
  _trim_import_verify_payload(payload)
336
362
  return
363
+ payload.pop("verification", None)
337
364
  if "applied_repairs" in payload or "repaired_file_path" in payload:
338
365
  _trim_import_repair_payload(payload)
339
366
  return
@@ -394,10 +421,14 @@ def _trim_record_write(payload: JSONObject) -> None:
394
421
  data.pop("normalized_payload", None)
395
422
  data.pop("human_review", None)
396
423
  data.pop("action", None)
397
- for key in ("update_route", "tried_routes"):
398
- value = payload.get(key)
399
- if value not in (None, [], {}, ""):
400
- data[key] = value
424
+ if payload.get("status") == "success":
425
+ data.pop("tried_routes", None)
426
+ update_route = payload.get("update_route")
427
+ if update_route not in (None, [], {}, ""):
428
+ data["update_route"] = update_route
429
+ tried_routes = payload.get("tried_routes")
430
+ if payload.get("status") != "success" and tried_routes not in (None, [], {}, ""):
431
+ data["tried_routes"] = tried_routes
401
432
  resource = _compact_record_resource(data.get("resource"))
402
433
  if resource:
403
434
  data["resource"] = resource
@@ -441,6 +472,7 @@ def _trim_record_write_batch(payload: JSONObject, data: JSONObject) -> None:
441
472
  "record_id",
442
473
  "apply_id",
443
474
  "write_executed",
475
+ "write_succeeded",
444
476
  "verification_status",
445
477
  "safe_to_retry",
446
478
  "update_route",
@@ -887,6 +919,12 @@ def _compact_import_column(item: dict[str, Any]) -> dict[str, Any]:
887
919
  compact["options"] = options
888
920
  if bool(item.get("accepts_natural_input")):
889
921
  compact["accepts_natural_input"] = True
922
+ import_value_format = item.get("import_value_format")
923
+ if isinstance(import_value_format, str) and import_value_format:
924
+ compact["import_value_format"] = import_value_format
925
+ format_hint = item.get("format_hint")
926
+ if isinstance(format_hint, str) and format_hint:
927
+ compact["format_hint"] = format_hint
890
928
  if bool(item.get("requires_upload")):
891
929
  compact["requires_upload"] = True
892
930
  target_app_key = item.get("target_app_key")
@@ -902,13 +940,37 @@ def _compact_import_column(item: dict[str, Any]) -> dict[str, Any]:
902
940
 
903
941
 
904
942
  def _looks_like_import_verify(payload: JSONObject) -> bool:
905
- return "verification_id" in payload and "can_import" in payload
943
+ if "verification_id" in payload and "can_import" in payload:
944
+ return True
945
+ return "can_import" in payload and (
946
+ "error_code" in payload
947
+ or "issues" in payload
948
+ or "file_path" in payload
949
+ )
906
950
 
907
951
 
908
952
  def _trim_import_verify_payload(payload: JSONObject) -> None:
909
953
  issues = payload.get("issues") if isinstance(payload.get("issues"), list) else []
910
954
  issue_summary = _summarize_import_issues(issues)
911
955
  payload["issue_summary"] = issue_summary
956
+ verification = payload.get("verification") if isinstance(payload.get("verification"), dict) else {}
957
+ compact_verification = {
958
+ key: verification.get(key)
959
+ for key in (
960
+ "import_auth_prechecked",
961
+ "import_auth_precheck_passed",
962
+ "import_auth_source",
963
+ "import_capability_can_import",
964
+ "local_precheck_passed",
965
+ "backend_verification_passed",
966
+ "auto_normalized",
967
+ )
968
+ if key in verification
969
+ }
970
+ if compact_verification:
971
+ payload["verification"] = compact_verification
972
+ else:
973
+ payload.pop("verification", None)
912
974
  columns = payload.get("columns")
913
975
  if "expected_columns" not in payload and isinstance(columns, list):
914
976
  payload["expected_columns"] = columns
@@ -1004,13 +1066,14 @@ def _trim_feedback(payload: JSONObject) -> None:
1004
1066
 
1005
1067
 
1006
1068
  def _trim_builder_envelope(payload: JSONObject) -> None:
1007
- if str(payload.get("status") or "").lower() == "success":
1069
+ if str(payload.get("status") or "").lower() in {"success", "partial_success", "verification_failed"}:
1008
1070
  details = payload.get("details")
1009
1071
  if isinstance(details, dict):
1010
1072
  _drop_deep_keys(details, {"request_route", "base_url", "normalized_args", "suggested_next_call", "transport", "response", "body", "raw"})
1011
1073
  preserved = {}
1012
- if isinstance(details.get("compiled_match_rules"), dict):
1013
- preserved["compiled_match_rules"] = details.get("compiled_match_rules")
1074
+ for key in ("compiled_match_rules", "issues", "expected_shape"):
1075
+ if key in details:
1076
+ preserved[key] = details.get(key)
1014
1077
  compact = _compact_scalar_dict(details)
1015
1078
  compact.update(preserved)
1016
1079
  if compact:
@@ -1142,6 +1205,7 @@ _register_policy(
1142
1205
  "app_views_apply",
1143
1206
  "app_charts_apply",
1144
1207
  "portal_apply",
1208
+ "portal_delete",
1145
1209
  "app_publish_verify",
1146
1210
  ),
1147
1211
  _trim_builder_list_like,