mammoth-cli 2.0.77__tar.gz → 2.0.79__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.79}/PKG-INFO +1 -1
  2. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/__init__.py +1 -1
  3. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/app.py +11 -4
  4. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/SKILL.md +18 -18
  5. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view-transform.md +2 -2
  6. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/dashboards.md +1 -1
  7. mammoth_cli-2.0.79/mammoth_cli/runtime/state.py +413 -0
  8. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/verify.py +51 -3
  9. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/data_quality.py +9 -1
  10. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/pyproject.toml +1 -1
  11. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/addon.yaml +24 -0
  12. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/agent.yaml +12 -0
  13. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/ai.yaml +3 -0
  14. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/annotation.yaml +19 -0
  15. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/auth.yaml +8 -0
  16. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/automation.yaml +24 -0
  17. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/batch.yaml +25 -0
  18. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/billing.yaml +39 -0
  19. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/calc.yaml +2 -1
  20. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/client-app.yaml +14 -0
  21. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/completion.yaml +5 -0
  22. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/config.yaml +5 -0
  23. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/connector.yaml +53 -0
  24. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/context.yaml +8 -0
  25. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/dashboard.yaml +413 -70
  26. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/data-app.yaml +29 -0
  27. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/dataset.yaml +50 -2
  28. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/external-key.yaml +9 -0
  29. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/file.yaml +38 -9
  30. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/folder.yaml +28 -0
  31. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/notification.yaml +16 -0
  32. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/parameter.yaml +44 -0
  33. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/project.yaml +50 -0
  34. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/schedule.yaml +14 -0
  35. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/skill.yaml +13 -0
  36. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/snippet.yaml +24 -0
  37. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/support.yaml +142 -0
  38. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/template.yaml +14 -0
  39. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/trash.yaml +8 -0
  40. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/upgrade.yaml +4 -0
  41. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/user.yaml +21 -0
  42. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/view.yaml +565 -115
  43. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/webhook.yaml +16 -0
  44. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/workflow.yaml +48 -0
  45. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/workspace.yaml +54 -0
  46. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/schema-v1.json +15 -0
  47. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/.gitignore +0 -0
  48. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/LICENSE +0 -0
  49. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/README.md +0 -0
  50. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/__main__.py +0 -0
  51. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/about-mammoth.md +0 -0
  52. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/auth.md +0 -0
  53. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities-misc.md +0 -0
  54. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/capabilities.md +0 -0
  55. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/command-index.md +0 -0
  56. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/activity.md +0 -0
  57. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/addon.md +0 -0
  58. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/agent.md +0 -0
  59. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/ai.md +0 -0
  60. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/annotation.md +0 -0
  61. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/auth.md +0 -0
  62. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/automation.md +0 -0
  63. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/batch.md +0 -0
  64. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/billing.md +0 -0
  65. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/browse.md +0 -0
  66. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/calc.md +0 -0
  67. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/capability.md +0 -0
  68. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/client-app.md +0 -0
  69. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/completion.md +0 -0
  70. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/config.md +0 -0
  71. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/connector.md +0 -0
  72. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/context.md +0 -0
  73. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard-qa.md +0 -0
  74. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dashboard.md +0 -0
  75. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/data-app.md +0 -0
  76. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/dataset.md +0 -0
  77. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/doctor.md +0 -0
  78. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/external-key.md +0 -0
  79. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/file.md +0 -0
  80. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/folder.md +0 -0
  81. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/job.md +0 -0
  82. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/log.md +0 -0
  83. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/notification.md +0 -0
  84. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/parameter.md +0 -0
  85. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/project.md +0 -0
  86. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/report.md +0 -0
  87. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schedule.md +0 -0
  88. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/schema.md +0 -0
  89. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/skill.md +0 -0
  90. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/snippet.md +0 -0
  91. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/support.md +0 -0
  92. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/template.md +0 -0
  93. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/trash.md +0 -0
  94. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/upgrade.md +0 -0
  95. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/user.md +0 -0
  96. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/version.md +0 -0
  97. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/view.md +0 -0
  98. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/webhook.md +0 -0
  99. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workflow.md +0 -0
  100. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/commands/workspace.md +0 -0
  101. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/handoff.md +0 -0
  102. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/input.md +0 -0
  103. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/jobs-drafts.md +0 -0
  104. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/machine-output.md +0 -0
  105. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/operations.md +0 -0
  106. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/auth-scope.md +0 -0
  107. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/cleanup.md +0 -0
  108. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/end-to-end.md +0 -0
  109. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/exports.md +0 -0
  110. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/index.md +0 -0
  111. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/need-action.md +0 -0
  112. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/resources.md +0 -0
  113. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/scheduling.md +0 -0
  114. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/transforms.md +0 -0
  115. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recipes/typed-etl-discovery.md +0 -0
  116. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/recovery.md +0 -0
  117. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/report-checklist.md +0 -0
  118. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/retention.md +0 -0
  119. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/safety.md +0 -0
  120. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/bundled_skill/mammoth-cli/references/task-start.md +0 -0
  121. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/__init__.py +0 -0
  122. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/activity.py +0 -0
  123. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/addon.py +0 -0
  124. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/agent.py +0 -0
  125. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/ai.py +0 -0
  126. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/annotation.py +0 -0
  127. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/auth.py +0 -0
  128. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/automation.py +0 -0
  129. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/batch.py +0 -0
  130. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/billing.py +0 -0
  131. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/browse.py +0 -0
  132. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/calc.py +0 -0
  133. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/capability.py +0 -0
  134. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/client_app.py +0 -0
  135. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/completion.py +0 -0
  136. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/config.py +0 -0
  137. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/connector.py +0 -0
  138. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/context.py +0 -0
  139. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/dashboard.py +0 -0
  140. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/data_app.py +0 -0
  141. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/dataset.py +0 -0
  142. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/doctor.py +0 -0
  143. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/external_key.py +0 -0
  144. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/file.py +0 -0
  145. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/folder.py +0 -0
  146. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/job.py +0 -0
  147. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/notification.py +0 -0
  148. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/parameter.py +0 -0
  149. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/project.py +0 -0
  150. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/registry.py +0 -0
  151. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/report.py +0 -0
  152. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/schedule.py +0 -0
  153. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/schema.py +0 -0
  154. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/skill.py +0 -0
  155. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/snippet.py +0 -0
  156. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/support.py +0 -0
  157. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/template.py +0 -0
  158. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/trash.py +0 -0
  159. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/upgrade.py +0 -0
  160. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/user.py +0 -0
  161. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/view.py +0 -0
  162. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/view_ops.py +0 -0
  163. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/webhook.py +0 -0
  164. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/workflow.py +0 -0
  165. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/commands/workspace.py +0 -0
  166. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/context/__init__.py +0 -0
  167. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/context/credentials.py +0 -0
  168. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/context/endpoint.py +0 -0
  169. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/context/profiles.py +0 -0
  170. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/context/resolver.py +0 -0
  171. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/contracts/__init__.py +0 -0
  172. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/contracts/auth.py +0 -0
  173. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/embed.py +0 -0
  174. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/errors/__init__.py +0 -0
  175. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/errors/envelope.py +0 -0
  176. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/manifest/__init__.py +0 -0
  177. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/manifest/loader.py +0 -0
  178. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/messages/__init__.py +0 -0
  179. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/output/__init__.py +0 -0
  180. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/output/envelope.py +0 -0
  181. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/output/normalize.py +0 -0
  182. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/output/policy.py +0 -0
  183. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/output/progress.py +0 -0
  184. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/output/render.py +0 -0
  185. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/py.typed +0 -0
  186. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/__init__.py +0 -0
  187. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/confirm.py +0 -0
  188. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/dryrun.py +0 -0
  189. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/embedded.py +0 -0
  190. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/executor.py +0 -0
  191. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/input_loader.py +0 -0
  192. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/invocation.py +0 -0
  193. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/options.py +0 -0
  194. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/parents.py +0 -0
  195. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/runlog.py +0 -0
  196. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/session.py +0 -0
  197. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/strict.py +0 -0
  198. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/updates.py +0 -0
  199. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/runtime/validate.py +0 -0
  200. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/__init__.py +0 -0
  201. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/argspec.py +0 -0
  202. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/coerce.py +0 -0
  203. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/command_contract.py +0 -0
  204. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/conditions.py +0 -0
  205. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/dashboard_pages.py +0 -0
  206. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/dashboard_review.py +0 -0
  207. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/dispatch.py +0 -0
  208. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/factory.py +0 -0
  209. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/input_fields.py +0 -0
  210. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/mapping.py +0 -0
  211. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/openapi_types.py +0 -0
  212. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/positionals.py +0 -0
  213. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/protocol.py +0 -0
  214. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/sdk_service.py +0 -0
  215. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/testing.py +0 -0
  216. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/services/type_system.py +0 -0
  217. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/skills/__init__.py +0 -0
  218. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/skills/installer.py +0 -0
  219. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/skills/steering.py +0 -0
  220. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/mammoth_cli/testing.py +0 -0
  221. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/activity.yaml +0 -0
  222. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/browse.yaml +0 -0
  223. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/capability.yaml +0 -0
  224. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/doctor.yaml +0 -0
  225. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/job.yaml +0 -0
  226. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/log.yaml +0 -0
  227. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/report.yaml +0 -0
  228. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/schema.yaml +0 -0
  229. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/commands/version.yaml +0 -0
  230. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/openapi-operations.yaml +0 -0
  231. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/sdk-catalog.source.yaml +0 -0
  232. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/manifests/sdk-methods.yaml +0 -0
  233. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/spec/openapi/metadata.json +0 -0
  234. {mammoth_cli-2.0.77 → mammoth_cli-2.0.79}/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.79
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.79"
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]]:
@@ -42,12 +42,16 @@ never read one from a file/env var, never run `auth login`
42
42
  yourself. If any envelope carries `meta.update_available`, run its `command`
43
43
  before the next step.
44
44
 
45
+ In a shell: piped stdout is compact JSON and prompts are off; in a session
46
+ that is not piped, `export MAMMOTH_OUTPUT=json MAMMOTH_NO_INPUT=1` once.
47
+ `project ensure` saves the active project (`--project`, `MAMMOTH_PROFILE`
48
+ and `MAMMOTH_PROJECT` override it). `mammoth skill show --input '{"file":
49
+ "references/recipes/transforms.md"}'` prints any file of this skill;
50
+ `mammoth skill agents-md install` writes a steering block into a
51
+ repository's AGENTS.md so later sessions start here.
52
+
45
53
  ## Defaults you do not repeat
46
54
 
47
- - Piped stdout is compact JSON and prompts are off. Only in a session that
48
- is not piped, `export MAMMOTH_OUTPUT=json MAMMOTH_NO_INPUT=1` once.
49
- - `project ensure` saves the active project; `--project` only overrides it.
50
- `MAMMOTH_PROFILE` / `MAMMOTH_PROJECT` also work as session defaults.
51
55
  - The CLI remembers which dataset owns each view from any read (`view list
52
56
  DATASET_ID`, `view get VIEW_ID`, an upload). After that, no `dataset_id`
53
57
  on transforms, exports or deletes. If a command asks for the parent
@@ -96,10 +100,10 @@ with the command tried.
96
100
  Users often upload related files without saying how they relate ("make a
97
101
  dashboard from t_a and t_b"). Work it out from the data before building:
98
102
 
99
- 1. Upload every file. Each result carries a `view` preview of what Mammoth
100
- made of it (`view_id`, column types, sample rows, `column_warnings`,
101
- `before_dashboard`). Do not open the local files (`cat`, `head`) — what
102
- Mammoth made of them is what counts. Run `view data get VIEW_ID` per view;
103
+ 1. Upload every file, or find the ones already uploaded. An upload result
104
+ carries a `view` preview of what Mammoth made of it (`view_id`, column
105
+ types, sample rows, `column_warnings`, `before_dashboard`). What Mammoth
106
+ made of the files is what counts, not the raw files. Run `view data get VIEW_ID` per view;
103
107
  warnings list numbers/dates stored as text, and blanks, each with its fix.
104
108
  2. Find the keys. A column in one view whose values appear in a column of
105
109
  the other (`customer_id` and `id`, `order_ref` and `order_no`) is a
@@ -114,8 +118,8 @@ dashboard from t_a and t_b"). Work it out from the data before building:
114
118
  is a miss.
115
119
  5. `view transform join` the lookup view into the main view (`LEFT`). The
116
120
  result's `join_check` gives `match_rate`, `unmatched_rows` and
117
- `unmatched_keys`; put them in your report, and stop to compare the keys
118
- if more than a few rows found no match.
121
+ `unmatched_keys`; put them in your report. When the result's
122
+ `verify.needs_user` is set, stop and tell the user before building on it.
119
123
  6. If the data has money, add it before you make the dashboard: `revenue`
120
124
  (`math`, `qty * price`, `new_column`), the `fix` in `before_dashboard`.
121
125
  A dashboard sees only the columns the view had when it was made.
@@ -134,9 +138,6 @@ your report. If no column links the files, ask before you combine them.
134
138
 
135
139
  - Ids are positionals; request fields are one `--input '{...}'` document;
136
140
  no per-field flags (`project create NAME`, not `--name`).
137
- - `mammoth skill show --input '{"file": "references/recipes/transforms.md"}'`
138
- prints any file of this skill; `mammoth skill agents-md install` writes a
139
- steering block into a repository's AGENTS.md so later sessions start here.
140
141
  - `COMMAND --help` lists input fields; `schema find WORDS` inlines
141
142
  `accepted_fields`/`agent_example` for top matches, else `mammoth schema
142
143
  get COMMAND_ID` (`--input '{"full": true}'` for the schema); `schema
@@ -161,11 +162,10 @@ your report. If no column links the files, ask before you combine them.
161
162
 
162
163
  ## Verify before you report
163
164
 
164
- - A success envelope proves the call, not the outcome. After a row-scoped
165
- change (set-values, filter, replace, join, fill) run `view data get
166
- VIEW_ID` (50 rows by default, `limit` to change) and check rows the
167
- condition should and should not have touched. Sample the target column
168
- before the change.
165
+ - A success envelope proves the call; the write's `state` (the data,
166
+ object or job status read back after it settled) shows the outcome.
167
+ Check it against your plan: rows the change should and should not have
168
+ touched. `unreadable` or a partial sample: read it with `view data get`.
169
169
  - A uniform result (every amount 0, every region "Unknown", most join rows
170
170
  unmatched) means the previous step went wrong; stop and re-inspect it.
171
171
  - `column_warnings` on a data read and `join_check` on a join are findings,
@@ -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"):