mammoth-cli 2.0.73__tar.gz → 2.0.75__tar.gz

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 (230) hide show
  1. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/PKG-INFO +2 -2
  2. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/__init__.py +1 -1
  3. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +1 -1
  4. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/schema.py +5 -2
  5. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/view.py +294 -11
  6. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/view_ops.py +13 -3
  7. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/runtime/verify.py +83 -33
  8. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/pyproject.toml +2 -2
  9. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/automation.yaml +3 -1
  10. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/view.yaml +4 -1
  11. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/sdk-catalog.source.yaml +7 -1
  12. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/.gitignore +0 -0
  13. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/LICENSE +0 -0
  14. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/README.md +0 -0
  15. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/__main__.py +0 -0
  16. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/app.py +0 -0
  17. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/about-mammoth.md +0 -0
  18. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +0 -0
  19. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities-misc.md +0 -0
  20. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities.md +0 -0
  21. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/command-index.md +0 -0
  22. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/activity.md +0 -0
  23. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/addon.md +0 -0
  24. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/agent.md +0 -0
  25. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/ai.md +0 -0
  26. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/annotation.md +0 -0
  27. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/auth.md +0 -0
  28. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/automation.md +0 -0
  29. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/batch.md +0 -0
  30. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/billing.md +0 -0
  31. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/browse.md +0 -0
  32. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/capability.md +0 -0
  33. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/client-app.md +0 -0
  34. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/completion.md +0 -0
  35. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/config.md +0 -0
  36. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/connector.md +0 -0
  37. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/context.md +0 -0
  38. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-qa.md +0 -0
  39. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard.md +0 -0
  40. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/data-app.md +0 -0
  41. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dataset.md +0 -0
  42. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/doctor.md +0 -0
  43. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/external-key.md +0 -0
  44. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/file.md +0 -0
  45. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/folder.md +0 -0
  46. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/job.md +0 -0
  47. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/log.md +0 -0
  48. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/notification.md +0 -0
  49. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/parameter.md +0 -0
  50. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/project.md +0 -0
  51. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/report.md +0 -0
  52. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schedule.md +0 -0
  53. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schema.md +0 -0
  54. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/skill.md +0 -0
  55. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/snippet.md +0 -0
  56. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/support.md +0 -0
  57. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/template.md +0 -0
  58. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/trash.md +0 -0
  59. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/upgrade.md +0 -0
  60. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/user.md +0 -0
  61. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/version.md +0 -0
  62. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view-transform.md +0 -0
  63. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view.md +0 -0
  64. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/webhook.md +0 -0
  65. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workflow.md +0 -0
  66. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workspace.md +0 -0
  67. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/handoff.md +0 -0
  68. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md +0 -0
  69. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +0 -0
  70. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +0 -0
  71. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/operations.md +0 -0
  72. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/auth-scope.md +0 -0
  73. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/cleanup.md +0 -0
  74. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/dashboards.md +0 -0
  75. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/end-to-end.md +0 -0
  76. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/exports.md +0 -0
  77. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/index.md +0 -0
  78. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/need-action.md +0 -0
  79. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/resources.md +0 -0
  80. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/scheduling.md +0 -0
  81. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/transforms.md +0 -0
  82. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/typed-etl-discovery.md +0 -0
  83. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +0 -0
  84. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/report-checklist.md +0 -0
  85. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/retention.md +0 -0
  86. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +0 -0
  87. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/bundled_skill/mammoth-cli/references/task-start.md +0 -0
  88. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/__init__.py +0 -0
  89. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/activity.py +0 -0
  90. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/addon.py +0 -0
  91. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/agent.py +0 -0
  92. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/ai.py +0 -0
  93. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/annotation.py +0 -0
  94. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/auth.py +0 -0
  95. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/automation.py +0 -0
  96. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/batch.py +0 -0
  97. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/billing.py +0 -0
  98. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/browse.py +0 -0
  99. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/capability.py +0 -0
  100. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/client_app.py +0 -0
  101. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/completion.py +0 -0
  102. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/config.py +0 -0
  103. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/connector.py +0 -0
  104. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/context.py +0 -0
  105. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/dashboard.py +0 -0
  106. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/data_app.py +0 -0
  107. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/dataset.py +0 -0
  108. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/doctor.py +0 -0
  109. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/external_key.py +0 -0
  110. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/file.py +0 -0
  111. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/folder.py +0 -0
  112. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/job.py +0 -0
  113. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/notification.py +0 -0
  114. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/parameter.py +0 -0
  115. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/project.py +0 -0
  116. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/registry.py +0 -0
  117. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/report.py +0 -0
  118. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/schedule.py +0 -0
  119. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/skill.py +0 -0
  120. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/snippet.py +0 -0
  121. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/support.py +0 -0
  122. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/template.py +0 -0
  123. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/trash.py +0 -0
  124. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/upgrade.py +0 -0
  125. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/user.py +0 -0
  126. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/webhook.py +0 -0
  127. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/workflow.py +0 -0
  128. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/commands/workspace.py +0 -0
  129. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/context/__init__.py +0 -0
  130. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/context/credentials.py +0 -0
  131. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/context/endpoint.py +0 -0
  132. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/context/profiles.py +0 -0
  133. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/context/resolver.py +0 -0
  134. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/contracts/__init__.py +0 -0
  135. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/contracts/auth.py +0 -0
  136. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/embed.py +0 -0
  137. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/errors/__init__.py +0 -0
  138. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/errors/envelope.py +0 -0
  139. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/manifest/__init__.py +0 -0
  140. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/manifest/loader.py +0 -0
  141. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/messages/__init__.py +0 -0
  142. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/output/__init__.py +0 -0
  143. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/output/envelope.py +0 -0
  144. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/output/normalize.py +0 -0
  145. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/output/policy.py +0 -0
  146. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/output/progress.py +0 -0
  147. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/output/render.py +0 -0
  148. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/py.typed +0 -0
  149. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/runtime/__init__.py +0 -0
  150. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/runtime/confirm.py +0 -0
  151. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/runtime/dryrun.py +0 -0
  152. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/runtime/embedded.py +0 -0
  153. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/runtime/executor.py +0 -0
  154. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/runtime/input_loader.py +0 -0
  155. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/runtime/invocation.py +0 -0
  156. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/runtime/options.py +0 -0
  157. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/runtime/parents.py +0 -0
  158. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/runtime/runlog.py +0 -0
  159. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/runtime/session.py +0 -0
  160. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/runtime/strict.py +0 -0
  161. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/runtime/updates.py +0 -0
  162. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/runtime/validate.py +0 -0
  163. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/__init__.py +0 -0
  164. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/argspec.py +0 -0
  165. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/coerce.py +0 -0
  166. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/command_contract.py +0 -0
  167. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/conditions.py +0 -0
  168. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/dashboard_pages.py +0 -0
  169. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/dashboard_review.py +0 -0
  170. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/data_quality.py +0 -0
  171. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/dispatch.py +0 -0
  172. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/factory.py +0 -0
  173. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/input_fields.py +0 -0
  174. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/mapping.py +0 -0
  175. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/openapi_types.py +0 -0
  176. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/positionals.py +0 -0
  177. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/protocol.py +0 -0
  178. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/sdk_service.py +0 -0
  179. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/testing.py +0 -0
  180. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/services/type_system.py +0 -0
  181. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/skills/__init__.py +0 -0
  182. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/skills/installer.py +0 -0
  183. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/skills/steering.py +0 -0
  184. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/mammoth_cli/testing.py +0 -0
  185. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/activity.yaml +0 -0
  186. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/addon.yaml +0 -0
  187. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/agent.yaml +0 -0
  188. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/ai.yaml +0 -0
  189. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/annotation.yaml +0 -0
  190. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/auth.yaml +0 -0
  191. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/batch.yaml +0 -0
  192. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/billing.yaml +0 -0
  193. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/browse.yaml +0 -0
  194. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/capability.yaml +0 -0
  195. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/client-app.yaml +0 -0
  196. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/completion.yaml +0 -0
  197. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/config.yaml +0 -0
  198. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/connector.yaml +0 -0
  199. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/context.yaml +0 -0
  200. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/dashboard.yaml +0 -0
  201. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/data-app.yaml +0 -0
  202. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/dataset.yaml +0 -0
  203. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/doctor.yaml +0 -0
  204. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/external-key.yaml +0 -0
  205. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/file.yaml +0 -0
  206. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/folder.yaml +0 -0
  207. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/job.yaml +0 -0
  208. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/log.yaml +0 -0
  209. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/notification.yaml +0 -0
  210. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/parameter.yaml +0 -0
  211. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/project.yaml +0 -0
  212. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/report.yaml +0 -0
  213. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/schedule.yaml +0 -0
  214. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/schema.yaml +0 -0
  215. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/skill.yaml +0 -0
  216. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/snippet.yaml +0 -0
  217. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/support.yaml +0 -0
  218. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/template.yaml +0 -0
  219. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/trash.yaml +0 -0
  220. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/upgrade.yaml +0 -0
  221. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/user.yaml +0 -0
  222. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/version.yaml +0 -0
  223. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/webhook.yaml +0 -0
  224. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/workflow.yaml +0 -0
  225. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/commands/workspace.yaml +0 -0
  226. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/openapi-operations.yaml +0 -0
  227. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/schema-v1.json +0 -0
  228. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/manifests/sdk-methods.yaml +0 -0
  229. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/openapi/metadata.json +0 -0
  230. {mammoth_cli-2.0.73 → mammoth_cli-2.0.75}/spec/openapi/openapi.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mammoth-cli
3
- Version: 2.0.73
3
+ Version: 2.0.75
4
4
  Summary: Command-line interface for the Mammoth Analytics platform
5
5
  Project-URL: homepage, https://mammoth.io
6
6
  Project-URL: repository, https://github.com/EdgeMetric/mammothsdk
@@ -17,7 +17,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
17
  Requires-Python: <3.15,>=3.12
18
18
  Requires-Dist: jsonschema>=4.20
19
19
  Requires-Dist: keyring<26,>=25.7
20
- Requires-Dist: mammoth-io<0.8,>=0.7.33
20
+ Requires-Dist: mammoth-io<0.8,>=0.7.34
21
21
  Requires-Dist: platformdirs<5,>=4.10
22
22
  Requires-Dist: pydantic<3,>=2.12.5
23
23
  Requires-Dist: pyyaml<7,>=6
@@ -6,7 +6,7 @@ not implement a second HTTP client and does not call private SDK members.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- __version__ = "2.0.73"
9
+ __version__ = "2.0.75"
10
10
 
11
11
  # The versioned machine output envelope contract.
12
12
  SCHEMA_VERSION = 1
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: mammoth-cli
3
- version: 2.0.73
3
+ version: 2.0.75
4
4
  description: "Use Mammoth Analytics from a terminal: install or authenticate the CLI, discover its live command contract, and safely manage projects, data, views, pipelines (join, merge, pivot, filter, clean), dashboards, exports, and handoffs."
5
5
  ---
6
6
 
@@ -222,7 +222,10 @@ _COMMAND_DISCOVERY_PURPOSES = {
222
222
  "join blend merge combine enrich match matching key keys rows add columns from "
223
223
  "another second view views dataset datasets table tables vlookup"
224
224
  ),
225
- "view.transform.json-extract": "json extract parse nested fields keys into columns",
225
+ "view.transform.json-extract": (
226
+ "json extract parse nested fields keys into columns list rows item index "
227
+ "one row per element explode array"
228
+ ),
226
229
  "view.transform.limit-rows": "limit top bottom first last n rows head",
227
230
  "view.transform.lookup": (
228
231
  "lookup look up vlookup reference table map code to name enrich one value "
@@ -320,7 +323,7 @@ _COMMAND_DISCOVERY_PURPOSES = {
320
323
  "automation.create": (
321
324
  "schedule scheduled recurring repeat refresh rerun run every day daily week weekly "
322
325
  "hour hourly month monthly automatically trigger alert email a dataset pipeline "
323
- "retention purge old data send an emailed file attachment"
326
+ "retention purge old data send an emailed file attachment view views csv"
324
327
  ),
325
328
  "billing.subscription.update": "upgrade downgrade bigger plan change subscription tier",
326
329
  "billing.invoice.list": "past bills billed billing history invoices payments receipts so far",
@@ -182,6 +182,29 @@ def _resolve_dataset_id(
182
182
  return dataset_id
183
183
 
184
184
 
185
+ def _resolve_dataset_id_for_settle(
186
+ service: Any,
187
+ invocation: Invocation,
188
+ view_id: int,
189
+ document: dict[str, Any],
190
+ dataset_index: int,
191
+ ) -> int | None:
192
+ """Best-effort :func:`_resolve_dataset_id`, for the settle step only.
193
+
194
+ The write this settles has already happened by the time this runs; a
195
+ parent that ``_resolve_dataset_id`` cannot resolve raises (these are
196
+ mutation commands, so it refuses project-wide discovery) -- that must
197
+ never surface as an exception past a write that already succeeded. A
198
+ ``None`` here leaves the settle step unable to run, which
199
+ ``_settle_async_view_write``/verify.py report as unverified rather than
200
+ guessing "done".
201
+ """
202
+ try:
203
+ return _resolve_dataset_id(service, invocation, view_id, document, dataset_index)
204
+ except CliError:
205
+ return None
206
+
207
+
185
208
  def _profile_name(invocation: Invocation) -> str:
186
209
  return invocation.profile or profiles.get_selected()
187
210
 
@@ -1928,6 +1951,12 @@ def view_pipeline_rerun(invocation: Invocation) -> HandlerResult:
1928
1951
  _forward_optional(document, kwargs, ("from_sequence", "dataset_id"))
1929
1952
  with open_service(invocation) as (service, auth):
1930
1953
  data = service.call(_symbol(invocation), **kwargs)
1954
+ dataset_id = kwargs.get("dataset_id")
1955
+ if dataset_id is None:
1956
+ dataset_id = _resolve_dataset_id_for_settle(
1957
+ service, invocation, dataview_id, document, 1
1958
+ )
1959
+ data = _settle_async_view_write(service, dataset_id, dataview_id, invocation.project, data)
1931
1960
  return data, _meta(invocation, auth.workspace_id, None)
1932
1961
 
1933
1962
 
@@ -1982,6 +2011,12 @@ def view_task_delete(invocation: Invocation) -> HandlerResult:
1982
2011
  _forward_optional(document, kwargs, ("dataset_id",))
1983
2012
  with open_service(invocation) as (service, auth):
1984
2013
  data = service.call(_symbol(invocation), **kwargs)
2014
+ dataset_id = kwargs.get("dataset_id")
2015
+ if dataset_id is None:
2016
+ dataset_id = _resolve_dataset_id_for_settle(
2017
+ service, invocation, dataview_id, document, 2
2018
+ )
2019
+ data = _settle_async_view_write(service, dataset_id, dataview_id, invocation.project, data)
1985
2020
  return data, _meta(invocation, auth.workspace_id, None)
1986
2021
 
1987
2022
 
@@ -2036,6 +2071,12 @@ def view_task_update(invocation: Invocation) -> HandlerResult:
2036
2071
  _forward_optional(document, kwargs, ("dataset_id",))
2037
2072
  with open_service(invocation) as (service, auth):
2038
2073
  data = service.call(_symbol(invocation), **kwargs)
2074
+ dataset_id = kwargs.get("dataset_id")
2075
+ if dataset_id is None:
2076
+ dataset_id = _resolve_dataset_id_for_settle(
2077
+ service, invocation, dataview_id, document, 2
2078
+ )
2079
+ data = _settle_async_view_write(service, dataset_id, dataview_id, invocation.project, data)
2039
2080
  return data, _meta(invocation, auth.workspace_id, None)
2040
2081
 
2041
2082
 
@@ -2433,6 +2474,7 @@ def view_export_specialized(invocation: Invocation) -> HandlerResult:
2433
2474
  details={"dataset_id": dataset_id, "target_ds_id": int(target_ds_id)},
2434
2475
  )
2435
2476
  target_view_before = None
2477
+ target_only_columns: list[str] = []
2436
2478
  if target_ds_id is not None:
2437
2479
  # Each `view export dataset` call creates its own PERSISTENT
2438
2480
  # export trigger on this view, which re-runs on every pipeline
@@ -2465,6 +2507,14 @@ def view_export_specialized(invocation: Invocation) -> HandlerResult:
2465
2507
  # rows_before is the target's own count ahead of this write, read
2466
2508
  # before the call so a later re-read can never be confused with it.
2467
2509
  target_view_before = _dataset_view_info(service, int(target_ds_id), result_project_id)
2510
+ if save_as_mode == "APPEND_TO_DS":
2511
+ target_only_columns = _reject_append_schema_mismatch(
2512
+ service,
2513
+ dataview_id,
2514
+ dataset_id,
2515
+ int(target_ds_id),
2516
+ kwargs.get("column_mapping"),
2517
+ )
2468
2518
  data = service.call_view(dataview_id, method, dataset_id=dataset_id, **kwargs)
2469
2519
  if is_dataset_route and isinstance(data, int):
2470
2520
  # The SDK returns the bare id of the dataset written to; name it, and
@@ -2489,7 +2539,7 @@ def view_export_specialized(invocation: Invocation) -> HandlerResult:
2489
2539
  # an existing target_ds_id -- this is the same race the old
2490
2540
  # "omit rather than report wrong" comment used to sidestep by
2491
2541
  # never reading the target's row count at all.
2492
- view_info = _view_info_after_settling(
2542
+ view_info, pipeline_error = _view_info_after_settling(
2493
2543
  service,
2494
2544
  int(target_ds_id) if target_ds_id is not None else data["dataset_id"],
2495
2545
  result_project_id,
@@ -2513,7 +2563,16 @@ def view_export_specialized(invocation: Invocation) -> HandlerResult:
2513
2563
  # An append is expected to grow the target; verify.py treats
2514
2564
  # a non-increase here as unverified, not just an omitted count.
2515
2565
  row_check["expected_row_increase"] = True
2566
+ if target_only_columns:
2567
+ # Allowed -- an append never has to cover every target column
2568
+ # -- but worth surfacing rather than leaving silent.
2569
+ row_check["warnings"] = [
2570
+ "target dataset has column(s) the source view does not (kept "
2571
+ "as-is): " + ", ".join(target_only_columns)
2572
+ ]
2516
2573
  data["row_check"] = row_check
2574
+ if pipeline_error is not None:
2575
+ data["pipeline_error"] = pipeline_error
2517
2576
  return data, _meta(invocation, auth.workspace_id, project_id)
2518
2577
 
2519
2578
 
@@ -2553,6 +2612,100 @@ def _existing_internal_dataset_export(
2553
2612
  return max(matches, key=lambda export: export.id or 0) if matches else None
2554
2613
 
2555
2614
 
2615
+ _DATASET_GET_SYMBOL = "mammoth.api.datasets.DatasetsAPI.get"
2616
+
2617
+
2618
+ def _target_dataset_schema_names(service: Any, target_ds_id: int) -> set[str]:
2619
+ """The target dataset's own schema column display names.
2620
+
2621
+ ``dataset.get``'s ``data_schema`` is the dataset's base schema, not any
2622
+ view's live/rendered columns -- a view's own rename task can show a
2623
+ different display name than the dataset's schema still uses (evidence:
2624
+ a target view renamed 'Cust ID' -> 'cust_id' while the dataset's
2625
+ data_schema still said 'Cust ID'). The backend's own append-schema match
2626
+ (``match_existing_target_schema``) compares against this dataset-level
2627
+ schema, not a view's, so this check must too. An unreadable or
2628
+ malformed response is never treated as "no columns" -- fail loud,
2629
+ rather than let an append write past a check that could not actually
2630
+ run. The response nests the dataset under ``dataset``, and each schema
2631
+ column carries its name as ``c_name`` (``c_id`` is the internal id).
2632
+ """
2633
+ response = service.call(_DATASET_GET_SYMBOL, dataset_id=target_ds_id)
2634
+ dataset = response.get("dataset") if isinstance(response, dict) else None
2635
+ data_schema = dataset.get("data_schema") if isinstance(dataset, dict) else None
2636
+ if not isinstance(data_schema, list):
2637
+ raise CliError(
2638
+ code="append_schema_unreadable",
2639
+ message=f"Could not read target dataset {target_ds_id}'s schema before the append.",
2640
+ hint="Read the target dataset before appending to it.",
2641
+ details={"side": "target", "dataset_id": target_ds_id},
2642
+ )
2643
+ names: set[str] = set()
2644
+ for column in data_schema:
2645
+ display_name = column.get("c_name") if isinstance(column, dict) else None
2646
+ if not isinstance(display_name, str):
2647
+ raise CliError(
2648
+ code="append_schema_unreadable",
2649
+ message=(f"Target dataset {target_ds_id}'s schema had a malformed column entry."),
2650
+ hint="Read the target dataset before appending to it.",
2651
+ details={"side": "target", "dataset_id": target_ds_id},
2652
+ )
2653
+ names.add(display_name)
2654
+ return names
2655
+
2656
+
2657
+ def _reject_append_schema_mismatch(
2658
+ service: Any,
2659
+ dataview_id: int,
2660
+ dataset_id: int | None,
2661
+ target_ds_id: int,
2662
+ column_mapping: Any,
2663
+ ) -> list[str]:
2664
+ """Refuse an ``APPEND_TO_DS`` export whose source has a column the target
2665
+ dataset's schema does not, and ``column_mapping`` does not cover.
2666
+
2667
+ An append writes into rows the target dataset already has; a source
2668
+ column with no home in the target schema and no explicit mapping would
2669
+ otherwise reach the backend as a malformed write. Checked here, before
2670
+ the call, so the refusal costs nothing and names exactly what to fix.
2671
+
2672
+ Returns the target-only column names (present in the target schema, not
2673
+ in the source, not a ``column_mapping`` destination) -- allowed, but
2674
+ worth a warning rather than silence.
2675
+ """
2676
+ source_columns = service.call_view(dataview_id, "columns", dataset_id=dataset_id)
2677
+ if not isinstance(source_columns, dict):
2678
+ raise CliError(
2679
+ code="append_schema_unreadable",
2680
+ message=f"Could not read view {dataview_id}'s columns before the append.",
2681
+ hint="Read the source view before appending from it.",
2682
+ details={"side": "source", "dataview_id": dataview_id},
2683
+ )
2684
+ target_names = _target_dataset_schema_names(service, target_ds_id)
2685
+ mapping = column_mapping if isinstance(column_mapping, dict) else {}
2686
+ # View.columns (mammoth/view.py) maps display name -> internal name; these
2687
+ # dict keys are display names, matching what the backend's own schema
2688
+ # match compares by -- never the internal ids in the dict's values.
2689
+ source_names = set(source_columns)
2690
+ source_only = sorted(source_names - target_names - set(mapping))
2691
+ target_only = sorted(target_names - source_names - set(mapping.values()))
2692
+ if source_only:
2693
+ raise CliError(
2694
+ code="append_schema_mismatch",
2695
+ message=(
2696
+ f"View {dataview_id} has column(s) the target dataset {target_ds_id} "
2697
+ f"does not: {', '.join(source_only)}."
2698
+ ),
2699
+ exit_status=EXIT_USAGE,
2700
+ hint=(
2701
+ "Map these in column_mapping (source column -> an existing target "
2702
+ "column), or rename them in the source view to match the target schema."
2703
+ ),
2704
+ details={"source_only": source_only, "target_only": target_only},
2705
+ )
2706
+ return target_only
2707
+
2708
+
2556
2709
  def _dataset_view_info(
2557
2710
  service: Any, dataset_id: int, project_id: int | None
2558
2711
  ) -> dict[str, Any] | None:
@@ -2588,8 +2741,19 @@ def _dataset_view_info(
2588
2741
  #: large.
2589
2742
  _ROW_CHECK_SETTLE_TIMEOUT = 60.0
2590
2743
 
2744
+ _PIPELINE_GET_SYMBOL = "mammoth.api.pipeline.PipelineAPI.get_pipeline"
2745
+ _TASK_LIST_SYMBOL = "mammoth.api.pipeline.PipelineAPI.list_tasks"
2746
+ _ERROR_TRANSFORM_STATUSES = {"ERROR", "REFERROR"}
2747
+ #: ``wait_for_pipeline`` polls the pipeline's ``state`` and can see it settle
2748
+ #: back to ``ready`` even though the backend's own ``execution_state`` -- a
2749
+ #: separate field -- already recorded the task that actually failed. Only
2750
+ #: ``execution_state`` is trustworthy here.
2751
+ _PIPELINE_ERROR_EXECUTION_STATES = frozenset({"runtime_error", "ref_error"})
2752
+
2591
2753
 
2592
- def wait_for_pipeline_to_settle(service: Any, dataset_id: int, view_id: int) -> None:
2754
+ def wait_for_pipeline_to_settle(
2755
+ service: Any, dataset_id: int, view_id: int
2756
+ ) -> dict[str, Any] | None:
2593
2757
  """Best-effort, bounded wait for a view's pipeline to reach a terminal state.
2594
2758
 
2595
2759
  A row-count read taken immediately after a write can catch the pipeline
@@ -2601,6 +2765,10 @@ def wait_for_pipeline_to_settle(service: Any, dataset_id: int, view_id: int) ->
2601
2765
  still reads the row count afterward; a failed wait is no reason to skip
2602
2766
  a read that might now succeed anyway, and :mod:`mammoth_cli.runtime.
2603
2767
  verify` treats an unreadable count as unverified either way.
2768
+
2769
+ Returns the pipeline's execution error (see :func:`_pipeline_execution_error`),
2770
+ if a fresh read finds one, so the caller can flag it even when the
2771
+ write's own envelope said ``status: done`` / ``pipeline_state: ready``.
2604
2772
  """
2605
2773
  try:
2606
2774
  service.call(
@@ -2611,42 +2779,157 @@ def wait_for_pipeline_to_settle(service: Any, dataset_id: int, view_id: int) ->
2611
2779
  )
2612
2780
  except Exception: # noqa: BLE001, S110 -- best effort; the write already ran
2613
2781
  pass
2782
+ return _pipeline_execution_error(service, dataset_id, view_id)
2783
+
2784
+
2785
+ def _pipeline_execution_error(service: Any, dataset_id: int, view_id: int) -> dict[str, Any] | None:
2786
+ """A settled pipeline's ``execution_state``, if it names an error.
2787
+
2788
+ A failed read here must not be silently treated as "no error" -- that
2789
+ would report the write as verified when nobody actually confirmed it
2790
+ (fail loud, no silent fallbacks). It returns ``execution_state:
2791
+ "unknown"`` with a ``read_error``, which verify.py reports as
2792
+ unverified instead. ``service.call`` maps every SDK exception to
2793
+ :class:`CliError` (see ``MammothService.call``'s contract), so that is
2794
+ the one identifiable type caught here. When a task list read cheaply
2795
+ finds the failing task, its id and ``reference_errors.error_code``
2796
+ (evidence: transform_status ERROR, error_code 7000) are added; a failed
2797
+ read there is recorded as ``task_detail_error`` rather than swallowed.
2798
+ """
2799
+ try:
2800
+ pipeline = service.call(_PIPELINE_GET_SYMBOL, dataview_id=view_id, dataset_id=dataset_id)
2801
+ except CliError as exc:
2802
+ return {"execution_state": "unknown", "read_error": f"{type(exc).__name__}: {exc}"}
2803
+ if not isinstance(pipeline, dict):
2804
+ return {
2805
+ "execution_state": "unknown",
2806
+ "read_error": f"the pipeline read returned {type(pipeline).__name__}, not a dict",
2807
+ }
2808
+ execution_state = pipeline.get("execution_state")
2809
+ if (
2810
+ not isinstance(execution_state, str)
2811
+ or execution_state.lower() not in _PIPELINE_ERROR_EXECUTION_STATES
2812
+ ):
2813
+ return None
2814
+ error: dict[str, Any] = {"execution_state": execution_state}
2815
+ task_id = pipeline.get("executing_task_id")
2816
+ if task_id is not None:
2817
+ error["task_id"] = task_id
2818
+ try:
2819
+ listing = service.call(_TASK_LIST_SYMBOL, dataview_id=view_id, dataset_id=dataset_id)
2820
+ tasks = listing.get("tasks") if isinstance(listing, dict) else None
2821
+ failing = next(
2822
+ (
2823
+ task
2824
+ for task in tasks or []
2825
+ if isinstance(task, dict)
2826
+ and task.get("transform_status") in _ERROR_TRANSFORM_STATUSES
2827
+ ),
2828
+ None,
2829
+ )
2830
+ if failing is not None:
2831
+ if failing.get("id") is not None:
2832
+ error["task_id"] = failing["id"]
2833
+ reference_errors = failing.get("reference_errors")
2834
+ error_code = (
2835
+ reference_errors.get("error_code") if isinstance(reference_errors, dict) else None
2836
+ )
2837
+ if error_code is not None:
2838
+ error["error_code"] = error_code
2839
+ except CliError as exc:
2840
+ error["task_detail_error"] = f"{type(exc).__name__}: {exc}"
2841
+ return error
2614
2842
 
2615
2843
 
2616
2844
  def wait_for_view_row_count(
2617
2845
  service: Any, dataset_id: int, view_id: int, project_id: int | None
2618
- ) -> Any:
2846
+ ) -> tuple[Any, dict[str, Any] | None]:
2619
2847
  """A view's row count once its pipeline has settled, best effort.
2620
2848
 
2621
- Returns ``None`` if the read still cannot be made; the caller (and
2849
+ Returns ``(rows_after, pipeline_error)``. ``rows_after`` is ``None`` if
2850
+ the read still cannot be made; the caller (and
2622
2851
  :mod:`mammoth_cli.runtime.verify`) must treat that as unverified, never
2623
- as a known count.
2852
+ as a known count. ``pipeline_error`` is the settled pipeline's own
2853
+ execution error (see :func:`_pipeline_execution_error`), if any.
2624
2854
  """
2625
- wait_for_pipeline_to_settle(service, dataset_id, view_id)
2855
+ pipeline_error = wait_for_pipeline_to_settle(service, dataset_id, view_id)
2626
2856
  try:
2627
2857
  info = service.call(
2628
2858
  _DATAVIEW_GET_SYMBOL, dataset_id=dataset_id, dataview_id=view_id, project_id=project_id
2629
2859
  )
2630
2860
  except Exception: # noqa: BLE001 -- best effort; the write already ran
2631
- return None
2861
+ return None, pipeline_error
2862
+ rows_after = info.get("row_count") if isinstance(info, dict) else None
2863
+ return rows_after, pipeline_error
2864
+
2865
+
2866
+ def _row_count_now(service: Any, dataset_id: int, view_id: int, project_id: int | None) -> Any:
2867
+ """A view's row count, read directly with no settle wait.
2868
+
2869
+ Raises the read's own :class:`CliError` rather than swallowing it -- the
2870
+ caller records it in ``row_check`` instead of silently reporting "no
2871
+ rows yet" for a read that never actually happened.
2872
+ """
2873
+ info = service.call(
2874
+ _DATAVIEW_GET_SYMBOL, dataset_id=dataset_id, dataview_id=view_id, project_id=project_id
2875
+ )
2632
2876
  return info.get("row_count") if isinstance(info, dict) else None
2633
2877
 
2634
2878
 
2879
+ def _settle_async_view_write(
2880
+ service: Any, dataset_id: Any, view_id: int, project_id: int | None, data: Any
2881
+ ) -> Any:
2882
+ """Turn an async view write's immediate response into a final one.
2883
+
2884
+ ``view task delete``/``update`` and ``view pipeline rerun`` can return
2885
+ ``status: processing`` right away, before the pipeline has actually run
2886
+ (evidence: ``view task delete`` -> ``{"status":"processing",
2887
+ "type_of_modification":"discard_rule"}``); a caller -- and
2888
+ :mod:`mammoth_cli.runtime.verify`, which reports ``status`` verbatim --
2889
+ must never see that as the final outcome. Only runs when ``dataset_id``
2890
+ is known, like every other settle-and-check path; an unknown parent is
2891
+ skipped rather than guessed (verify.py still catches a leftover
2892
+ ``processing`` status either way).
2893
+ """
2894
+ if dataset_id is None or not isinstance(data, dict):
2895
+ return data
2896
+ dataset_id = int(dataset_id)
2897
+ try:
2898
+ row_check: dict[str, Any] = {
2899
+ "rows_before": _row_count_now(service, dataset_id, view_id, project_id)
2900
+ }
2901
+ except CliError as exc:
2902
+ row_check = {"rows_before": None, "rows_before_error": f"{type(exc).__name__}: {exc}"}
2903
+ rows_after, pipeline_error = wait_for_view_row_count(service, dataset_id, view_id, project_id)
2904
+ row_check["rows_after"] = rows_after
2905
+ data = {**data, "row_check": row_check}
2906
+ if pipeline_error is not None:
2907
+ data["pipeline_error"] = pipeline_error
2908
+ data["status"] = "failed"
2909
+ elif data.get("status") == "processing":
2910
+ data["status"] = "done"
2911
+ return data
2912
+
2913
+
2635
2914
  def _view_info_after_settling(
2636
2915
  service: Any, dataset_id: int, project_id: int | None
2637
- ) -> dict[str, Any] | None:
2916
+ ) -> tuple[dict[str, Any] | None, dict[str, Any] | None]:
2638
2917
  """A dataset's current view info, re-read once its pipeline has settled.
2639
2918
 
2640
2919
  Used right after a `view.export.dataset` write, for a brand-new dataset
2641
2920
  or an existing ``target_ds_id`` alike: id/name/row_count all come from
2642
2921
  this settled read, not the discovery lookup that finds the view id, so
2643
2922
  a still-recomputing pipeline can never leave ``row_count`` stale.
2923
+
2924
+ Returns ``(view_info, pipeline_error)`` -- ``pipeline_error`` is the
2925
+ settled pipeline's own execution error (see :func:`_pipeline_execution_error`),
2926
+ if any.
2644
2927
  """
2645
2928
  view_info = _dataset_view_info(service, dataset_id, project_id)
2646
2929
  if view_info is None:
2647
- return None
2648
- wait_for_pipeline_to_settle(service, dataset_id, view_info["id"])
2649
- return _dataset_view_info(service, dataset_id, project_id) or view_info
2930
+ return None, None
2931
+ pipeline_error = wait_for_pipeline_to_settle(service, dataset_id, view_info["id"])
2932
+ return _dataset_view_info(service, dataset_id, project_id) or view_info, pipeline_error
2650
2933
 
2651
2934
 
2652
2935
  _DATAVIEW_LIST_SYMBOL = "mammoth.api.dataviews.DataviewsAPI.list"
@@ -280,6 +280,8 @@ def _dispatch_view(
280
280
  the view's row count is read before and after the call and added to the
281
281
  result as ``row_check``, so a caller always sees whether the write changed
282
282
  the row count -- the join path builds its own richer ``join_check`` instead.
283
+ A result staged as a draft never ran the pipeline, so no ``row_check`` is
284
+ added and there is nothing to wait for.
283
285
  """
284
286
  auto_row_check = before is None and after is None
285
287
  # ``dataset_id`` is invocation-local resource context. It is not a View
@@ -330,14 +332,22 @@ def _dispatch_view(
330
332
  )
331
333
  if after is not None and dataset_id is not None:
332
334
  data = after(service, int(dataset_id), state, data)
333
- elif auto_row_check and dataset_id is not None and isinstance(data, dict):
335
+ elif (
336
+ auto_row_check
337
+ and dataset_id is not None
338
+ and isinstance(data, dict)
339
+ and data.get("status") != "staged"
340
+ ):
334
341
  # A read taken right away can catch the pipeline still
335
342
  # recomputing and read back no row_count at all; wait for it to
336
- # settle (bounded) before trusting this one.
337
- rows_after = wait_for_view_row_count(
343
+ # settle (bounded) before trusting this one. A staged draft never
344
+ # ran the pipeline, so there is nothing to wait for or read.
345
+ rows_after, pipeline_error = wait_for_view_row_count(
338
346
  service, int(dataset_id), view_id, invocation.project
339
347
  )
340
348
  data["row_check"] = {"rows_before": rows_before, "rows_after": rows_after}
349
+ if pipeline_error is not None:
350
+ data["pipeline_error"] = pipeline_error
341
351
  return data, _meta(invocation, auth.workspace_id)
342
352
 
343
353