graph-agents-cli 0.3.1__py3-none-any.whl

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 (291) hide show
  1. graph_agents_cli/__init__.py +26 -0
  2. graph_agents_cli/_api_policy.py +2145 -0
  3. graph_agents_cli/_approvals.py +400 -0
  4. graph_agents_cli/_build.py +186 -0
  5. graph_agents_cli/_build_info.json +7 -0
  6. graph_agents_cli/_chat_client.py +462 -0
  7. graph_agents_cli/_click.py +157 -0
  8. graph_agents_cli/_defaults.py +139 -0
  9. graph_agents_cli/_experiments.py +64 -0
  10. graph_agents_cli/_http.py +192 -0
  11. graph_agents_cli/_output.py +83 -0
  12. graph_agents_cli/_project.py +462 -0
  13. graph_agents_cli/_remote.py +220 -0
  14. graph_agents_cli/_response_schema.py +264 -0
  15. graph_agents_cli/_runner.py +319 -0
  16. graph_agents_cli/_skills_check.py +274 -0
  17. graph_agents_cli/_tools.py +189 -0
  18. graph_agents_cli/_trust.py +66 -0
  19. graph_agents_cli/api/__init__.py +15 -0
  20. graph_agents_cli/api/_changes.py +506 -0
  21. graph_agents_cli/api/_files.py +658 -0
  22. graph_agents_cli/api/cmd_api.py +2480 -0
  23. graph_agents_cli/deploy/__init__.py +15 -0
  24. graph_agents_cli/deploy/_config.py +171 -0
  25. graph_agents_cli/deploy/_image.py +128 -0
  26. graph_agents_cli/deploy/_kube.py +286 -0
  27. graph_agents_cli/deploy/_modes.py +234 -0
  28. graph_agents_cli/deploy/_preflight.py +370 -0
  29. graph_agents_cli/deploy/_values.py +168 -0
  30. graph_agents_cli/deploy/cmd_deploy.py +1866 -0
  31. graph_agents_cli/deploy/gitops.py +562 -0
  32. graph_agents_cli/deploy/local_load.py +273 -0
  33. graph_agents_cli/dev/__init__.py +13 -0
  34. graph_agents_cli/dev/cmd_build.py +131 -0
  35. graph_agents_cli/dev/cmd_install.py +78 -0
  36. graph_agents_cli/dev/cmd_lint.py +119 -0
  37. graph_agents_cli/dev/cmd_playground.py +297 -0
  38. graph_agents_cli/dev/policy_check.py +1287 -0
  39. graph_agents_cli/eval/__init__.py +22 -0
  40. graph_agents_cli/eval/_client.py +670 -0
  41. graph_agents_cli/eval/_common.py +177 -0
  42. graph_agents_cli/eval/_judge.py +168 -0
  43. graph_agents_cli/eval/_judge_runner.py +238 -0
  44. graph_agents_cli/eval/_paths.py +212 -0
  45. graph_agents_cli/eval/checks.py +581 -0
  46. graph_agents_cli/eval/cmd_analyze.py +278 -0
  47. graph_agents_cli/eval/cmd_compare.py +284 -0
  48. graph_agents_cli/eval/cmd_eval_group.py +80 -0
  49. graph_agents_cli/eval/cmd_generate.py +558 -0
  50. graph_agents_cli/eval/cmd_grade.py +466 -0
  51. graph_agents_cli/eval/cmd_metric.py +156 -0
  52. graph_agents_cli/eval/cmd_run.py +370 -0
  53. graph_agents_cli/eval/cmd_submit.py +400 -0
  54. graph_agents_cli/eval/config.py +435 -0
  55. graph_agents_cli/eval/dataset.py +350 -0
  56. graph_agents_cli/eval/gate.py +420 -0
  57. graph_agents_cli/eval/transcript.py +192 -0
  58. graph_agents_cli/extension/__init__.py +13 -0
  59. graph_agents_cli/extension/_compat.py +86 -0
  60. graph_agents_cli/extension/_loader.py +293 -0
  61. graph_agents_cli/extension/_manifest.py +135 -0
  62. graph_agents_cli/extension/_overrides.py +195 -0
  63. graph_agents_cli/extension/_paths.py +91 -0
  64. graph_agents_cli/extension/_refs.py +193 -0
  65. graph_agents_cli/extension/_resolver.py +453 -0
  66. graph_agents_cli/extension/_schema.py +106 -0
  67. graph_agents_cli/extension/_spec.py +253 -0
  68. graph_agents_cli/extension/_sync.py +102 -0
  69. graph_agents_cli/extension/_trust.py +58 -0
  70. graph_agents_cli/extension/cmd_extension_add.py +259 -0
  71. graph_agents_cli/extension/cmd_extension_group.py +57 -0
  72. graph_agents_cli/extension/cmd_extension_list.py +56 -0
  73. graph_agents_cli/extension/cmd_extension_remove.py +61 -0
  74. graph_agents_cli/extension/cmd_extension_update.py +195 -0
  75. graph_agents_cli/info/__init__.py +13 -0
  76. graph_agents_cli/info/cmd_info.py +222 -0
  77. graph_agents_cli/infra/__init__.py +15 -0
  78. graph_agents_cli/infra/checks.py +1169 -0
  79. graph_agents_cli/infra/cmd_infra.py +103 -0
  80. graph_agents_cli/main.py +591 -0
  81. graph_agents_cli/peer/__init__.py +15 -0
  82. graph_agents_cli/peer/_generate.py +254 -0
  83. graph_agents_cli/peer/cmd_peer.py +1151 -0
  84. graph_agents_cli/run/__init__.py +13 -0
  85. graph_agents_cli/run/_local_server.py +1157 -0
  86. graph_agents_cli/run/_signals.py +141 -0
  87. graph_agents_cli/run/cmd_approvals.py +530 -0
  88. graph_agents_cli/run/cmd_run.py +1421 -0
  89. graph_agents_cli/scaffold/__init__.py +19 -0
  90. graph_agents_cli/scaffold/agents/README.md +24 -0
  91. graph_agents_cli/scaffold/agents/empty_py/.template/templateconfig.yaml +22 -0
  92. graph_agents_cli/scaffold/agents/langgraph/.env.example +292 -0
  93. graph_agents_cli/scaffold/agents/langgraph/.template/templateconfig.yaml +28 -0
  94. graph_agents_cli/scaffold/agents/langgraph/Dockerfile +59 -0
  95. graph_agents_cli/scaffold/agents/langgraph/Dockerfile.langgraph-server +59 -0
  96. graph_agents_cli/scaffold/agents/langgraph/README.md +571 -0
  97. graph_agents_cli/scaffold/agents/langgraph/api-policy.yaml +60 -0
  98. graph_agents_cli/scaffold/agents/langgraph/app/__init__.py +20 -0
  99. graph_agents_cli/scaffold/agents/langgraph/app/agent.py +174 -0
  100. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/__init__.py +15 -0
  101. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a.py +2162 -0
  102. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a_client.py +1167 -0
  103. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/api_client.py +4220 -0
  104. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/approvals.py +1349 -0
  105. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/auth.py +1986 -0
  106. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/chat.py +2962 -0
  107. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/checkpointer.py +432 -0
  108. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/content.py +569 -0
  109. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/db.py +580 -0
  110. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/limits.py +203 -0
  111. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/metrics.py +231 -0
  112. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/middleware.py +361 -0
  113. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/model.py +611 -0
  114. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/playground.py +230 -0
  115. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/run_locks.py +459 -0
  116. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/structured.py +755 -0
  117. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/telemetry.py +681 -0
  118. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/threads.py +493 -0
  119. graph_agents_cli/scaffold/agents/langgraph/app/app_utils/token_exchange.py +959 -0
  120. graph_agents_cli/scaffold/agents/langgraph/app/fast_api_app.py +770 -0
  121. graph_agents_cli/scaffold/agents/langgraph/app/policies/__init__.py +55 -0
  122. graph_agents_cli/scaffold/agents/langgraph/app/policies/custom.py +97 -0
  123. graph_agents_cli/scaffold/agents/langgraph/app/tools/__init__.py +46 -0
  124. graph_agents_cli/scaffold/agents/langgraph/app/tools/example_api.py +92 -0
  125. graph_agents_cli/scaffold/agents/langgraph/app/tools/weather.py +33 -0
  126. graph_agents_cli/scaffold/agents/langgraph/langgraph.json +14 -0
  127. graph_agents_cli/scaffold/agents/langgraph/pyproject.toml +78 -0
  128. graph_agents_cli/scaffold/agents/langgraph/tests/conftest.py +376 -0
  129. graph_agents_cli/scaffold/agents/langgraph/tests/eval/datasets/basic-dataset.json +53 -0
  130. graph_agents_cli/scaffold/agents/langgraph/tests/eval/eval_config.yaml +32 -0
  131. graph_agents_cli/scaffold/agents/langgraph/tests/integration/approval_graph.py +137 -0
  132. graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_issuer.py +216 -0
  133. graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_openai.py +357 -0
  134. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_outcomes.py +569 -0
  135. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_relay.py +479 -0
  136. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_api_surface.py +812 -0
  137. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals.py +1367 -0
  138. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals_server.py +794 -0
  139. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor.py +497 -0
  140. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor_server.py +247 -0
  141. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_history_repair.py +278 -0
  142. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_model_apis.py +242 -0
  143. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_postgres.py +637 -0
  144. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_resilience_postgres.py +770 -0
  145. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_runtime_guardrails.py +854 -0
  146. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_e2e.py +340 -0
  147. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_runtime.py +989 -0
  148. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_answers.py +584 -0
  149. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_server.py +222 -0
  150. graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_token_exchange_issuer.py +650 -0
  151. graph_agents_cli/scaffold/agents/langgraph/tests/load_test/.results/.placeholder +0 -0
  152. graph_agents_cli/scaffold/agents/langgraph/tests/load_test/README.md +22 -0
  153. graph_agents_cli/scaffold/agents/langgraph/tests/load_test/conftest.py +21 -0
  154. graph_agents_cli/scaffold/agents/langgraph/tests/load_test/load_test.py +81 -0
  155. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_client.py +824 -0
  156. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_scoping.py +724 -0
  157. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client.py +1214 -0
  158. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client_hardening.py +716 -0
  159. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_policy_rpc.py +767 -0
  160. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_approval_ledger.py +1536 -0
  161. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_fake_model.py +115 -0
  162. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_jwt_policy.py +991 -0
  163. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_limits.py +310 -0
  164. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging.py +148 -0
  165. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging_hardening.py +271 -0
  166. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_policy.py +378 -0
  167. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_resilience.py +610 -0
  168. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_server_auth.py +702 -0
  169. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_structured.py +673 -0
  170. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_telemetry.py +404 -0
  171. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_thread_listing.py +255 -0
  172. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_threads.py +268 -0
  173. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_token_exchange.py +1320 -0
  174. graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_untrusted_content.py +393 -0
  175. graph_agents_cli/scaffold/agents/langgraph/uv-fastapi.lock +2084 -0
  176. graph_agents_cli/scaffold/agents/langgraph/uv-langgraph-server.lock +2106 -0
  177. graph_agents_cli/scaffold/agents/langgraph/{{cookiecutter.agent_guidance_filename}} +129 -0
  178. graph_agents_cli/scaffold/base_templates/_shared/graph-agents-cli-manifest.yaml +36 -0
  179. graph_agents_cli/scaffold/base_templates/python/.dockerignore +32 -0
  180. graph_agents_cli/scaffold/base_templates/python/.github/CODEOWNERS +30 -0
  181. graph_agents_cli/scaffold/base_templates/python/.github/agent.env +7 -0
  182. graph_agents_cli/scaffold/base_templates/python/.github/workflows/pr_checks.yaml +214 -0
  183. graph_agents_cli/scaffold/base_templates/python/.gitignore +209 -0
  184. graph_agents_cli/scaffold/base_templates/python/tests/unit/test_dummy.py +23 -0
  185. graph_agents_cli/scaffold/base_templates/python/{{cookiecutter.agent_guidance_filename}} +35 -0
  186. graph_agents_cli/scaffold/cmd_scaffold_group.py +49 -0
  187. graph_agents_cli/scaffold/commands/__init__.py +13 -0
  188. graph_agents_cli/scaffold/commands/create.py +1424 -0
  189. graph_agents_cli/scaffold/commands/enhance.py +1652 -0
  190. graph_agents_cli/scaffold/commands/upgrade.py +570 -0
  191. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/agent.env +12 -0
  192. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/promote-to-prod.yaml +371 -0
  193. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/staging.yaml +450 -0
  194. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-dev.yaml +43 -0
  195. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-prod.yaml +41 -0
  196. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-staging.yaml +43 -0
  197. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/.helmignore +14 -0
  198. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/Chart.yaml +21 -0
  199. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/examples/networkpolicy.yaml +103 -0
  200. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/NOTES.txt +48 -0
  201. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/_helpers.tpl +189 -0
  202. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/certificate.yaml +15 -0
  203. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/configmap.yaml +10 -0
  204. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/deployment.yaml +199 -0
  205. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/hpa.yaml +22 -0
  206. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/httproute.yaml +30 -0
  207. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/ingress.yaml +39 -0
  208. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/networkpolicy.yaml +48 -0
  209. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/pdb.yaml +13 -0
  210. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/postgresql-secret.yaml +37 -0
  211. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/service.yaml +15 -0
  212. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/serviceaccount.yaml +13 -0
  213. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/servicemonitor.yaml +42 -0
  214. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-dev.yaml +22 -0
  215. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-prod.yaml +45 -0
  216. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-staging.yaml +29 -0
  217. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values.yaml +396 -0
  218. graph_agents_cli/scaffold/deployment_targets/kubernetes/python/tests/integration/test_chart.py +269 -0
  219. graph_agents_cli/scaffold/deployment_targets/none/README.md +5 -0
  220. graph_agents_cli/scaffold/deployment_targets/none/python/README.md +6 -0
  221. graph_agents_cli/scaffold/utils/__init__.py +13 -0
  222. graph_agents_cli/scaffold/utils/backup.py +212 -0
  223. graph_agents_cli/scaffold/utils/build_record.py +257 -0
  224. graph_agents_cli/scaffold/utils/cli_options.py +184 -0
  225. graph_agents_cli/scaffold/utils/fs.py +83 -0
  226. graph_agents_cli/scaffold/utils/generate_locks.py +214 -0
  227. graph_agents_cli/scaffold/utils/generation_metadata.py +88 -0
  228. graph_agents_cli/scaffold/utils/keyedit.py +768 -0
  229. graph_agents_cli/scaffold/utils/keymerge.py +537 -0
  230. graph_agents_cli/scaffold/utils/language.py +138 -0
  231. graph_agents_cli/scaffold/utils/lock_utils.py +94 -0
  232. graph_agents_cli/scaffold/utils/logging.py +77 -0
  233. graph_agents_cli/scaffold/utils/manifest.py +292 -0
  234. graph_agents_cli/scaffold/utils/merge.py +970 -0
  235. graph_agents_cli/scaffold/utils/merge3.py +216 -0
  236. graph_agents_cli/scaffold/utils/openapi_seed.py +199 -0
  237. graph_agents_cli/scaffold/utils/remote_template.py +376 -0
  238. graph_agents_cli/scaffold/utils/template.py +1352 -0
  239. graph_agents_cli/scaffold/utils/upgrade.py +894 -0
  240. graph_agents_cli/scaffold/utils/version.py +438 -0
  241. graph_agents_cli/secrets/__init__.py +15 -0
  242. graph_agents_cli/secrets/_apply.py +954 -0
  243. graph_agents_cli/secrets/_required.py +188 -0
  244. graph_agents_cli/secrets/cmd_secrets.py +211 -0
  245. graph_agents_cli/setup/__init__.py +13 -0
  246. graph_agents_cli/setup/_antigravity.py +221 -0
  247. graph_agents_cli/setup/cmd_auth.py +1030 -0
  248. graph_agents_cli/setup/cmd_dev_token.py +513 -0
  249. graph_agents_cli/setup/cmd_setup.py +428 -0
  250. graph_agents_cli/setup/cmd_update.py +140 -0
  251. graph_agents_cli/skills/__init__.py +13 -0
  252. graph_agents_cli/skills/_bundle.py +65 -0
  253. graph_agents_cli/skills/data/README.md +19 -0
  254. graph_agents_cli/skills/data/graph-agents-cli-deploy/SKILL.md +357 -0
  255. graph_agents_cli/skills/data/graph-agents-cli-deploy/references/github-settings.md +113 -0
  256. graph_agents_cli/skills/data/graph-agents-cli-deploy/references/gitops.md +137 -0
  257. graph_agents_cli/skills/data/graph-agents-cli-deploy/references/kubernetes.md +315 -0
  258. graph_agents_cli/skills/data/graph-agents-cli-deploy/references/secrets.md +160 -0
  259. graph_agents_cli/skills/data/graph-agents-cli-eval/SKILL.md +303 -0
  260. graph_agents_cli/skills/data/graph-agents-cli-eval/references/dataset_schema.md +282 -0
  261. graph_agents_cli/skills/data/graph-agents-cli-eval/references/metrics-guide.md +143 -0
  262. graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/SKILL.md +659 -0
  263. graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langchain-models.md +124 -0
  264. graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langgraph.md +235 -0
  265. graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/template-contract.md +477 -0
  266. graph_agents_cli/skills/data/graph-agents-cli-observability/SKILL.md +231 -0
  267. graph_agents_cli/skills/data/graph-agents-cli-observability/references/langsmith.md +46 -0
  268. graph_agents_cli/skills/data/graph-agents-cli-observability/references/otel.md +59 -0
  269. graph_agents_cli/skills/data/graph-agents-cli-scaffold/SKILL.md +414 -0
  270. graph_agents_cli/skills/data/graph-agents-cli-scaffold/references/flags.md +134 -0
  271. graph_agents_cli/skills/data/graph-agents-cli-workflow/SKILL.md +478 -0
  272. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/brainstorming.md +118 -0
  273. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/commands.md +419 -0
  274. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/extension.md +156 -0
  275. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/internals.md +67 -0
  276. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/spec-template.md +56 -0
  277. graph_agents_cli/skills/data/graph-agents-cli-workflow/references/terminology.md +119 -0
  278. graph_agents_cli/system/__init__.py +15 -0
  279. graph_agents_cli/system/_apply.py +519 -0
  280. graph_agents_cli/system/_checks.py +1023 -0
  281. graph_agents_cli/system/_deploy.py +215 -0
  282. graph_agents_cli/system/_model.py +363 -0
  283. graph_agents_cli/system/_system.py +664 -0
  284. graph_agents_cli/system/_views.py +208 -0
  285. graph_agents_cli/system/cmd_system.py +423 -0
  286. graph_agents_cli-0.3.1.dist-info/METADATA +162 -0
  287. graph_agents_cli-0.3.1.dist-info/RECORD +291 -0
  288. graph_agents_cli-0.3.1.dist-info/WHEEL +4 -0
  289. graph_agents_cli-0.3.1.dist-info/entry_points.txt +2 -0
  290. graph_agents_cli-0.3.1.dist-info/licenses/LICENSE +201 -0
  291. graph_agents_cli-0.3.1.dist-info/licenses/NOTICE +19 -0
@@ -0,0 +1,497 @@
1
+ # Copyright 2026 graph-agents-cli contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Agents calling this agent for a user: one agent never reaches or decides another's work.
16
+
17
+ This agent plays `orders`, behind the `jwt` policy, with a gated `cancelOrder`
18
+ call to a backend that records every request (`httpx.MockTransport`). A fake
19
+ issuer (`fake_issuer.py`: a JWKS and RFC 8693 token exchange with nested `act`)
20
+ mints alice's own token and exchanges it for the agents that call `orders` for
21
+ her: `concierge`, and `billing` (called by concierge). The tests send what
22
+ those agents would, with their exchanged tokens, over A2A JSON-RPC and HTTP.
23
+
24
+ * The concierge's request pauses at the gated cancel. Billing, acting for the
25
+ same user, gets GetTask -32001, ListTasks with no row, and its decisions are
26
+ refused; the backend receives no POST.
27
+ * With `decide_with: relayed` naming the concierge, the concierge delivers the
28
+ person's decision (naming the approval's digest): the call is sent once and
29
+ the approval records `decided_via`.
30
+ * With `decide_with: direct` (the default), the concierge's relay is refused
31
+ with `approval_direct_only`, and alice's own token decides.
32
+ * alice's own token reads, lists and cancels the concierge's task (canceled in
33
+ place: one row, the concierge's); continuing it stays the concierge's.
34
+
35
+ Every test runs with the in-memory checkpointer, and on Postgres when
36
+ `TEST_POSTGRES_DSN` is set.
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ import asyncio
42
+ import json
43
+ import os
44
+ import uuid
45
+ from collections.abc import AsyncIterator, Iterator
46
+ from pathlib import Path
47
+ from typing import Any
48
+ from urllib.parse import urlsplit
49
+
50
+ # The environment must be in place before the app (and the graph) is imported.
51
+ os.environ.update(
52
+ {
53
+ "MODEL_PROVIDER": "fake",
54
+ "MODEL_NAME": "fake",
55
+ "CHECKPOINTER": "memory",
56
+ "AUTH_POLICY": "shared-bearer",
57
+ "API_KEY": "test-key",
58
+ "APP_ENV": "dev",
59
+ "TRACING_ENABLED": "false",
60
+ "RUNTIME": "fastapi",
61
+ "APP_URL": "http://testserver",
62
+ }
63
+ )
64
+
65
+ import httpx
66
+ import jwt
67
+ import pytest
68
+ from fake_issuer import FakeIssuer
69
+ from langchain.tools import ToolRuntime
70
+ from langchain_core.tools import tool
71
+
72
+ from {{cookiecutter.agent_directory}}.app_utils.api_client import get_client
73
+ from {{cookiecutter.agent_directory}}.app_utils.api_client import (
74
+ reset_policy_cache as reset_api_policy,
75
+ )
76
+ from {{cookiecutter.agent_directory}}.app_utils.auth import reset_policy_cache
77
+ from {{cookiecutter.agent_directory}}.fast_api_app import app
78
+
79
+ A2A_PATH = "/a2a/{{cookiecutter.agent_directory}}"
80
+ AUDIENCE = "orders"
81
+ PROMPT = "Cancel the order for 7"
82
+ SECRET = "s3cret"
83
+
84
+ POLICY = """
85
+ apis:
86
+ shop:
87
+ base_url_env: SHOP_API_BASE_URL
88
+ auth: none
89
+ allowed_methods: [GET, POST]
90
+ approval:
91
+ required_for:
92
+ operations:
93
+ - operationId: cancelOrder
94
+ path: /orders/{order_id}/cancel
95
+ methods: [POST]
96
+ approvers: [requester]
97
+ timeout_s: 60
98
+ """
99
+ RELAYED = " decide_with: relayed\n relayers: [concierge]\n"
100
+
101
+ # What the backend received.
102
+ SENT: list[httpx.Request] = []
103
+
104
+
105
+ def _backend(request: httpx.Request) -> httpx.Response:
106
+ SENT.append(request)
107
+ return httpx.Response(200, json={"cancelled": request.url.path})
108
+
109
+
110
+ @tool
111
+ async def cancel_order(order_id: str, runtime: ToolRuntime[Any]) -> str:
112
+ """Cancel an order by its id."""
113
+ context = getattr(runtime, "context", None)
114
+ client = get_client("shop", context=context, transport=httpx.MockTransport(_backend))
115
+ data = await client.post(
116
+ "/orders/{order_id}/cancel",
117
+ operation_id="cancelOrder",
118
+ path_params={"order_id": order_id},
119
+ json_body={"reason": "customer asked"},
120
+ )
121
+ return json.dumps(data)
122
+
123
+
124
+ @pytest.fixture(scope="module")
125
+ def issuer() -> Iterator[FakeIssuer]:
126
+ server = FakeIssuer(
127
+ {
128
+ "concierge": (SECRET, {"billing", AUDIENCE}),
129
+ "billing": (SECRET, {AUDIENCE}),
130
+ }
131
+ )
132
+ try:
133
+ yield server
134
+ finally:
135
+ server.close()
136
+
137
+
138
+ def _exchange(issuer: FakeIssuer, client: str, subject_token: str, audience: str) -> str:
139
+ """What `client` gets from the issuer for `subject_token` (RFC 8693), for `audience`."""
140
+ answer = httpx.post(
141
+ issuer.token_url,
142
+ data={
143
+ "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
144
+ "subject_token": subject_token,
145
+ "subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
146
+ "audience": audience,
147
+ },
148
+ auth=(client, SECRET),
149
+ timeout=10,
150
+ )
151
+ assert answer.status_code == 200, answer.text
152
+ return str(answer.json()["access_token"])
153
+
154
+
155
+ @pytest.fixture
156
+ def tokens(issuer: FakeIssuer) -> dict[str, str]:
157
+ """alice's own token at orders, and the tokens her agents present there for her."""
158
+ user = f"alice-{uuid.uuid4().hex[:8]}" # the A2A task store lives as long as the app
159
+ at_concierge = issuer.login(user, audience="concierge")
160
+ concierge = _exchange(issuer, "concierge", at_concierge, AUDIENCE)
161
+ at_billing = _exchange(issuer, "concierge", at_concierge, "billing")
162
+ billing = _exchange(issuer, "billing", at_billing, AUDIENCE) # act: billing <- concierge
163
+ return {
164
+ "alice": issuer.login(user, audience=AUDIENCE),
165
+ "concierge": concierge,
166
+ "billing": billing,
167
+ }
168
+
169
+
170
+ ADMIN_DSN = os.environ.get("TEST_POSTGRES_DSN", "")
171
+
172
+
173
+ @pytest.fixture(params=["memory", "postgres"])
174
+ async def database(
175
+ request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch
176
+ ) -> AsyncIterator[str]:
177
+ if request.param == "memory":
178
+ yield "memory"
179
+ return
180
+ if not ADMIN_DSN:
181
+ pytest.skip("TEST_POSTGRES_DSN is not set")
182
+ import psycopg
183
+
184
+ name = f"gac_test_{uuid.uuid4().hex[:12]}"
185
+ async with await psycopg.AsyncConnection.connect(ADMIN_DSN, autocommit=True) as admin:
186
+ await admin.execute(f'CREATE DATABASE "{name}"')
187
+ monkeypatch.setenv("CHECKPOINTER", "postgres")
188
+ monkeypatch.setenv("POSTGRES_DSN", urlsplit(ADMIN_DSN)._replace(path=f"/{name}").geturl())
189
+ yield "postgres"
190
+ async with await psycopg.AsyncConnection.connect(ADMIN_DSN, autocommit=True) as admin:
191
+ await admin.execute(f'DROP DATABASE IF EXISTS "{name}" WITH (FORCE)')
192
+
193
+
194
+ async def _orders(
195
+ issuer: FakeIssuer,
196
+ tmp_path: Path,
197
+ monkeypatch: pytest.MonkeyPatch,
198
+ use_test_tools: Any,
199
+ decide: str,
200
+ ) -> AsyncIterator[httpx.AsyncClient]:
201
+ policy = tmp_path / "api-policy.yaml"
202
+ policy.write_text(POLICY + decide, encoding="utf-8")
203
+ for name, value in {
204
+ "API_POLICY_PATH": str(policy),
205
+ "SHOP_API_BASE_URL": "http://shop.test",
206
+ "AUTH_POLICY": "jwt",
207
+ "AUTH_JWT_JWKS_URL": issuer.jwks_url,
208
+ "AUTH_JWT_ISSUER": issuer.issuer,
209
+ "AUTH_JWT_AUDIENCE": AUDIENCE,
210
+ "AUTH_ALLOWED_ACTORS": "concierge,billing",
211
+ }.items():
212
+ monkeypatch.setenv(name, value)
213
+ for name in ("AUTH_JWT_PUBLIC_KEY", "AUTH_READ_ACROSS_ROLES", "TRACE_CAPTURE"):
214
+ monkeypatch.delenv(name, raising=False)
215
+ reset_policy_cache()
216
+ reset_api_policy()
217
+ SENT.clear()
218
+ async with app.router.lifespan_context(app):
219
+ use_test_tools(cancel_order)
220
+ transport = httpx.ASGITransport(app=app, raise_app_exceptions=False)
221
+ async with httpx.AsyncClient(
222
+ transport=transport, base_url="http://testserver", timeout=30
223
+ ) as client:
224
+ started = asyncio.get_running_loop().time()
225
+ while (await client.get("/ready")).status_code != 200: # postgres: the schema
226
+ assert asyncio.get_running_loop().time() - started < 30, "not ready"
227
+ await asyncio.sleep(0.1)
228
+ yield client
229
+ reset_policy_cache()
230
+ reset_api_policy()
231
+
232
+
233
+ @pytest.fixture
234
+ async def relayed(
235
+ database: str,
236
+ issuer: FakeIssuer,
237
+ tmp_path: Path,
238
+ monkeypatch: pytest.MonkeyPatch,
239
+ use_test_tools: Any,
240
+ ) -> AsyncIterator[httpx.AsyncClient]:
241
+ """orders, its cancel gate opted into `decide_with: relayed` by the concierge."""
242
+ async for client in _orders(issuer, tmp_path, monkeypatch, use_test_tools, RELAYED):
243
+ yield client
244
+
245
+
246
+ @pytest.fixture
247
+ async def direct(
248
+ database: str,
249
+ issuer: FakeIssuer,
250
+ tmp_path: Path,
251
+ monkeypatch: pytest.MonkeyPatch,
252
+ use_test_tools: Any,
253
+ ) -> AsyncIterator[httpx.AsyncClient]:
254
+ """orders, its cancel gate at the default `decide_with: direct`."""
255
+ async for client in _orders(issuer, tmp_path, monkeypatch, use_test_tools, ""):
256
+ yield client
257
+
258
+
259
+ async def _rpc(client: httpx.AsyncClient, token: str, method: str, params: dict) -> dict:
260
+ r = await client.post(
261
+ A2A_PATH,
262
+ json={"jsonrpc": "2.0", "id": uuid.uuid4().hex, "method": method, "params": params},
263
+ headers={"Authorization": f"Bearer {token}", "A2A-Version": "1.0"},
264
+ )
265
+ assert r.status_code == 200, r.text
266
+ return r.json()
267
+
268
+
269
+ def _message(task: dict[str, Any] | None = None, **part: Any) -> dict[str, Any]:
270
+ fields = {"taskId": task["id"], "contextId": task["contextId"]} if task else {}
271
+ return {
272
+ "message": {
273
+ "messageId": uuid.uuid4().hex,
274
+ "role": "ROLE_USER",
275
+ "parts": [part],
276
+ **fields,
277
+ }
278
+ }
279
+
280
+
281
+ def _approval_part(task: dict[str, Any]) -> dict[str, Any]:
282
+ data = next(p["data"] for p in task["status"]["message"]["parts"] if "data" in p)
283
+ assert data["type"] == "approval_request"
284
+ return data["approval"]
285
+
286
+
287
+ def _note(task: dict[str, Any]) -> str:
288
+ return "".join(p.get("text", "") for p in task["status"]["message"]["parts"])
289
+
290
+
291
+ async def _paused_by_concierge(client: httpx.AsyncClient, tokens: dict[str, str]) -> Any:
292
+ sent = await _rpc(client, tokens["concierge"], "SendMessage", _message(text=PROMPT))
293
+ task = sent["result"]["task"]
294
+ assert task["status"]["state"] == "TASK_STATE_INPUT_REQUIRED", task
295
+ approval = _approval_part(task)
296
+ assert approval["path"] == "/orders/7/cancel" and approval["status"] == "pending"
297
+ assert approval["requester_actor"] == "concierge"
298
+ assert approval["digest"].startswith("sha256:")
299
+ assert SENT == []
300
+ return task, approval
301
+
302
+
303
+ def _decision(approval: dict[str, Any], **extra: Any) -> dict[str, Any]:
304
+ return {"data": {"approval_id": approval["approval_id"], "decision": "approve", **extra}}
305
+
306
+
307
+ async def test_another_agent_for_the_same_user_cannot_reach_or_decide(
308
+ relayed: httpx.AsyncClient, tokens: dict[str, str]
309
+ ) -> None:
310
+ client = relayed
311
+ task, approval = await _paused_by_concierge(client, tokens)
312
+ billing = tokens["billing"]
313
+ # Billing's token: the task is not found, not listed, not continued, not decided.
314
+ got = await _rpc(client, billing, "GetTask", {"id": task["id"]})
315
+ assert got["error"]["code"] == -32001
316
+ listed = await _rpc(client, billing, "ListTasks", {})
317
+ assert listed["result"].get("tasks", []) == []
318
+ decided = await _rpc(
319
+ client, billing, "SendMessage", _message(task, **_decision(approval, digest="x"))
320
+ )
321
+ assert decided["error"]["code"] == -32001
322
+ on_context = await _rpc(
323
+ client,
324
+ billing,
325
+ "SendMessage",
326
+ {"message": {**_message(**_decision(approval))["message"], "contextId": task["contextId"]}},
327
+ )
328
+ assert on_context["result"]["task"]["status"]["state"] == "TASK_STATE_FAILED"
329
+ assert "another principal" in _note(on_context["result"]["task"])
330
+ http = await client.post(
331
+ f"/threads/{task['contextId']}/approvals/{approval['approval_id']}",
332
+ json={"decision": "approve", "digest": approval["digest"]},
333
+ headers={"Authorization": f"Bearer {billing}"},
334
+ )
335
+ assert http.status_code == 403 and http.json()["code"] == "not_an_approver"
336
+ seen = await client.get(
337
+ f"/threads/{task['contextId']}/approvals", headers={"Authorization": f"Bearer {billing}"}
338
+ )
339
+ assert seen.status_code == 403
340
+ # The order is unchanged: the backend received nothing.
341
+ assert SENT == []
342
+ # The concierge's own relay, naming the digest the person approved, is sent once.
343
+ missing = await _rpc(
344
+ client, tokens["concierge"], "SendMessage", _message(task, **_decision(approval))
345
+ )
346
+ assert missing["result"]["task"]["status"]["state"] == "TASK_STATE_INPUT_REQUIRED"
347
+ assert "approval_digest_mismatch" in _note(missing["result"]["task"])
348
+ assert SENT == []
349
+ done = await _rpc(
350
+ client,
351
+ tokens["concierge"],
352
+ "SendMessage",
353
+ _message(task, **_decision(approval, digest=approval["digest"])),
354
+ )
355
+ assert done["result"]["task"]["status"]["state"] == "TASK_STATE_COMPLETED", done
356
+ assert [(r.method, r.url.path) for r in SENT] == [("POST", "/orders/7/cancel")]
357
+ # The person sees who relayed it.
358
+ listed = await client.get(
359
+ f"/threads/{task['contextId']}/approvals",
360
+ headers={"Authorization": f"Bearer {tokens['alice']}"},
361
+ )
362
+ [record] = listed.json()
363
+ assert (record["status"], record["decided_via"], record["requester_actor"]) == (
364
+ "approved",
365
+ "concierge",
366
+ "concierge",
367
+ )
368
+ again = await _rpc(
369
+ client,
370
+ tokens["concierge"],
371
+ "SendMessage",
372
+ _message(task, **_decision(approval, digest=approval["digest"])),
373
+ )
374
+ assert "error" in again or again["result"]["task"]["status"]["state"] != "TASK_STATE_COMPLETED"
375
+ assert len(SENT) == 1
376
+
377
+
378
+ async def test_under_direct_the_relay_is_refused_and_the_person_decides(
379
+ direct: httpx.AsyncClient, tokens: dict[str, str]
380
+ ) -> None:
381
+ client = direct
382
+ task, approval = await _paused_by_concierge(client, tokens)
383
+ assert approval["decide_with"] == "direct"
384
+ relayed = await _rpc(
385
+ client,
386
+ tokens["concierge"],
387
+ "SendMessage",
388
+ _message(task, **_decision(approval, digest=approval["digest"])),
389
+ )
390
+ refused = relayed["result"]["task"]
391
+ assert refused["status"]["state"] == "TASK_STATE_INPUT_REQUIRED"
392
+ assert "approval_direct_only" in _note(refused)
393
+ assert "not relayed by agent concierge" in _note(refused)
394
+ http = await client.post(
395
+ f"/threads/{task['contextId']}/approvals/{approval['approval_id']}",
396
+ json={"decision": "approve", "digest": approval["digest"]},
397
+ headers={"Authorization": f"Bearer {tokens['concierge']}"},
398
+ )
399
+ assert http.status_code == 403 and http.json()["code"] == "approval_direct_only"
400
+ assert SENT == []
401
+ # alice, at orders with her own token, sees the approval her agent's run asked for ...
402
+ seen = await client.get(
403
+ f"/threads/{task['contextId']}/approvals",
404
+ headers={"Authorization": f"Bearer {tokens['alice']}"},
405
+ )
406
+ assert [a["approval_id"] for a in seen.json()] == [approval["approval_id"]]
407
+ # ... and decides it; a digest from another view of the call is refused first.
408
+ stale = await client.post(
409
+ f"/threads/{task['contextId']}/approvals/{approval['approval_id']}",
410
+ json={"decision": "approve", "digest": "sha256:" + "0" * 64},
411
+ headers={"Authorization": f"Bearer {tokens['alice']}"},
412
+ )
413
+ assert stale.status_code == 409 and stale.json()["code"] == "approval_digest_mismatch"
414
+ decided = await client.post(
415
+ f"/threads/{task['contextId']}/approvals/{approval['approval_id']}",
416
+ json={"decision": "approve"},
417
+ headers={"Authorization": f"Bearer {tokens['alice']}"},
418
+ )
419
+ assert decided.status_code == 200, decided.text
420
+ assert "message.end" in decided.text
421
+ assert [(r.method, r.url.path) for r in SENT] == [("POST", "/orders/7/cancel")]
422
+ [record] = (
423
+ await client.get(
424
+ f"/threads/{task['contextId']}/approvals",
425
+ headers={"Authorization": f"Bearer {tokens['alice']}"},
426
+ )
427
+ ).json()
428
+ assert (record["status"], record["decided_via"]) == ("approved", None)
429
+
430
+
431
+ async def test_an_unlisted_agent_is_refused_before_anything_runs(
432
+ direct: httpx.AsyncClient, tokens: dict[str, str], monkeypatch: pytest.MonkeyPatch
433
+ ) -> None:
434
+ monkeypatch.setenv("AUTH_ALLOWED_ACTORS", "concierge")
435
+ r = await direct.post(
436
+ A2A_PATH,
437
+ json={
438
+ "jsonrpc": "2.0",
439
+ "id": "1",
440
+ "method": "SendMessage",
441
+ "params": _message(text=PROMPT),
442
+ },
443
+ headers={"Authorization": f"Bearer {tokens['billing']}", "A2A-Version": "1.0"},
444
+ )
445
+ assert r.status_code == 403
446
+ assert r.json() == {
447
+ "detail": "Delegated caller billing is not allowed here (AUTH_ALLOWED_ACTORS)."
448
+ }
449
+ assert SENT == []
450
+
451
+
452
+ async def _task_rows(database: str, task_id: str) -> list[tuple[str, str]]:
453
+ """(owner, state) of every stored row of `task_id` (Postgres); [] under memory."""
454
+ if database != "postgres":
455
+ return []
456
+ import psycopg
457
+
458
+ async with await psycopg.AsyncConnection.connect(os.environ["POSTGRES_DSN"]) as conn:
459
+ cursor = await conn.execute(
460
+ "SELECT owner, state FROM a2a_tasks WHERE task_id = %s ORDER BY owner", (task_id,)
461
+ )
462
+ return [(str(owner), str(state)) for owner, state in await cursor.fetchall()]
463
+
464
+
465
+ async def test_the_person_reads_lists_and_cancels_their_agents_task(
466
+ direct: httpx.AsyncClient, tokens: dict[str, str], database: str
467
+ ) -> None:
468
+ """The owner's decision of 2026-09-28: with their own token, the person reads, lists and
469
+ cancels the task the concierge started for them; continuing it stays the concierge's, and
470
+ billing (another agent for the same person) still sees nothing."""
471
+ client = direct
472
+ task, _approval = await _paused_by_concierge(client, tokens)
473
+ alice = tokens["alice"]
474
+ subject = jwt.decode(alice, options={"verify_signature": False})["sub"]
475
+ got = await _rpc(client, alice, "GetTask", {"id": task["id"]})
476
+ assert got["result"]["id"] == task["id"]
477
+ assert got["result"]["status"]["state"] == "TASK_STATE_INPUT_REQUIRED"
478
+ listed = await _rpc(client, alice, "ListTasks", {})
479
+ assert [t["id"] for t in listed["result"]["tasks"]] == [task["id"]]
480
+ continued = await _rpc(client, alice, "SendMessage", _message(task, text="hello"))
481
+ assert continued["error"]["code"] == -32001
482
+ for method in ("GetTask", "CancelTask"):
483
+ assert (await _rpc(client, tokens["billing"], method, {"id": task["id"]}))["error"][
484
+ "code"
485
+ ] == -32001
486
+ assert (await _rpc(client, tokens["billing"], "ListTasks", {}))["result"].get("tasks", []) == []
487
+
488
+ canceled = await _rpc(client, alice, "CancelTask", {"id": task["id"]})
489
+ assert canceled["result"]["status"]["state"] == "TASK_STATE_CANCELED", canceled
490
+ seen = await _rpc(client, tokens["concierge"], "GetTask", {"id": task["id"]})
491
+ assert seen["result"]["status"]["state"] == "TASK_STATE_CANCELED"
492
+ if database == "postgres":
493
+ # One row, the concierge's, canceled where it is: no copy under the person's key.
494
+ assert await _task_rows(database, task["id"]) == [
495
+ (f"{subject}\x1fconcierge", "TASK_STATE_CANCELED")
496
+ ]
497
+ assert SENT == []