cmpnd 0.28.0__tar.gz → 0.30.0__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 (351) hide show
  1. {cmpnd-0.28.0 → cmpnd-0.30.0}/PKG-INFO +1 -1
  2. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/commands/admin.py +41 -4
  3. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/verbs.py +1 -0
  4. {cmpnd-0.28.0 → cmpnd-0.30.0}/docs/cli.md +49 -0
  5. {cmpnd-0.28.0 → cmpnd-0.30.0}/pyproject.toml +1 -1
  6. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_admin.py +54 -1
  7. {cmpnd-0.28.0 → cmpnd-0.30.0}/uv.lock +1 -1
  8. {cmpnd-0.28.0 → cmpnd-0.30.0}/.gitignore +0 -0
  9. {cmpnd-0.28.0 → cmpnd-0.30.0}/AGENTS.md +0 -0
  10. {cmpnd-0.28.0 → cmpnd-0.30.0}/CLAUDE.md +0 -0
  11. {cmpnd-0.28.0 → cmpnd-0.30.0}/README.md +0 -0
  12. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/__init__.py +0 -0
  13. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/_assessments.py +0 -0
  14. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/_authored_import.py +0 -0
  15. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/_deployments.py +0 -0
  16. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/_exporter_pool.py +0 -0
  17. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/_gepa_patch.py +0 -0
  18. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/_http.py +0 -0
  19. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/_program_patch.py +0 -0
  20. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/_rlm_patch.py +0 -0
  21. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/_runtime_tools.py +0 -0
  22. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/_timespec.py +0 -0
  23. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/_traces.py +0 -0
  24. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/_valsets.py +0 -0
  25. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/authored.py +0 -0
  26. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/callback.py +0 -0
  27. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/__init__.py +0 -0
  28. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/api_client.py +0 -0
  29. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/commands/__init__.py +0 -0
  30. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/commands/assessments.py +0 -0
  31. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/commands/auth.py +0 -0
  32. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/commands/datasets.py +0 -0
  33. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/commands/deployments.py +0 -0
  34. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/commands/evals.py +0 -0
  35. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/commands/login.py +0 -0
  36. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/commands/optimizations.py +0 -0
  37. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/commands/org.py +0 -0
  38. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/commands/programs.py +0 -0
  39. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
  40. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/commands/signatures.py +0 -0
  41. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/commands/tool_servers.py +0 -0
  42. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/commands/top.py +0 -0
  43. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/commands/traces.py +0 -0
  44. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/commands/valsets.py +0 -0
  45. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/credentials.py +0 -0
  46. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/deprecation.py +0 -0
  47. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/pagination.py +0 -0
  48. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/shell.py +0 -0
  49. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/trace_render.py +0 -0
  50. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/cli/tree_help.py +0 -0
  51. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/client.py +0 -0
  52. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/configuration.py +0 -0
  53. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/context.py +0 -0
  54. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/dataset_sync.py +0 -0
  55. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/datasets.py +0 -0
  56. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/decorators.py +0 -0
  57. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/deployment.py +0 -0
  58. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/deployment_metadata.py +0 -0
  59. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/eval_handler.py +0 -0
  60. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/execution.py +0 -0
  61. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/exporter.py +0 -0
  62. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/exporter_http.py +0 -0
  63. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/helpers.py +0 -0
  64. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/identity.py +0 -0
  65. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/imaging.py +0 -0
  66. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/ir/__init__.py +0 -0
  67. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/ir/decode.py +0 -0
  68. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/ir/encode.py +0 -0
  69. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/ir/hash.py +0 -0
  70. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/ir/program.py +0 -0
  71. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/ir/reflection.py +0 -0
  72. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/ir/types.py +0 -0
  73. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/ir/xxh64.py +0 -0
  74. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/lm_configuration.py +0 -0
  75. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/mcp_servers.py +0 -0
  76. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/models.py +0 -0
  77. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/optimization.py +0 -0
  78. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/optimizers/__init__.py +0 -0
  79. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/optimizers/gepa.py +0 -0
  80. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/optimizers/gepa_callback.py +0 -0
  81. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/optimizers/resume.py +0 -0
  82. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/optimizers/tracker.py +0 -0
  83. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/packaging.py +0 -0
  84. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/references.py +0 -0
  85. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/retry.py +0 -0
  86. {cmpnd-0.28.0 → cmpnd-0.30.0}/cmpnd/traces.py +0 -0
  87. {cmpnd-0.28.0 → cmpnd-0.30.0}/docs/2026-04-02-code-review.md +0 -0
  88. {cmpnd-0.28.0 → cmpnd-0.30.0}/docs/client.md +0 -0
  89. {cmpnd-0.28.0 → cmpnd-0.30.0}/docs/custom-proposer.md +0 -0
  90. {cmpnd-0.28.0 → cmpnd-0.30.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  91. {cmpnd-0.28.0 → cmpnd-0.30.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  92. {cmpnd-0.28.0 → cmpnd-0.30.0}/docs/reading-traces.md +0 -0
  93. {cmpnd-0.28.0 → cmpnd-0.30.0}/docs/sdk-instrumentation.md +0 -0
  94. {cmpnd-0.28.0 → cmpnd-0.30.0}/examples/generalizing_proposer.py +0 -0
  95. {cmpnd-0.28.0 → cmpnd-0.30.0}/examples/local_ollama.py +0 -0
  96. {cmpnd-0.28.0 → cmpnd-0.30.0}/ingest/LINEAGE.md +0 -0
  97. {cmpnd-0.28.0 → cmpnd-0.30.0}/ingest/README.md +0 -0
  98. {cmpnd-0.28.0 → cmpnd-0.30.0}/ingest/cmpnd_ingest/__init__.py +0 -0
  99. {cmpnd-0.28.0 → cmpnd-0.30.0}/ingest/cmpnd_ingest/bodies.py +0 -0
  100. {cmpnd-0.28.0 → cmpnd-0.30.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
  101. {cmpnd-0.28.0 → cmpnd-0.30.0}/ingest/cmpnd_ingest/protocols.py +0 -0
  102. {cmpnd-0.28.0 → cmpnd-0.30.0}/ingest/cmpnd_ingest/py.typed +0 -0
  103. {cmpnd-0.28.0 → cmpnd-0.30.0}/ingest/cmpnd_ingest/request_log.py +0 -0
  104. {cmpnd-0.28.0 → cmpnd-0.30.0}/ingest/cmpnd_ingest/retry.py +0 -0
  105. {cmpnd-0.28.0 → cmpnd-0.30.0}/ingest/pyproject.toml +0 -0
  106. {cmpnd-0.28.0 → cmpnd-0.30.0}/scripts/audit-sdist.sh +0 -0
  107. {cmpnd-0.28.0 → cmpnd-0.30.0}/scripts/capture_review_run.py +0 -0
  108. {cmpnd-0.28.0 → cmpnd-0.30.0}/scripts/check_release_artifacts.py +0 -0
  109. {cmpnd-0.28.0 → cmpnd-0.30.0}/scripts/fixtures/review_run.json.gz +0 -0
  110. {cmpnd-0.28.0 → cmpnd-0.30.0}/scripts/gepa_resume_bug.py +0 -0
  111. {cmpnd-0.28.0 → cmpnd-0.30.0}/scripts/release-stress.sh +0 -0
  112. {cmpnd-0.28.0 → cmpnd-0.30.0}/scripts/require_version_bump.py +0 -0
  113. {cmpnd-0.28.0 → cmpnd-0.30.0}/scripts/seed_review_run.py +0 -0
  114. {cmpnd-0.28.0 → cmpnd-0.30.0}/scripts/test-in-linux.sh +0 -0
  115. {cmpnd-0.28.0 → cmpnd-0.30.0}/stubs/dspy/__init__.pyi +0 -0
  116. {cmpnd-0.28.0 → cmpnd-0.30.0}/stubs/dspy/primitives/__init__.pyi +0 -0
  117. {cmpnd-0.28.0 → cmpnd-0.30.0}/stubs/dspy/primitives/prediction.pyi +0 -0
  118. {cmpnd-0.28.0 → cmpnd-0.30.0}/stubs/dspy/utils/__init__.pyi +0 -0
  119. {cmpnd-0.28.0 → cmpnd-0.30.0}/stubs/dspy/utils/callback.pyi +0 -0
  120. {cmpnd-0.28.0 → cmpnd-0.30.0}/stubs/dspy/utils/dummies.pyi +0 -0
  121. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/__init__.py +0 -0
  122. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/_deployment_signature_helpers.py +0 -0
  123. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/_exporter_helpers.py +0 -0
  124. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/_fake_clock.py +0 -0
  125. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/_identity_helpers.py +0 -0
  126. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/_resolve_refs.py +0 -0
  127. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/_traced_run.py +0 -0
  128. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/conftest.py +0 -0
  129. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/__init__.py +0 -0
  130. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/committee_task.py +0 -0
  131. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/conftest.py +0 -0
  132. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  133. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  134. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/fake_lm.py +0 -0
  135. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/helpers.py +0 -0
  136. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/target_capabilities.py +0 -0
  137. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_assessments_cli.py +0 -0
  138. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_capture_prompts.py +0 -0
  139. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_composite_module.py +0 -0
  140. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
  141. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_deploy_execute.py +0 -0
  142. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_deploy_programs.py +0 -0
  143. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_edge_cases.py +0 -0
  144. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_example_hash.py +0 -0
  145. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_gepa_optimization.py +0 -0
  146. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_interpreter_spans.py +0 -0
  147. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_lm_accuracy.py +0 -0
  148. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_module_tracing.py +0 -0
  149. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_optimization_lineage.py +0 -0
  150. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
  151. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_optimize_failures.py +0 -0
  152. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_optimize_loop.py +0 -0
  153. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_optimize_review_committee.py +0 -0
  154. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
  155. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_parallel_export.py +0 -0
  156. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_project_attribution.py +0 -0
  157. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_react_tool_spans.py +0 -0
  158. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_save_load.py +0 -0
  159. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_trace_structure.py +0 -0
  160. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_traces_read.py +0 -0
  161. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_user_attribution.py +0 -0
  162. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/e2e/test_valsets_cli.py +0 -0
  163. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/identity_vectors/01_predict_qa.json +0 -0
  164. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  165. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  166. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  167. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  168. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  169. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
  170. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/identity_vectors/08_opaque_module.json +0 -0
  171. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  172. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/identity_vectors/10_react.json +0 -0
  173. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/identity_vectors/11_refine.json +0 -0
  174. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/identity_vectors/12_retrieve.json +0 -0
  175. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/identity_vectors/13_knn.json +0 -0
  176. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/identity_vectors/14_parallel.json +0 -0
  177. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/identity_vectors/README.md +0 -0
  178. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/ingest_conformance.py +0 -0
  179. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/integration/__init__.py +0 -0
  180. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/integration/authored_optimize/judge.py +0 -0
  181. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
  182. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/integration/conftest.py +0 -0
  183. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
  184. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/integration/hosted_optimize_probe.py +0 -0
  185. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/integration/optimization_run_probe.py +0 -0
  186. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  187. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/integration/test_logs_renders_traces.py +0 -0
  188. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/integration/test_optimization_run.py +0 -0
  189. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/integration/test_optimizing_progress.py +0 -0
  190. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/integration/test_unhappy_path.py +0 -0
  191. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/load/__init__.py +0 -0
  192. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/load/__main__.py +0 -0
  193. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/load/drain.py +0 -0
  194. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/load/ingest.py +0 -0
  195. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/load/latency_transport.py +0 -0
  196. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/load/rlm_shape.py +0 -0
  197. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/load/rlm_size_report.py +0 -0
  198. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/load/sweep.py +0 -0
  199. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/load/test_rlm_shape.py +0 -0
  200. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/load/test_rlm_size_report.py +0 -0
  201. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/parity/__init__.py +0 -0
  202. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/parity/conftest.py +0 -0
  203. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/parity/seed_parity.py +0 -0
  204. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/parity/test_auth.py +0 -0
  205. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/parity/test_datasets.py +0 -0
  206. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/parity/test_deployments.py +0 -0
  207. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/parity/test_evals.py +0 -0
  208. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/parity/test_health.py +0 -0
  209. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/parity/test_modules.py +0 -0
  210. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/parity/test_optimizations.py +0 -0
  211. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/parity/test_programs.py +0 -0
  212. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/parity/test_signatures.py +0 -0
  213. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/parity/test_trace_streaming.py +0 -0
  214. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/parity/test_traces.py +0 -0
  215. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/span_reference_fixture.py +0 -0
  216. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/__init__.py +0 -0
  217. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/_provider.py +0 -0
  218. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/_readiness.py +0 -0
  219. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/_reliability.py +0 -0
  220. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/_result.py +0 -0
  221. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/mint_bedrock_token.py +0 -0
  222. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/test_deploy_programs.py +0 -0
  223. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/test_multi_client_stress.py +0 -0
  224. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/test_optimize_client_committee.py +0 -0
  225. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/test_optimize_loop.py +0 -0
  226. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
  227. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/test_optimize_stress.py +0 -0
  228. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/test_provider_roles.py +0 -0
  229. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/test_readiness.py +0 -0
  230. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/test_real_provider_twins.py +0 -0
  231. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/test_reliability.py +0 -0
  232. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/test_reliability_classify.py +0 -0
  233. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/test_rlm_wasm.py +0 -0
  234. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/stress/test_trace_search.py +0 -0
  235. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_adapter_reentrancy.py +0 -0
  236. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_assessments_api.py +0 -0
  237. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_authored.py +0 -0
  238. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_authored_bundle_collisions.py +0 -0
  239. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_authored_metric_contract.py +0 -0
  240. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_authored_named_models.py +0 -0
  241. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_authored_optimize.py +0 -0
  242. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_callback.py +0 -0
  243. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_candidate_lineage.py +0 -0
  244. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/__init__.py +0 -0
  245. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/conftest.py +0 -0
  246. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_api_client.py +0 -0
  247. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_assessments.py +0 -0
  248. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_auth.py +0 -0
  249. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_credentials.py +0 -0
  250. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_datasets.py +0 -0
  251. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_deployments.py +0 -0
  252. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_evals.py +0 -0
  253. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_login.py +0 -0
  254. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_main.py +0 -0
  255. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_optimizations.py +0 -0
  256. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_org.py +0 -0
  257. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_pagination.py +0 -0
  258. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_parity.py +0 -0
  259. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_programs.py +0 -0
  260. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_provider_credentials.py +0 -0
  261. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_schema_sample.py +0 -0
  262. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_shell.py +0 -0
  263. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_signatures.py +0 -0
  264. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_tool_servers.py +0 -0
  265. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_top.py +0 -0
  266. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_traces.py +0 -0
  267. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_traces_search_wire.py +0 -0
  268. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_tree_help.py +0 -0
  269. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cli/test_valsets.py +0 -0
  270. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_client.py +0 -0
  271. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_clustering/__init__.py +0 -0
  272. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_clustering/conftest.py +0 -0
  273. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_clustering/test_edge_cases.py +0 -0
  274. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_clustering/test_hash_consistency.py +0 -0
  275. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_clustering/test_type_conversion.py +0 -0
  276. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_cmp551_grouping_identity.py +0 -0
  277. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_configuration.py +0 -0
  278. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_context.py +0 -0
  279. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_context_propagation.py +0 -0
  280. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_dataset_polling.py +0 -0
  281. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_dataset_sync.py +0 -0
  282. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_datasets.py +0 -0
  283. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_decorators.py +0 -0
  284. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_deploy.py +0 -0
  285. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_deploy_mcp_tool_references.py +0 -0
  286. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_deployment_metadata.py +0 -0
  287. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_deployments_read.py +0 -0
  288. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_eval.py +0 -0
  289. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_eval_example_attribution.py +0 -0
  290. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_eval_start.py +0 -0
  291. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_eval_trace_grouping.py +0 -0
  292. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_exception_paths.py +0 -0
  293. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_execute.py +0 -0
  294. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_exporter.py +0 -0
  295. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_exporter_queue.py +0 -0
  296. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_exporter_real.py +0 -0
  297. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_fake_lm.py +0 -0
  298. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_gepa_callback.py +0 -0
  299. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_gepa_integration.py +0 -0
  300. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_gepa_resume.py +0 -0
  301. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_identity.py +0 -0
  302. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_imaging.py +0 -0
  303. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_import_weight.py +0 -0
  304. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_ingest_conformance.py +0 -0
  305. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_ingest_second_producer.py +0 -0
  306. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_integration.py +0 -0
  307. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_interpreter_callback.py +0 -0
  308. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_ir_encoder_properties.py +0 -0
  309. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_ir_golden_vectors.py +0 -0
  310. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_ir_roundtrip.py +0 -0
  311. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_lm_configuration.py +0 -0
  312. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_lm_usage_shapes.py +0 -0
  313. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_mcp_tools.py +0 -0
  314. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_models.py +0 -0
  315. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_optimization_progress.py +0 -0
  316. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_optimization_start.py +0 -0
  317. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_optimize.py +0 -0
  318. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_optimize_budget.py +0 -0
  319. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_optimize_lift.py +0 -0
  320. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_optimize_lms.py +0 -0
  321. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_optimize_proposer.py +0 -0
  322. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_optimize_reflection_lm.py +0 -0
  323. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_optimize_terminal_failure.py +0 -0
  324. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_packaging.py +0 -0
  325. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_packaging_callable_source.py +0 -0
  326. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_packaging_mcp_tools.py +0 -0
  327. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_packaging_program_source.py +0 -0
  328. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_poll_until.py +0 -0
  329. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_program_lineage_persist.py +0 -0
  330. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_project_payload.py +0 -0
  331. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_reflector_properties.py +0 -0
  332. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_reflector_smoke.py +0 -0
  333. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_release_stress_script.py +0 -0
  334. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_repro_reactv2_bugs.py +0 -0
  335. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_require_version_bump.py +0 -0
  336. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_retry.py +0 -0
  337. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_reversible_failures.py +0 -0
  338. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_rlm_patch.py +0 -0
  339. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_runtime_tools.py +0 -0
  340. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_schemas.py +0 -0
  341. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_seed_review_run.py +0 -0
  342. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_span_references.py +0 -0
  343. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_span_sent_once.py +0 -0
  344. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_trace_render.py +0 -0
  345. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_traces_read.py +0 -0
  346. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_tracked_gepa.py +0 -0
  347. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_tracker_enrich.py +0 -0
  348. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_truncation.py +0 -0
  349. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_valsets_api.py +0 -0
  350. {cmpnd-0.28.0 → cmpnd-0.30.0}/tests/test_xxh64.py +0 -0
  351. {cmpnd-0.28.0 → cmpnd-0.30.0}/todo.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cmpnd
3
- Version: 0.28.0
3
+ Version: 0.30.0
4
4
  Summary: DSPy observability and deployment SDK for cmpnd
5
5
  Project-URL: Homepage, https://cmpnd.ai
6
6
  Author-email: cmpnd <hello@cmpnd.ai>
@@ -1,8 +1,9 @@
1
1
  """Platform-operator commands, hidden behind CMPND_CLI_ADMIN=1.
2
2
 
3
- `cmpnd admin org create|set-owner|list|show|set-limit|set-github-login|elect-owner`
3
+ `cmpnd admin org create|invite|set-owner|list|show|set-limit|set-github-login|elect-owner`
4
4
  is the closed-beta org bootstrap and steward surface:
5
- stand up a shared org with its GitHub auto-join key, anoint its first owner,
5
+ stand up a shared org with its GitHub auto-join key, prepare one for a customer
6
+ with a pre-approved owner invite (``create --owner-email`` or ``invite``), anoint an owner,
6
7
  adjust its member cap or GitHub link, and grant ownership from the election
7
8
  queue. `cmpnd admin user …` is the operator's user-lifecycle surface (list,
8
9
  inspect, create, approve/reject the pending-signup queue, promote/demote admin,
@@ -42,8 +43,28 @@ def _register_org(sub: _SubParsersAction[ArgumentParser]) -> None:
42
43
  default="",
43
44
  help="GitHub org login for OAuth auto-join (optional)",
44
45
  )
46
+ create.add_argument(
47
+ "--owner-email",
48
+ dest="owner_email",
49
+ default="",
50
+ help="Prepare the org for this customer: mail them a pre-approved owner invite (optional)",
51
+ )
45
52
  create.set_defaults(func=handle_org_create)
46
53
 
54
+ invite_owner = osub.add_parser(
55
+ "invite", help="Mail someone a pre-approved invite that makes them an owner of the org when they accept"
56
+ )
57
+ invite_owner.add_argument("org_id", help="Organization id")
58
+ invite_owner.add_argument("email", help="The customer's email")
59
+ invite_owner.set_defaults(func=handle_org_invite_owner)
60
+
61
+ revoke_invite = osub.add_parser(
62
+ "revoke-invite", help="Withdraw the open owner invites sent to an email, so the link stops working"
63
+ )
64
+ revoke_invite.add_argument("org_id", help="Organization id")
65
+ revoke_invite.add_argument("email", help="The email the invite was sent to")
66
+ revoke_invite.set_defaults(func=handle_org_revoke_invite)
67
+
47
68
  set_owner = osub.add_parser("set-owner", help="Make an existing user the org's owner")
48
69
  set_owner.add_argument("org_id", help="Organization id")
49
70
  set_owner.add_argument("email", help="Owner email (must already be a provisioned user)")
@@ -169,9 +190,25 @@ def _register_feature_flags(sub: _SubParsersAction[ArgumentParser]) -> None:
169
190
 
170
191
 
171
192
  def handle_org_create(client: api_client.ApiClient, args: Any) -> Any:
193
+ # With --owner-email the server marks the org operator-seeded and mails the
194
+ # customer a pre-approved owner invite; redeeming it hands the org over.
195
+ body = {"name": args.name, "github_login": args.github_login}
196
+ if args.owner_email:
197
+ body["owner_email"] = args.owner_email
198
+ return client.post("/api/v1/admin/organizations", json=body)
199
+
200
+
201
+ def handle_org_invite_owner(client: api_client.ApiClient, args: Any) -> Any:
202
+ return client.post(
203
+ f"/api/v1/admin/organizations/{args.org_id}/owner-invite",
204
+ json={"email": args.email},
205
+ )
206
+
207
+
208
+ def handle_org_revoke_invite(client: api_client.ApiClient, args: Any) -> Any:
172
209
  return client.post(
173
- "/api/v1/admin/organizations",
174
- json={"name": args.name, "github_login": args.github_login},
210
+ f"/api/v1/admin/organizations/{args.org_id}/owner-invite/revoke",
211
+ json={"email": args.email},
175
212
  )
176
213
 
177
214
 
@@ -149,6 +149,7 @@ DOMAIN_VERBS: dict[str, str] = {
149
149
  "disable": "suspends an account and keeps its rows — the reversible half of delete",
150
150
  "reset-password": "sets a credential the caller cannot read back, so it is not an update to a visible field",
151
151
  "elect-owner": "grants ownership out of the election queue, which has its own gate",
152
+ "revoke-invite": "withdraws the open owner invites to an address; bare revoke would read as revoking the org",
152
153
  "run": "invokes a deployment; the response is the point, and nothing is stored under that name",
153
154
  "follow": "tails a record until it settles — a list over time rather than a snapshot",
154
155
  "select": "marks one candidate as the program's chosen version",
@@ -413,3 +413,52 @@ not store, so the endpoint's existing key stays in place. Plain
413
413
  `cmpnd login` never carries admin scopes, so an operator's everyday key has
414
414
  ordinary privilege. `--admin` does not combine with `--with-token`, because a
415
415
  pasted key keeps the scopes it was minted with.
416
+
417
+ `cmpnd admin org create NAME --owner-email EMAIL` prepares an org for a
418
+ customer. The server creates the org, marks it operator-seeded, and mails EMAIL
419
+ a pre-approved owner invite. The response carries the invite link as
420
+ `owner_invite.url` in case the email does not arrive. When the customer redeems
421
+ the link they become the org's owner, with no approval step, and `cmpnd admin
422
+ org show` reports the org handed off (`seed.handoff_pending: false`). `cmpnd
423
+ admin org invite ORG_ID EMAIL` does the same for an existing org, whatever
424
+ owners it already has: the invite is the admin's anointment, no different from
425
+ `set-owner`, so an org may gain as many owners this way as it wants. `cmpnd
426
+ admin org revoke-invite ORG_ID EMAIL` withdraws the open owner invites sent to
427
+ that address, so a mistaken link stops working; `cmpnd admin org show` lists
428
+ the addresses still pending under `seed.pending_owner_emails`.
429
+
430
+ The step-by-step way to prepare an org for a customer keeps every write under
431
+ the operator's own identity:
432
+
433
+ 1. `cmpnd admin org create NAME` and `cmpnd admin org set-owner ORG_ID
434
+ YOUR_EMAIL` to create the org and own it.
435
+ 2. Prepare it with an ordinary key: deploy programs, register credentials,
436
+ set limits.
437
+ 3. `cmpnd admin org invite ORG_ID CUSTOMER_EMAIL` to mail the customer a
438
+ pre-approved owner invite. They accept and become an owner beside you.
439
+ 4. `cmpnd org leave ORG_ID` once they have accepted; a co-owner may leave.
440
+ Revoke the key from step 2 as well, so no dead key lingers.
441
+
442
+ The customer then takes over with their own keys. Nothing the operator
443
+ prepared is bound to the operator's membership: credentials and tool servers
444
+ carry only the org, and a deployment keeps its creator after they leave.
445
+
446
+ 5. `cmpnd login` and `cmpnd auth me`: the org and the owner role.
447
+ 6. Rotate the credentials to their own: `cmpnd credentials create --target
448
+ PROVIDER` with the same target, and `cmpnd credentials create --kind mcp
449
+ --server URL --name NAME` with the same server and name, replace the sealed
450
+ keys in place; the tool server and the deployment keep pointing at them.
451
+ 7. `cmpnd tool-servers show SERVER_ID` and `cmpnd tool-servers refresh
452
+ SERVER_ID`: the tools and the allow-list, re-read under their key.
453
+ 8. `cmpnd ps`, `cmpnd sample ID`, `cmpnd run ID '<json>'`: the deployment runs
454
+ under their credential, and `cmpnd traces list` shows the trace as theirs.
455
+ 9. `cmpnd optimize gepa FILE --reflection-model MODEL --auto light` from the
456
+ program's source checkout, then `cmpnd optimizations show RUN_ID`: their
457
+ first optimization, spent on their own credential.
458
+
459
+ Both sides then verify. The operator's `cmpnd admin org show ORG_ID` lists the
460
+ customer as owner and not the operator, with `seed.handoff_pending` false and
461
+ `seed.handed_off_to_email` the customer; the operator's step-2 key gets 401.
462
+ The customer's `cmpnd ps`, `cmpnd run`, `cmpnd tool-servers show` and `cmpnd
463
+ optimizations show` all succeed under the rotated credentials, and the web app
464
+ shows them the program, its deployment, the traces and the optimization runs.
@@ -5,7 +5,7 @@ name = "cmpnd"
5
5
  # it's new) — no git tags. Bump with `uv version --bump {minor,patch}` or by
6
6
  # hand. Runtime `cmpnd.__version__` reads installed metadata, so it tracks
7
7
  # this automatically. See docs/plans/2026-07-02-sdk-pypi-tag-free-publish.md.
8
- version = "0.28.0"
8
+ version = "0.30.0"
9
9
  description = "DSPy observability and deployment SDK for cmpnd"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.10"
@@ -10,13 +10,66 @@ from cmpnd.cli.commands import admin
10
10
  class TestAdminOrg:
11
11
  def test_create_posts_name_and_github_login(self, mock_client):
12
12
  mock_client.post.return_value = {"id": "o_1", "name": "Acme", "github_login": "acme"}
13
- result = admin.handle_org_create(mock_client, Namespace(name="Acme", github_login="acme"))
13
+ result = admin.handle_org_create(mock_client, Namespace(name="Acme", github_login="acme", owner_email=""))
14
14
  mock_client.post.assert_called_once_with(
15
15
  "/api/v1/admin/organizations",
16
16
  json={"name": "Acme", "github_login": "acme"},
17
17
  )
18
18
  assert result["github_login"] == "acme"
19
19
 
20
+ def test_create_without_owner_email_sends_no_owner_email(self, mock_client):
21
+ mock_client.post.return_value = {"id": "o_1"}
22
+ admin.handle_org_create(mock_client, Namespace(name="Acme", github_login="", owner_email=""))
23
+ mock_client.post.assert_called_once_with(
24
+ "/api/v1/admin/organizations",
25
+ json={"name": "Acme", "github_login": ""},
26
+ )
27
+
28
+ def test_create_with_owner_email_prepares_the_org_for_its_customer(self, mock_client):
29
+ mock_client.post.return_value = {
30
+ "id": "o_1",
31
+ "owner_invite": {"role": "owner", "email": "founder@acme.com", "url": "https://x/auth/invite/t"},
32
+ }
33
+ result = admin.handle_org_create(
34
+ mock_client, Namespace(name="Acme", github_login="", owner_email="founder@acme.com")
35
+ )
36
+ mock_client.post.assert_called_once_with(
37
+ "/api/v1/admin/organizations",
38
+ json={"name": "Acme", "github_login": "", "owner_email": "founder@acme.com"},
39
+ )
40
+ assert result["owner_invite"]["role"] == "owner"
41
+
42
+ def test_invite_owner_posts_email(self, mock_client):
43
+ mock_client.post.return_value = {"role": "owner", "email": "founder@acme.com"}
44
+ result = admin.handle_org_invite_owner(mock_client, Namespace(org_id="o_1", email="founder@acme.com"))
45
+ mock_client.post.assert_called_once_with(
46
+ "/api/v1/admin/organizations/o_1/owner-invite",
47
+ json={"email": "founder@acme.com"},
48
+ )
49
+ assert result["role"] == "owner"
50
+
51
+ def test_revoke_invite_posts_email(self, mock_client):
52
+ mock_client.post.return_value = {"org_id": "o_1", "email": "founder@acme.com", "revoked": 1}
53
+ result = admin.handle_org_revoke_invite(mock_client, Namespace(org_id="o_1", email="founder@acme.com"))
54
+ mock_client.post.assert_called_once_with(
55
+ "/api/v1/admin/organizations/o_1/owner-invite/revoke",
56
+ json={"email": "founder@acme.com"},
57
+ )
58
+ assert result["revoked"] == 1
59
+
60
+ def test_create_parser_accepts_owner_email(self):
61
+ from argparse import ArgumentParser
62
+
63
+ parser = ArgumentParser()
64
+ admin.register(parser.add_subparsers())
65
+ args = parser.parse_args(["admin", "org", "create", "Acme", "--owner-email", "founder@acme.com"])
66
+ assert args.owner_email == "founder@acme.com"
67
+ assert args.func is admin.handle_org_create
68
+ args = parser.parse_args(["admin", "org", "invite", "o_1", "founder@acme.com"])
69
+ assert args.func is admin.handle_org_invite_owner
70
+ args = parser.parse_args(["admin", "org", "revoke-invite", "o_1", "founder@acme.com"])
71
+ assert args.func is admin.handle_org_revoke_invite
72
+
20
73
  def test_set_owner_posts_and_confirms(self, mock_client):
21
74
  mock_client.post.return_value = None # 204 No Content
22
75
  result = admin.handle_org_set_owner(mock_client, Namespace(org_id="o_1", email="owner@acme.com"))
@@ -496,7 +496,7 @@ wheels = [
496
496
 
497
497
  [[package]]
498
498
  name = "cmpnd"
499
- version = "0.28.0"
499
+ version = "0.30.0"
500
500
  source = { editable = "." }
501
501
  dependencies = [
502
502
  { name = "dspy" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes