mammoth-cli 2.0.77__tar.gz → 2.0.78__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 (234) hide show
  1. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/PKG-INFO +1 -1
  2. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/__init__.py +1 -1
  3. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/app.py +11 -4
  4. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view-transform.md +2 -2
  5. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/dashboards.md +1 -1
  6. mammoth_cli-2.0.78/mammoth_cli/runtime/state.py +413 -0
  7. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/runtime/verify.py +51 -3
  8. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/data_quality.py +9 -1
  9. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/pyproject.toml +1 -1
  10. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/addon.yaml +24 -0
  11. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/agent.yaml +12 -0
  12. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/ai.yaml +3 -0
  13. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/annotation.yaml +19 -0
  14. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/auth.yaml +8 -0
  15. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/automation.yaml +24 -0
  16. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/batch.yaml +25 -0
  17. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/billing.yaml +39 -0
  18. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/calc.yaml +2 -1
  19. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/client-app.yaml +14 -0
  20. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/completion.yaml +5 -0
  21. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/config.yaml +5 -0
  22. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/connector.yaml +53 -0
  23. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/context.yaml +8 -0
  24. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/dashboard.yaml +413 -70
  25. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/data-app.yaml +29 -0
  26. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/dataset.yaml +50 -2
  27. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/external-key.yaml +9 -0
  28. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/file.yaml +38 -9
  29. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/folder.yaml +28 -0
  30. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/notification.yaml +16 -0
  31. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/parameter.yaml +44 -0
  32. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/project.yaml +50 -0
  33. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/schedule.yaml +14 -0
  34. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/skill.yaml +13 -0
  35. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/snippet.yaml +24 -0
  36. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/support.yaml +142 -0
  37. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/template.yaml +14 -0
  38. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/trash.yaml +8 -0
  39. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/upgrade.yaml +4 -0
  40. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/user.yaml +21 -0
  41. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/view.yaml +565 -115
  42. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/webhook.yaml +16 -0
  43. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/workflow.yaml +48 -0
  44. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/workspace.yaml +54 -0
  45. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/schema-v1.json +15 -0
  46. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/.gitignore +0 -0
  47. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/LICENSE +0 -0
  48. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/README.md +0 -0
  49. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/__main__.py +0 -0
  50. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +0 -0
  51. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/about-mammoth.md +0 -0
  52. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +0 -0
  53. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities-misc.md +0 -0
  54. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities.md +0 -0
  55. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/command-index.md +0 -0
  56. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/activity.md +0 -0
  57. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/addon.md +0 -0
  58. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/agent.md +0 -0
  59. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/ai.md +0 -0
  60. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/annotation.md +0 -0
  61. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/auth.md +0 -0
  62. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/automation.md +0 -0
  63. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/batch.md +0 -0
  64. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/billing.md +0 -0
  65. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/browse.md +0 -0
  66. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/calc.md +0 -0
  67. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/capability.md +0 -0
  68. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/client-app.md +0 -0
  69. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/completion.md +0 -0
  70. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/config.md +0 -0
  71. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/connector.md +0 -0
  72. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/context.md +0 -0
  73. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-qa.md +0 -0
  74. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard.md +0 -0
  75. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/data-app.md +0 -0
  76. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dataset.md +0 -0
  77. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/doctor.md +0 -0
  78. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/external-key.md +0 -0
  79. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/file.md +0 -0
  80. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/folder.md +0 -0
  81. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/job.md +0 -0
  82. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/log.md +0 -0
  83. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/notification.md +0 -0
  84. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/parameter.md +0 -0
  85. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/project.md +0 -0
  86. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/report.md +0 -0
  87. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schedule.md +0 -0
  88. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schema.md +0 -0
  89. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/skill.md +0 -0
  90. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/snippet.md +0 -0
  91. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/support.md +0 -0
  92. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/template.md +0 -0
  93. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/trash.md +0 -0
  94. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/upgrade.md +0 -0
  95. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/user.md +0 -0
  96. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/version.md +0 -0
  97. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view.md +0 -0
  98. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/webhook.md +0 -0
  99. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workflow.md +0 -0
  100. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workspace.md +0 -0
  101. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/handoff.md +0 -0
  102. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md +0 -0
  103. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +0 -0
  104. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +0 -0
  105. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/operations.md +0 -0
  106. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/auth-scope.md +0 -0
  107. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/cleanup.md +0 -0
  108. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/end-to-end.md +0 -0
  109. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/exports.md +0 -0
  110. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/index.md +0 -0
  111. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/need-action.md +0 -0
  112. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/resources.md +0 -0
  113. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/scheduling.md +0 -0
  114. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/transforms.md +0 -0
  115. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/typed-etl-discovery.md +0 -0
  116. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +0 -0
  117. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/report-checklist.md +0 -0
  118. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/retention.md +0 -0
  119. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +0 -0
  120. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/bundled_skill/mammoth-cli/references/task-start.md +0 -0
  121. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/__init__.py +0 -0
  122. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/activity.py +0 -0
  123. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/addon.py +0 -0
  124. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/agent.py +0 -0
  125. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/ai.py +0 -0
  126. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/annotation.py +0 -0
  127. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/auth.py +0 -0
  128. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/automation.py +0 -0
  129. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/batch.py +0 -0
  130. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/billing.py +0 -0
  131. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/browse.py +0 -0
  132. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/calc.py +0 -0
  133. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/capability.py +0 -0
  134. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/client_app.py +0 -0
  135. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/completion.py +0 -0
  136. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/config.py +0 -0
  137. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/connector.py +0 -0
  138. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/context.py +0 -0
  139. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/dashboard.py +0 -0
  140. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/data_app.py +0 -0
  141. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/dataset.py +0 -0
  142. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/doctor.py +0 -0
  143. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/external_key.py +0 -0
  144. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/file.py +0 -0
  145. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/folder.py +0 -0
  146. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/job.py +0 -0
  147. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/notification.py +0 -0
  148. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/parameter.py +0 -0
  149. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/project.py +0 -0
  150. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/registry.py +0 -0
  151. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/report.py +0 -0
  152. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/schedule.py +0 -0
  153. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/schema.py +0 -0
  154. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/skill.py +0 -0
  155. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/snippet.py +0 -0
  156. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/support.py +0 -0
  157. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/template.py +0 -0
  158. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/trash.py +0 -0
  159. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/upgrade.py +0 -0
  160. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/user.py +0 -0
  161. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/view.py +0 -0
  162. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/view_ops.py +0 -0
  163. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/webhook.py +0 -0
  164. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/workflow.py +0 -0
  165. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/commands/workspace.py +0 -0
  166. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/context/__init__.py +0 -0
  167. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/context/credentials.py +0 -0
  168. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/context/endpoint.py +0 -0
  169. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/context/profiles.py +0 -0
  170. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/context/resolver.py +0 -0
  171. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/contracts/__init__.py +0 -0
  172. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/contracts/auth.py +0 -0
  173. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/embed.py +0 -0
  174. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/errors/__init__.py +0 -0
  175. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/errors/envelope.py +0 -0
  176. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/manifest/__init__.py +0 -0
  177. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/manifest/loader.py +0 -0
  178. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/messages/__init__.py +0 -0
  179. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/output/__init__.py +0 -0
  180. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/output/envelope.py +0 -0
  181. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/output/normalize.py +0 -0
  182. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/output/policy.py +0 -0
  183. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/output/progress.py +0 -0
  184. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/output/render.py +0 -0
  185. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/py.typed +0 -0
  186. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/runtime/__init__.py +0 -0
  187. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/runtime/confirm.py +0 -0
  188. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/runtime/dryrun.py +0 -0
  189. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/runtime/embedded.py +0 -0
  190. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/runtime/executor.py +0 -0
  191. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/runtime/input_loader.py +0 -0
  192. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/runtime/invocation.py +0 -0
  193. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/runtime/options.py +0 -0
  194. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/runtime/parents.py +0 -0
  195. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/runtime/runlog.py +0 -0
  196. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/runtime/session.py +0 -0
  197. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/runtime/strict.py +0 -0
  198. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/runtime/updates.py +0 -0
  199. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/runtime/validate.py +0 -0
  200. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/__init__.py +0 -0
  201. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/argspec.py +0 -0
  202. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/coerce.py +0 -0
  203. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/command_contract.py +0 -0
  204. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/conditions.py +0 -0
  205. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/dashboard_pages.py +0 -0
  206. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/dashboard_review.py +0 -0
  207. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/dispatch.py +0 -0
  208. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/factory.py +0 -0
  209. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/input_fields.py +0 -0
  210. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/mapping.py +0 -0
  211. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/openapi_types.py +0 -0
  212. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/positionals.py +0 -0
  213. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/protocol.py +0 -0
  214. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/sdk_service.py +0 -0
  215. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/testing.py +0 -0
  216. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/services/type_system.py +0 -0
  217. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/skills/__init__.py +0 -0
  218. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/skills/installer.py +0 -0
  219. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/skills/steering.py +0 -0
  220. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/mammoth_cli/testing.py +0 -0
  221. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/activity.yaml +0 -0
  222. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/browse.yaml +0 -0
  223. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/capability.yaml +0 -0
  224. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/doctor.yaml +0 -0
  225. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/job.yaml +0 -0
  226. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/log.yaml +0 -0
  227. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/report.yaml +0 -0
  228. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/schema.yaml +0 -0
  229. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/commands/version.yaml +0 -0
  230. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/openapi-operations.yaml +0 -0
  231. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/sdk-catalog.source.yaml +0 -0
  232. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/manifests/sdk-methods.yaml +0 -0
  233. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/spec/openapi/metadata.json +0 -0
  234. {mammoth_cli-2.0.77 → mammoth_cli-2.0.78}/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.77
3
+ Version: 2.0.78
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
@@ -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.77"
9
+ __version__ = "2.0.78"
10
10
 
11
11
  # The versioned machine output envelope contract.
12
12
  SCHEMA_VERSION = 1
@@ -43,6 +43,7 @@ from mammoth_cli.output.policy import (
43
43
  )
44
44
  from mammoth_cli.runtime import executor, validate
45
45
  from mammoth_cli.runtime.invocation import Invocation
46
+ from mammoth_cli.runtime.state import with_state
46
47
  from mammoth_cli.runtime.strict import validate_extra_args
47
48
  from mammoth_cli.runtime.verify import with_verify
48
49
  from mammoth_cli.services.positionals import PositionalSpec, resolve_positionals
@@ -825,7 +826,7 @@ def _execute(invocation: Invocation) -> None:
825
826
  if invocation.dry_run:
826
827
  return _dry_run(handler, invocation)
827
828
  data, meta = handler(invocation)
828
- return _apply_verify(invocation.command_id, data), meta
829
+ return _apply_verify(invocation, data), meta
829
830
 
830
831
  executor.run(
831
832
  invocation.command_id,
@@ -837,17 +838,23 @@ def _execute(invocation: Invocation) -> None:
837
838
  )
838
839
 
839
840
 
840
- def _apply_verify(command_id: str, data: Any) -> Any:
841
+ def _apply_verify(invocation: Invocation, data: Any) -> Any:
841
842
  """Add the automatic write read-back to a real, mutating, API-backed result.
842
843
 
843
844
  A read command and a command with no reviewed ``sdk_symbol`` (a local or
844
845
  not-yet-backed command) are returned unchanged; ``--dry-run`` never
845
846
  reaches this at all, since it returns before the handler's real call.
847
+
848
+ Two independent read-backs are layered on top of the raw result: ``verify``
849
+ (status/row-count signals derived from the write's own response) and
850
+ ``state`` (a fresh read of what the write actually produced, per the
851
+ command's manifest ``readback``/``no_readback`` declaration -- see
852
+ :mod:`mammoth_cli.runtime.state`).
846
853
  """
847
- record = command_by_id(command_id) or {}
854
+ record = command_by_id(invocation.command_id) or {}
848
855
  if record.get("mutation_class") == "read" or not record.get("sdk_symbol"):
849
856
  return data
850
- return with_verify(data)
857
+ return with_state(invocation, with_verify(data, invocation))
851
858
 
852
859
 
853
860
  def _dry_run(handler: Handler, invocation: Invocation) -> tuple[Any, dict[str, Any]]:
@@ -96,9 +96,9 @@ Status on release: ran once on CLI 2.0.40 through `view.task.add` and was read b
96
96
 
97
97
  Run: `mammoth view transform delete-columns`. Exact input fields: `mammoth schema get view.transform.delete-columns`.
98
98
 
99
- Example: `mammoth view transform delete-columns 123 --input '{"columns": ["Status"], "dataset_id": 456}'`. Placeholders are illustrative; resolve IDs and input from observed reads.
99
+ Example: `mammoth view transform delete-columns 123 --input '{"columns": ["Status"], "dataset_id": 456}'`. Illustrative only: append `--yes` after observing an owned target.
100
100
 
101
- Result: `ViewTransformDeleteColumnsResult`; mutation `reversible_pipeline`, confirmation `none`, wait policy `always_wait`.
101
+ Result: `ViewTransformDeleteColumnsResult`; mutation `reversible_pipeline`, confirmation `prompt_or_yes`, wait policy `always_wait`.
102
102
 
103
103
  Status on release: ran once on CLI 2.0.40 through `view.task.add` and was read back with `view data get`; other inputs for this transform are untried.
104
104
 
@@ -7,7 +7,7 @@ sentence with `v3 generate`, and change it with `chat edit`; author a canvas by
7
7
  hand (`create-blank`, `canvas save`, `pages add`) only when asked to.
8
8
  `dashboard source list` is an observed blocker on release (see
9
9
  capabilities); verify the view binding with `dashboard get DASHBOARD_ID`
10
- (`data.dataview_id`) instead.
10
+ (`data.sources`) instead.
11
11
 
12
12
  ```bash
13
13
  mammoth dashboard v3 generate --input '{"body": {"params": {"intent": "Monthly revenue by region, with top customers", "dataview_id": VIEW_ID}}}'
@@ -0,0 +1,413 @@
1
+ """Automatic write read-back: attach the write's new state to its result.
2
+
3
+ ``with_verify`` (see :mod:`mammoth_cli.runtime.verify`) tells the caller
4
+ whether a write succeeded. It does not show what the write actually produced:
5
+ an export can report ``verified: true`` while the dataset it wrote has one of
6
+ four expected columns, and nothing in the result would say so. :func:`with_state`
7
+ closes that gap: every manifest command whose ``mutation_class`` is not
8
+ ``read`` declares, in its command record, either a ``readback`` (a read
9
+ command plus how to source its arguments from this write's input/result, plus
10
+ a ``kind``) or a ``no_readback`` reason. After the write settles, the executor
11
+ runs the declared read and attaches a compact ``state`` block built from it --
12
+ never from the request, and never omitted: a read that fails still yields a
13
+ ``state`` (``kind: unreadable``) rather than silence.
14
+
15
+ See ``CONTRACT-write-state.md`` (D-077) for the manifest shape this reads.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ from typing import Any
22
+
23
+ from mammoth_cli.errors.envelope import CliError
24
+ from mammoth_cli.manifest.loader import command_by_id
25
+ from mammoth_cli.runtime.invocation import Invocation
26
+ from mammoth_cli.services.positionals import resolve_positionals
27
+
28
+ #: Total JSON size a ``state`` block may reach before trimming kicks in.
29
+ STATE_SIZE_CAP_BYTES = 1500
30
+
31
+ #: Sample rows a ``kind: data`` state keeps, and the char length each cell is
32
+ #: truncated to -- row count and columns are never dropped to make room.
33
+ _SAMPLE_ROW_CAP = 5
34
+ _CELL_CHAR_CAP = 80
35
+
36
+ _VIEW_GET_COMMAND = "view.get"
37
+ _VIEW_DATA_COMMAND = "view.data.get"
38
+ _VIEW_LIST_COMMAND = "view.list"
39
+
40
+ #: ``verify.state`` of a write that was accepted but has not finished.
41
+ _UNFINISHED_VERIFY_STATE = "processing"
42
+
43
+
44
+ def with_state(invocation: Invocation, data: Any) -> Any:
45
+ """Return ``data`` with a ``state`` read-back block added, per the manifest.
46
+
47
+ A command with no ``readback``/``no_readback`` declared yet (mid-rollout)
48
+ is returned unchanged, as is a command that declares ``no_readback`` --
49
+ there is nothing to read back, by the family owner's own account.
50
+ """
51
+ if not isinstance(data, dict):
52
+ return data
53
+ record = command_by_id(invocation.command_id) or {}
54
+ readback = record.get("readback")
55
+ if not isinstance(readback, dict):
56
+ return data
57
+ unfinished = _unfinished_state(data)
58
+ state = unfinished or _enforce_cap(_build_state(invocation, data, readback))
59
+ return {**data, "state": state}
60
+
61
+
62
+ def _unfinished_state(data: dict[str, Any]) -> dict[str, Any] | None:
63
+ """A write ``verify`` found still processing has no new state to read yet."""
64
+ verify = data.get("verify")
65
+ if not isinstance(verify, dict) or verify.get("state") != _UNFINISHED_VERIFY_STATE:
66
+ return None
67
+ return {
68
+ "kind": "delivery",
69
+ "read_by": "verify",
70
+ "status": _UNFINISHED_VERIFY_STATE,
71
+ "detail": verify.get("reason"),
72
+ }
73
+
74
+
75
+ def _build_state(
76
+ invocation: Invocation, data: dict[str, Any], readback: dict[str, Any]
77
+ ) -> dict[str, Any]:
78
+ read_command_id = str(readback["command"])
79
+ kind = str(readback["kind"])
80
+ ids_spec: dict[str, Any] = readback.get("ids") or {}
81
+ resolved_ids: dict[str, Any] = {}
82
+ try:
83
+ resolved_ids = {
84
+ name: _resolve_source(str(source), data, invocation)
85
+ for name, source in ids_spec.items()
86
+ }
87
+ missing = [name for name, value in resolved_ids.items() if value is None]
88
+ if missing:
89
+ raise ValueError(f"could not resolve id(s) {missing} from the write's result/input")
90
+ if kind == "data":
91
+ return _data_state(resolved_ids, invocation)
92
+ read_data = _call_read(read_command_id, resolved_ids, invocation)
93
+ read_by = _read_by_label(read_command_id, resolved_ids)
94
+ if kind == "object":
95
+ return _object_state(read_by, read_data)
96
+ if kind == "delivery":
97
+ return _delivery_state(read_by, read_data)
98
+ raise ValueError(f"unknown readback kind {kind!r}")
99
+ except Exception as exc: # noqa: BLE001 -- a read failure must surface, never crash the write
100
+ read_by = _read_by_label(read_command_id, resolved_ids)
101
+ reason = exc.message if isinstance(exc, CliError) else str(exc)
102
+ return {"kind": "unreadable", "read_by": read_by, "reason": reason or type(exc).__name__}
103
+
104
+
105
+ def _read_by_label(command_id: str, resolved_ids: dict[str, Any]) -> str:
106
+ representative = next(iter(resolved_ids.values()), None)
107
+ return f"{command_id} {representative}" if representative is not None else command_id
108
+
109
+
110
+ # ---------------------------------------------------------------------------
111
+ # id resolution
112
+ # ---------------------------------------------------------------------------
113
+
114
+
115
+ def _resolve_source(source: str, data: dict[str, Any], invocation: Invocation) -> Any:
116
+ """Resolve one ``ids`` value; ``a|b`` takes the first alternative that resolves.
117
+
118
+ A write whose response comes in more than one shape (an export returns
119
+ either ``{trigger_id, future_id}`` or ``{"job": {...}}``) names each place
120
+ its id can be.
121
+ """
122
+ for alternative in source.split("|"):
123
+ value = _resolve_one_source(alternative, data, invocation)
124
+ if value is not None:
125
+ return value
126
+ return None
127
+
128
+
129
+ def _resolve_one_source(source: str, data: dict[str, Any], invocation: Invocation) -> Any:
130
+ """Resolve one ``result.<path>`` | ``input.<field>`` | ``positional.<n>`` source."""
131
+ if source.startswith("result."):
132
+ return _get_path(data, source[len("result.") :])
133
+ if source.startswith("input."):
134
+ document = invocation.bound_input() or {}
135
+ return _get_path(document, source[len("input.") :])
136
+ if source.startswith("positional."):
137
+ index_text = source[len("positional.") :]
138
+ try:
139
+ index = int(index_text)
140
+ except ValueError:
141
+ raise ValueError(f"invalid positional index in id source {source!r}") from None
142
+ return invocation.extra_args[index] if index < len(invocation.extra_args) else None
143
+ raise ValueError(f"unrecognized id source {source!r}")
144
+
145
+
146
+ def _get_path(root: Any, path: str) -> Any:
147
+ current = root
148
+ for segment in path.split("."):
149
+ if isinstance(current, dict):
150
+ current = current.get(segment)
151
+ elif isinstance(current, list) and segment.isdigit():
152
+ index = int(segment)
153
+ current = current[index] if index < len(current) else None
154
+ else:
155
+ return None
156
+ return current
157
+
158
+
159
+ # ---------------------------------------------------------------------------
160
+ # dispatching a read command's handler directly (never through the executor:
161
+ # this is an internal follow-up read, not a fresh command invocation)
162
+ # ---------------------------------------------------------------------------
163
+
164
+
165
+ def _read_invocation(command_id: str, resolved_ids: dict[str, Any], base: Invocation) -> Invocation:
166
+ """Build the :class:`Invocation` a read command's handler expects.
167
+
168
+ Ids that name a declared positional of ``command_id`` are supplied in
169
+ order (matching how handlers read ``extra_args``); any id left over is
170
+ offered as a pre-admitted ``--input`` document instead.
171
+ """
172
+ positional_names = [spec.name for spec in resolve_positionals(command_id)]
173
+ positionals: dict[str, Any] = {}
174
+ extra_args: list[str] = []
175
+ for name in positional_names:
176
+ if name not in resolved_ids:
177
+ break
178
+ value = resolved_ids[name]
179
+ positionals[name] = value
180
+ extra_args.append(str(value))
181
+ document = {name: value for name, value in resolved_ids.items() if name not in positionals}
182
+ invocation = Invocation(
183
+ command_id=command_id,
184
+ output="json",
185
+ profile=base.profile,
186
+ project=base.project,
187
+ timeout=base.timeout,
188
+ job_timeout=base.job_timeout,
189
+ pipeline_timeout=base.pipeline_timeout,
190
+ no_input=True,
191
+ positionals=positionals,
192
+ extra_args=extra_args,
193
+ )
194
+ object.__setattr__(invocation, "_prepared_input", document or None)
195
+ return invocation
196
+
197
+
198
+ def _call_read(command_id: str, resolved_ids: dict[str, Any], base: Invocation) -> Any:
199
+ from mammoth_cli.commands.registry import HANDLERS
200
+
201
+ handler = HANDLERS.get(command_id)
202
+ if handler is None:
203
+ raise LookupError(f"no handler registered for read command {command_id!r}")
204
+ read_invocation = _read_invocation(command_id, resolved_ids, base)
205
+ read_data, _meta = handler(read_invocation)
206
+ return read_data
207
+
208
+
209
+ # ---------------------------------------------------------------------------
210
+ # kind: object
211
+ # ---------------------------------------------------------------------------
212
+
213
+
214
+ def _object_state(read_by: str, read_data: Any) -> dict[str, Any]:
215
+ return {"kind": "object", "read_by": read_by, "object": _extract_object(read_data)}
216
+
217
+
218
+ def _extract_object(read_data: Any) -> Any:
219
+ if not isinstance(read_data, dict):
220
+ return read_data
221
+ # A single-record read (``view.get``, ``dataset.get``, ...) carries its own
222
+ # ``id`` at the top level; a listing read (``view.list``, ...) wraps its
223
+ # items under a plural key instead. Checking for ``id`` first keeps a
224
+ # record's own nested list fields (e.g. a dataview's ``metadata`` columns)
225
+ # from being mistaken for the collection.
226
+ if "id" not in read_data:
227
+ listing = _first_list(read_data)
228
+ if listing is not None:
229
+ return [_scalar_fields(item) for item in listing]
230
+ return _scalar_fields(read_data)
231
+
232
+
233
+ def _first_list(record: dict[str, Any]) -> list[Any] | None:
234
+ """The first list-of-dicts value in a ``*.list`` read's result, if any."""
235
+ for value in record.values():
236
+ if isinstance(value, list) and (not value or isinstance(value[0], dict)):
237
+ return value
238
+ return None
239
+
240
+
241
+ def _scalar_fields(item: Any) -> Any:
242
+ """Keep only an object's top-level scalar fields (every id is one)."""
243
+ if not isinstance(item, dict):
244
+ return item
245
+ return {
246
+ key: value
247
+ for key, value in item.items()
248
+ if value is None or isinstance(value, (str, int, float, bool))
249
+ }
250
+
251
+
252
+ # ---------------------------------------------------------------------------
253
+ # kind: delivery
254
+ # ---------------------------------------------------------------------------
255
+
256
+
257
+ def _delivery_state(read_by: str, read_data: Any) -> dict[str, Any]:
258
+ status: Any = None
259
+ detail: Any = None
260
+ if isinstance(read_data, dict):
261
+ status = read_data.get("status")
262
+ if status is None:
263
+ job = read_data.get("job")
264
+ status = job.get("status") if isinstance(job, dict) else None
265
+ detail = read_data.get("message") or read_data.get("detail") or read_data.get("error")
266
+ return {
267
+ "kind": "delivery",
268
+ "read_by": read_by,
269
+ "status": status if isinstance(status, str) else None,
270
+ "detail": str(detail) if detail is not None else None,
271
+ }
272
+
273
+
274
+ # ---------------------------------------------------------------------------
275
+ # kind: data -- the write's output view: real columns, row count, 5 rows.
276
+ #
277
+ # No single read command returns columns+type, row_count and sample rows
278
+ # together, so this always makes two fixed internal reads once it has a
279
+ # view id: ``view.get`` for the typed columns and row count, ``view.data.get``
280
+ # for the sample. When the write's own ids only resolve a dataset id (a
281
+ # dataset/upload write, whose output is that dataset's first view), the
282
+ # view id is discovered with one ``view.list`` read first.
283
+ # ---------------------------------------------------------------------------
284
+
285
+
286
+ def _data_state(resolved_ids: dict[str, Any], base: Invocation) -> dict[str, Any]:
287
+ view_id = _as_int(resolved_ids.get("view_id") or resolved_ids.get("dataview_id"))
288
+ dataset_id = _as_int(resolved_ids.get("dataset_id"))
289
+ if view_id is None:
290
+ if dataset_id is None:
291
+ raise ValueError("readback kind 'data' needs a resolved view_id or dataset_id")
292
+ view_id = _discover_view_id(dataset_id, base)
293
+ view_ids: dict[str, Any] = {"view_id": view_id}
294
+ if dataset_id is not None:
295
+ view_ids["dataset_id"] = dataset_id
296
+ info = _call_read(_VIEW_GET_COMMAND, view_ids, base)
297
+ page = _call_read(_VIEW_DATA_COMMAND, view_ids, base)
298
+ rows = page.get("data") if isinstance(page, dict) else None
299
+ sample = [_trim_row(row) for row in (rows or [])[:_SAMPLE_ROW_CAP]]
300
+ return {
301
+ "kind": "data",
302
+ "read_by": f"{_VIEW_DATA_COMMAND} {view_id}",
303
+ "columns": _columns(info),
304
+ "row_count": info.get("row_count") if isinstance(info, dict) else None,
305
+ "sample": sample,
306
+ }
307
+
308
+
309
+ def _discover_view_id(dataset_id: int, base: Invocation) -> int:
310
+ listing = _call_read(_VIEW_LIST_COMMAND, {"dataset_id": dataset_id}, base)
311
+ views = listing.get("dataviews") if isinstance(listing, dict) else None
312
+ if not isinstance(views, list) or not views or not isinstance(views[0], dict):
313
+ raise ValueError(f"dataset {dataset_id} has no view to read its new state from")
314
+ view_id = views[0].get("id")
315
+ if not isinstance(view_id, int):
316
+ raise ValueError(f"dataset {dataset_id}'s first view has no usable id")
317
+ return view_id
318
+
319
+
320
+ def _columns(info: Any) -> list[dict[str, str]]:
321
+ if not isinstance(info, dict):
322
+ return []
323
+ metadata = info.get("metadata")
324
+ if not isinstance(metadata, list):
325
+ return []
326
+ columns: list[dict[str, str]] = []
327
+ for column in metadata:
328
+ if not isinstance(column, dict):
329
+ continue
330
+ name = column.get("display_name") or column.get("internal_name")
331
+ if name is None:
332
+ continue
333
+ columns.append({"name": str(name), "type": str(column.get("type") or "")})
334
+ return columns
335
+
336
+
337
+ def _trim_row(row: Any) -> Any:
338
+ if not isinstance(row, dict):
339
+ return row
340
+ return {key: _trim_cell(value) for key, value in row.items()}
341
+
342
+
343
+ def _trim_cell(value: Any) -> Any:
344
+ if isinstance(value, str) and len(value) > _CELL_CHAR_CAP:
345
+ return value[:_CELL_CHAR_CAP] + "..."
346
+ return value
347
+
348
+
349
+ def _as_int(value: Any) -> int | None:
350
+ if isinstance(value, bool):
351
+ return None
352
+ if isinstance(value, int):
353
+ return value
354
+ if isinstance(value, str) and value.strip().lstrip("-").isdigit():
355
+ return int(value)
356
+ return None
357
+
358
+
359
+ # ---------------------------------------------------------------------------
360
+ # size cap
361
+ # ---------------------------------------------------------------------------
362
+
363
+
364
+ def _enforce_cap(state: dict[str, Any]) -> dict[str, Any]:
365
+ """Trim ``state`` under :data:`STATE_SIZE_CAP_BYTES`; never drop columns/row_count."""
366
+ if _size(state) <= STATE_SIZE_CAP_BYTES:
367
+ return state
368
+ kind = state.get("kind")
369
+ if kind == "data":
370
+ sample = list(state.get("sample") or [])
371
+ while sample and _size({**state, "sample": sample}) > STATE_SIZE_CAP_BYTES:
372
+ sample.pop()
373
+ return {**state, "sample": sample}
374
+ if kind == "object":
375
+ capped = {**state, "object": _capped_object(state)}
376
+ original = state.get("object")
377
+ if isinstance(original, list) and len(capped["object"]) < len(original):
378
+ capped["object_total"] = len(original)
379
+ return capped
380
+ if kind == "delivery":
381
+ detail = state.get("detail")
382
+ if isinstance(detail, str):
383
+ overflow = _size(state) - STATE_SIZE_CAP_BYTES
384
+ keep = max(0, len(detail) - overflow - 3)
385
+ return {**state, "detail": detail[:keep] + "..."}
386
+ return state
387
+
388
+
389
+ def _capped_object(state: dict[str, Any]) -> Any:
390
+ obj = state.get("object")
391
+ if isinstance(obj, list):
392
+ items = list(obj)
393
+ while items and _size({**state, "object": items}) > STATE_SIZE_CAP_BYTES:
394
+ items.pop()
395
+ return items
396
+ if isinstance(obj, dict):
397
+ fields = dict(obj)
398
+ protected = {key for key in fields if key == "id" or key.endswith("_id")}
399
+ droppable = sorted(
400
+ (key for key in fields if key not in protected),
401
+ key=lambda key: len(str(fields[key])),
402
+ reverse=True,
403
+ )
404
+ for key in droppable:
405
+ if _size({**state, "object": fields}) <= STATE_SIZE_CAP_BYTES:
406
+ break
407
+ fields.pop(key, None)
408
+ return fields
409
+ return obj
410
+
411
+
412
+ def _size(value: Any) -> int:
413
+ return len(json.dumps(value, default=str))
@@ -10,7 +10,10 @@ what it should" instead of re-deriving it per command.
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
- from typing import Any
13
+ from typing import TYPE_CHECKING, Any
14
+
15
+ if TYPE_CHECKING:
16
+ from mammoth_cli.runtime.invocation import Invocation
14
17
 
15
18
  #: Below this share of matched rows, a join has enough misses to flag before
16
19
  #: the caller builds anything further on it.
@@ -19,6 +22,10 @@ JOIN_MATCH_RATE_THRESHOLD = 0.8
19
22
  _FAILURE_STATUSES = frozenset({"error", "failed", "failure"})
20
23
  _FAILURE_PIPELINE_STATES = frozenset({"error", "ref_error"})
21
24
 
25
+ #: HTTP status codes that mean "accepted, not finished" when a write's own
26
+ #: response carries no other completion signal at all.
27
+ _ACCEPTED_STATUS_CODES = frozenset({202})
28
+
22
29
 
23
30
  #: Surfaced in ``warnings`` (and drives ``verified: false``) when a
24
31
  #: ``row_check``/``join_check`` was attempted but ``rows_after`` never came
@@ -39,14 +46,19 @@ _UNSETTLED_REASON = (
39
46
  )
40
47
 
41
48
 
42
- def with_verify(data: Any) -> Any:
49
+ def with_verify(data: Any, invocation: Invocation | None = None) -> Any:
43
50
  """Return ``data`` with a ``verify`` read-back block added.
44
51
 
45
52
  ``data`` that is not a dict (a dry-run report, a bare list, ...) is
46
- returned unchanged.
53
+ returned unchanged. When ``invocation`` is given, a write whose own
54
+ response is nothing but an unsettled async job (a bare ``job_id``/
55
+ ``future_id``, or an HTTP 202 with no job reference at all) is settled
56
+ first -- see :func:`_settle_unfinished_write` -- so it is never reported
57
+ ``verified: true`` before it has actually finished.
47
58
  """
48
59
  if not isinstance(data, dict):
49
60
  return data
61
+ data = _settle_unfinished_write(data, invocation)
50
62
  check = _check_dict(data)
51
63
  row_count_attempted = _row_count_attempted(check)
52
64
  rows_before, rows_after = _row_counts(check) if row_count_attempted else (None, None)
@@ -80,6 +92,42 @@ def _job_status(data: dict[str, Any]) -> str | None:
80
92
  return status if isinstance(status, str) else None
81
93
 
82
94
 
95
+ def _settle_unfinished_write(data: dict[str, Any], invocation: Invocation | None) -> dict[str, Any]:
96
+ """Settle a write whose immediate response is an unfinished async job.
97
+
98
+ ``wait_if_job`` (every ``MammothService``) already recognizes a bare
99
+ ``{"job_id": N}``/``{"future_id": N}`` job reference and polls it to
100
+ completion -- raising on failure or timeout, exactly like every other
101
+ settle in this codebase -- but only a write handler that explicitly calls
102
+ it gets that settle. A write whose result carries no other completion
103
+ signal (no ``status``/``pipeline_state``/nested ``job.status``) must not
104
+ be reported ``verified: true`` on the strength of an unpolled job
105
+ reference, or of an HTTP 202 with no reference to poll at all.
106
+ """
107
+ if invocation is None or _job_status(data) is not None:
108
+ return data
109
+ if isinstance(data.get("status"), str) or isinstance(data.get("pipeline_state"), str):
110
+ return data
111
+ if _has_bare_job_reference(data):
112
+ from mammoth_cli.runtime.session import open_service
113
+
114
+ with open_service(invocation) as (service, _auth):
115
+ settled = service.wait_if_job(data)
116
+ return settled if isinstance(settled, dict) else data
117
+ if data.get("status_code") in _ACCEPTED_STATUS_CODES:
118
+ # Accepted, with nothing at all to poll: still not finished.
119
+ return {**data, "status": "processing"}
120
+ return data
121
+
122
+
123
+ def _has_bare_job_reference(data: dict[str, Any]) -> bool:
124
+ for key in ("job_id", "future_id"):
125
+ value = data.get(key)
126
+ if isinstance(value, int) and not isinstance(value, bool):
127
+ return True
128
+ return False
129
+
130
+
83
131
  def _check_dict(data: dict[str, Any]) -> dict[str, Any] | None:
84
132
  """The ``row_check``/``join_check`` dict a command attached, if any."""
85
133
  for key in ("row_check", "join_check"):
@@ -147,7 +147,15 @@ def column_warnings(
147
147
  )
148
148
  if others:
149
149
  shown = ", ".join(repr(o) for o in others[:_MAX_EXAMPLES])
150
- detail += f" Other values: {shown} (the conversion makes them empty)."
150
+ detail += f" Other values: {shown}"
151
+ if kind == "numbers":
152
+ # A value that fails this number check truly can't
153
+ # become NUMERIC. Dates have no such guarantee: the
154
+ # backend's date parser accepts formats this
155
+ # whitelist doesn't recognize, so claiming data loss
156
+ # there would be untrue.
157
+ detail += " (the conversion makes them empty)"
158
+ detail += "."
151
159
  warnings.append(
152
160
  {
153
161
  "column": column,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mammoth-cli"
3
- version = "2.0.77"
3
+ version = "2.0.78"
4
4
  description = "Command-line interface for the Mammoth Analytics platform"
5
5
  authors = [{name = "Mammoth Analytics", email = "support@mammoth.io"}]
6
6
  readme = "README.md"
@@ -34,6 +34,10 @@ commands:
34
34
  undo_test: null
35
35
  known_restrictions: null
36
36
  reviewed_by: primary
37
+ readback:
38
+ command: addon.list
39
+ ids: {}
40
+ kind: object
37
41
  - command_id: addon.connector.remove
38
42
  command_path: addon connector remove
39
43
  disposition: command
@@ -68,6 +72,10 @@ commands:
68
72
  undo_test: null
69
73
  known_restrictions: null
70
74
  reviewed_by: primary
75
+ readback:
76
+ command: addon.list
77
+ ids: {}
78
+ kind: object
71
79
  - command_id: addon.list
72
80
  command_path: addon list
73
81
  disposition: command
@@ -135,6 +143,10 @@ commands:
135
143
  undo_test: null
136
144
  known_restrictions: null
137
145
  reviewed_by: primary
146
+ readback:
147
+ command: addon.list
148
+ ids: {}
149
+ kind: object
138
150
  - command_id: addon.storage.remove
139
151
  command_path: addon storage remove
140
152
  disposition: command
@@ -169,6 +181,10 @@ commands:
169
181
  undo_test: null
170
182
  known_restrictions: null
171
183
  reviewed_by: primary
184
+ readback:
185
+ command: addon.list
186
+ ids: {}
187
+ kind: object
172
188
  - command_id: addon.user.add
173
189
  command_path: addon user add
174
190
  disposition: command
@@ -203,6 +219,10 @@ commands:
203
219
  undo_test: null
204
220
  known_restrictions: null
205
221
  reviewed_by: primary
222
+ readback:
223
+ command: addon.list
224
+ ids: {}
225
+ kind: object
206
226
  - command_id: addon.user.remove
207
227
  command_path: addon user remove
208
228
  disposition: command
@@ -237,3 +257,7 @@ commands:
237
257
  undo_test: null
238
258
  known_restrictions: null
239
259
  reviewed_by: primary
260
+ readback:
261
+ command: addon.list
262
+ ids: {}
263
+ kind: object