healthcloud-cli 3.7.0__tar.gz → 3.7.1__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.
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/PKG-INFO +33 -17
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/README.md +32 -16
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/departments/operations.py +74 -5
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/policies/operations.py +73 -5
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/results.py +53 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/skills/operations.py +58 -4
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/teams/operations.py +58 -4
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/pyproject.toml +1 -1
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/.gitignore +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/hatch_build.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/__init__.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/admins/__init__.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/admins/commands.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/admins/group.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/admins/operations.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/__init__.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/agents/__init__.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/agents/commands.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/agents/group.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/agents/operations.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/agents/projections.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/auth.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/commands.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/compatibility.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/context.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/data_sources/__init__.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/data_sources/commands.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/data_sources/group.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/data_sources/operations.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/data_sources/projections.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/departments/__init__.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/departments/commands.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/departments/group.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/departments/projections.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/domain_errors.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/errors.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/group.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/guardrails/__init__.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/guardrails/commands.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/guardrails/group.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/guardrails/operations.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/guardrails/projections.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/organization/__init__.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/organization/commands.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/organization/group.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/organization/operations.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/organization/projections.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/output.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/policies/__init__.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/policies/commands.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/policies/group.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/policies/projections.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/pretty.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/registry.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/session.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/sessions/__init__.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/sessions/commands.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/sessions/group.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/sessions/operations.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/sessions/projections.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/skills/__init__.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/skills/commands.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/skills/group.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/skills/projections.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/target.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/teams/__init__.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/teams/commands.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/teams/group.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/teams/projections.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/tools/__init__.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/tools/commands.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/tools/group.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/tools/operations.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/tools/projections.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/tuning.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/ui.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/windows_files.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/cli.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/context.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/contracts/__init__.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/contracts/mcp-tools.manifest.json +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/main.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/manifest.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/output.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/params.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/tenants/__init__.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/tenants/commands.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/tenants/group.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/tenants/operations.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/util.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/scripts/__init__.py +0 -0
- {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/scripts/bundle_manifest.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: healthcloud-cli
|
|
3
|
-
Version: 3.7.
|
|
3
|
+
Version: 3.7.1
|
|
4
4
|
Summary: Command-line interface for the HealthCloud MCP gateway, generated from the shared MCP tool manifest.
|
|
5
5
|
Project-URL: Issues, https://github.com/healthcloudservices/healthcloud-services/issues
|
|
6
6
|
Project-URL: Repository, https://github.com/healthcloudservices/healthcloud-services
|
|
@@ -751,10 +751,14 @@ uv run --frozen hc ams departments delete --environment dev --tenant-id TENANT_I
|
|
|
751
751
|
reports a `validation` refusal that names the department, the tenant that
|
|
752
752
|
received it, and tells you to run `hc ams departments get` — it never claims
|
|
753
753
|
a landed write was a stopped no-op.
|
|
754
|
-
- If the transport times out
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
754
|
+
- If the transport times out, the connection drops mid-write, or the server
|
|
755
|
+
answers an HTTP 5xx/408 before a readable result, the write's outcome is
|
|
756
|
+
unknown: the CLI exits 1 with an `unknown` envelope
|
|
757
|
+
(`verification.saved_state: unknown`, `verification.side_effects:
|
|
758
|
+
unverified`, `data.mutation_replayed: false`, and
|
|
759
|
+
`data.reconciliation.status: not_read`) whose message says the outcome is
|
|
760
|
+
unknown and to check `hc ams departments get` before retrying. It never
|
|
761
|
+
re-sends the write and never re-reads on its own.
|
|
758
762
|
|
|
759
763
|
Read-only team listing and inspection are also complete and unpaged. The bounded
|
|
760
764
|
projection omits descriptions and creator identities; it includes evaluation-suite
|
|
@@ -797,10 +801,14 @@ uv run --frozen hc ams teams delete --environment dev --tenant-id TENANT_ID --
|
|
|
797
801
|
`validation` refusal that reports the landed write truthfully: it names the
|
|
798
802
|
team and the tenant that actually received it and points to `hc ams teams
|
|
799
803
|
get` — it never claims a landed write was stopped.
|
|
800
|
-
- If the transport times out
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
+
- If the transport times out, the connection drops mid-write, or the server
|
|
805
|
+
answers an HTTP 5xx/408 before a readable result, the write's outcome is
|
|
806
|
+
unknown: the CLI exits 1 with an `unknown` envelope
|
|
807
|
+
(`verification.saved_state: unknown`, `verification.side_effects:
|
|
808
|
+
unverified`, `data.mutation_replayed: false`, and
|
|
809
|
+
`data.reconciliation.status: not_read`) whose message says the outcome is
|
|
810
|
+
unknown and to check `hc ams teams get` before retrying. It never re-sends
|
|
811
|
+
the write and never re-reads on its own.
|
|
804
812
|
|
|
805
813
|
The organization hierarchy is the backend-built department/team/agent tree. The
|
|
806
814
|
CLI preserves its order and nesting without issuing entity reads, joining records,
|
|
@@ -862,10 +870,14 @@ uv run --frozen hc ams skills delete --environment dev --tenant-id TENANT_ID --s
|
|
|
862
870
|
landed write truthfully: it names the skill and the tenant that actually
|
|
863
871
|
received it and points to `hc ams skills get` — it never claims a landed
|
|
864
872
|
write was stopped.
|
|
865
|
-
- If the transport times out
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
873
|
+
- If the transport times out, the connection drops mid-write, or the server
|
|
874
|
+
answers an HTTP 5xx/408 before a readable result, the write's outcome is
|
|
875
|
+
unknown: the CLI exits 1 with an `unknown` envelope
|
|
876
|
+
(`verification.saved_state: unknown`, `verification.side_effects:
|
|
877
|
+
unverified`, `data.mutation_replayed: false`, and
|
|
878
|
+
`data.reconciliation.status: not_read`) whose message says the outcome is
|
|
879
|
+
unknown and to check `hc ams skills get` before retrying. It never re-sends
|
|
880
|
+
the write and never re-reads on its own.
|
|
869
881
|
|
|
870
882
|
Read-only policy catalog listing and inspection are complete and unpaged. The
|
|
871
883
|
bounded projection includes only policy IDs, names, department IDs, guardrail
|
|
@@ -954,10 +966,14 @@ uv run --frozen hc ams policies delete --environment dev --tenant-id TENANT_ID -
|
|
|
954
966
|
landed write truthfully: it names the policy and the tenant that actually
|
|
955
967
|
received it and points to `hc ams policies get` — it never claims a landed
|
|
956
968
|
write was stopped.
|
|
957
|
-
- If the transport times out
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
969
|
+
- If the transport times out, the connection drops mid-write, or the server
|
|
970
|
+
answers an HTTP 5xx/408 before a readable result, the write's outcome is
|
|
971
|
+
unknown: the CLI exits 1 with an `unknown` envelope
|
|
972
|
+
(`verification.saved_state: unknown`, `verification.side_effects:
|
|
973
|
+
unverified`, `data.mutation_replayed: false`, and
|
|
974
|
+
`data.reconciliation.status: not_read`) whose message says the outcome is
|
|
975
|
+
unknown and to check `hc ams policies get` before retrying. It never
|
|
976
|
+
re-sends the write and never re-reads on its own.
|
|
961
977
|
|
|
962
978
|
Governed authoring sessions and their messages are first-class commands over the
|
|
963
979
|
VibeCoder session routes. Creating a session and appending a message make no model
|
|
@@ -733,10 +733,14 @@ uv run --frozen hc ams departments delete --environment dev --tenant-id TENANT_I
|
|
|
733
733
|
reports a `validation` refusal that names the department, the tenant that
|
|
734
734
|
received it, and tells you to run `hc ams departments get` — it never claims
|
|
735
735
|
a landed write was a stopped no-op.
|
|
736
|
-
- If the transport times out
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
736
|
+
- If the transport times out, the connection drops mid-write, or the server
|
|
737
|
+
answers an HTTP 5xx/408 before a readable result, the write's outcome is
|
|
738
|
+
unknown: the CLI exits 1 with an `unknown` envelope
|
|
739
|
+
(`verification.saved_state: unknown`, `verification.side_effects:
|
|
740
|
+
unverified`, `data.mutation_replayed: false`, and
|
|
741
|
+
`data.reconciliation.status: not_read`) whose message says the outcome is
|
|
742
|
+
unknown and to check `hc ams departments get` before retrying. It never
|
|
743
|
+
re-sends the write and never re-reads on its own.
|
|
740
744
|
|
|
741
745
|
Read-only team listing and inspection are also complete and unpaged. The bounded
|
|
742
746
|
projection omits descriptions and creator identities; it includes evaluation-suite
|
|
@@ -779,10 +783,14 @@ uv run --frozen hc ams teams delete --environment dev --tenant-id TENANT_ID --
|
|
|
779
783
|
`validation` refusal that reports the landed write truthfully: it names the
|
|
780
784
|
team and the tenant that actually received it and points to `hc ams teams
|
|
781
785
|
get` — it never claims a landed write was stopped.
|
|
782
|
-
- If the transport times out
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
+
- If the transport times out, the connection drops mid-write, or the server
|
|
787
|
+
answers an HTTP 5xx/408 before a readable result, the write's outcome is
|
|
788
|
+
unknown: the CLI exits 1 with an `unknown` envelope
|
|
789
|
+
(`verification.saved_state: unknown`, `verification.side_effects:
|
|
790
|
+
unverified`, `data.mutation_replayed: false`, and
|
|
791
|
+
`data.reconciliation.status: not_read`) whose message says the outcome is
|
|
792
|
+
unknown and to check `hc ams teams get` before retrying. It never re-sends
|
|
793
|
+
the write and never re-reads on its own.
|
|
786
794
|
|
|
787
795
|
The organization hierarchy is the backend-built department/team/agent tree. The
|
|
788
796
|
CLI preserves its order and nesting without issuing entity reads, joining records,
|
|
@@ -844,10 +852,14 @@ uv run --frozen hc ams skills delete --environment dev --tenant-id TENANT_ID --s
|
|
|
844
852
|
landed write truthfully: it names the skill and the tenant that actually
|
|
845
853
|
received it and points to `hc ams skills get` — it never claims a landed
|
|
846
854
|
write was stopped.
|
|
847
|
-
- If the transport times out
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
855
|
+
- If the transport times out, the connection drops mid-write, or the server
|
|
856
|
+
answers an HTTP 5xx/408 before a readable result, the write's outcome is
|
|
857
|
+
unknown: the CLI exits 1 with an `unknown` envelope
|
|
858
|
+
(`verification.saved_state: unknown`, `verification.side_effects:
|
|
859
|
+
unverified`, `data.mutation_replayed: false`, and
|
|
860
|
+
`data.reconciliation.status: not_read`) whose message says the outcome is
|
|
861
|
+
unknown and to check `hc ams skills get` before retrying. It never re-sends
|
|
862
|
+
the write and never re-reads on its own.
|
|
851
863
|
|
|
852
864
|
Read-only policy catalog listing and inspection are complete and unpaged. The
|
|
853
865
|
bounded projection includes only policy IDs, names, department IDs, guardrail
|
|
@@ -936,10 +948,14 @@ uv run --frozen hc ams policies delete --environment dev --tenant-id TENANT_ID -
|
|
|
936
948
|
landed write truthfully: it names the policy and the tenant that actually
|
|
937
949
|
received it and points to `hc ams policies get` — it never claims a landed
|
|
938
950
|
write was stopped.
|
|
939
|
-
- If the transport times out
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
951
|
+
- If the transport times out, the connection drops mid-write, or the server
|
|
952
|
+
answers an HTTP 5xx/408 before a readable result, the write's outcome is
|
|
953
|
+
unknown: the CLI exits 1 with an `unknown` envelope
|
|
954
|
+
(`verification.saved_state: unknown`, `verification.side_effects:
|
|
955
|
+
unverified`, `data.mutation_replayed: false`, and
|
|
956
|
+
`data.reconciliation.status: not_read`) whose message says the outcome is
|
|
957
|
+
unknown and to check `hc ams policies get` before retrying. It never
|
|
958
|
+
re-sends the write and never re-reads on its own.
|
|
943
959
|
|
|
944
960
|
Governed authoring sessions and their messages are first-class commands over the
|
|
945
961
|
VibeCoder session routes. Creating a session and appending a message make no model
|
{healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/departments/operations.py
RENAMED
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from ..context import AmsDependencies
|
|
6
6
|
from ..domain_errors import DomainReadRefused
|
|
7
|
-
from ..results import succeeded
|
|
7
|
+
from ..results import is_unknown_write_status, succeeded, unknown_write
|
|
8
8
|
from ..target import TenantTarget
|
|
9
9
|
from .projections import department_projection
|
|
10
10
|
|
|
@@ -27,6 +27,18 @@ UNKNOWN_OUTCOME = (
|
|
|
27
27
|
"department's current server state before deciding whether to retry."
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
+
UNKNOWN_HTTP_OUTCOME = (
|
|
31
|
+
"The department write was sent once and its outcome is now unknown: the server answered with an "
|
|
32
|
+
"error status before a readable result, so the request may have been processed. Run "
|
|
33
|
+
"`hc ams departments get` to check the department's current server state before deciding whether "
|
|
34
|
+
"to retry."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
UNKNOWN_RECONCILIATION = (
|
|
38
|
+
"This command did not re-read the department after the uncertain write; run "
|
|
39
|
+
"`hc ams departments list` before doing anything else."
|
|
40
|
+
)
|
|
41
|
+
|
|
30
42
|
|
|
31
43
|
def _department_id(value: str) -> str:
|
|
32
44
|
if not isinstance(value, str) or not value.strip():
|
|
@@ -140,11 +152,26 @@ def create_department(
|
|
|
140
152
|
)
|
|
141
153
|
except HealthCloudNetworkError:
|
|
142
154
|
# Sent once: the request may have landed; the outcome is unknown, never retried here.
|
|
143
|
-
|
|
155
|
+
unknown_write(
|
|
156
|
+
"departments.create",
|
|
157
|
+
_target(target),
|
|
158
|
+
UNKNOWN_OUTCOME,
|
|
159
|
+
{"department": None},
|
|
160
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
161
|
+
)
|
|
144
162
|
except HealthCloudHTTPError as error:
|
|
145
163
|
refusal = _create_not_found(error, parent_department_id)
|
|
146
164
|
if refusal is not None:
|
|
147
165
|
raise refusal from error
|
|
166
|
+
if is_unknown_write_status(getattr(error, "status_code", None)):
|
|
167
|
+
# A 5xx/408 may have committed server-side: report it as unknown, never a retry shape.
|
|
168
|
+
unknown_write(
|
|
169
|
+
"departments.create",
|
|
170
|
+
_target(target),
|
|
171
|
+
UNKNOWN_HTTP_OUTCOME,
|
|
172
|
+
{"department": None},
|
|
173
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
174
|
+
)
|
|
148
175
|
raise
|
|
149
176
|
# The write has already landed server-side; a mismatching readback must never be
|
|
150
177
|
# reported as a stopped no-op. Name the tenant that actually received it.
|
|
@@ -174,11 +201,25 @@ def update_department(
|
|
|
174
201
|
try:
|
|
175
202
|
department = resource.update(department_id, **fields)
|
|
176
203
|
except HealthCloudNetworkError:
|
|
177
|
-
|
|
204
|
+
unknown_write(
|
|
205
|
+
"departments.update",
|
|
206
|
+
_target(target, department_id),
|
|
207
|
+
UNKNOWN_OUTCOME,
|
|
208
|
+
{"department": None},
|
|
209
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
210
|
+
)
|
|
178
211
|
except HealthCloudHTTPError as error:
|
|
179
212
|
refusal = _not_found(error, department_id)
|
|
180
213
|
if refusal is not None:
|
|
181
214
|
raise refusal from error
|
|
215
|
+
if is_unknown_write_status(getattr(error, "status_code", None)):
|
|
216
|
+
unknown_write(
|
|
217
|
+
"departments.update",
|
|
218
|
+
_target(target, department_id),
|
|
219
|
+
UNKNOWN_HTTP_OUTCOME,
|
|
220
|
+
{"department": None},
|
|
221
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
222
|
+
)
|
|
182
223
|
raise
|
|
183
224
|
_identity(department, target, department_id)
|
|
184
225
|
return _write_result("departments.update", target, department_id, {"department": department_projection(department)})
|
|
@@ -193,11 +234,25 @@ def _lifecycle(dependencies: AmsDependencies, target: TenantTarget, department_i
|
|
|
193
234
|
try:
|
|
194
235
|
department = getattr(resource, method)(department_id)
|
|
195
236
|
except HealthCloudNetworkError:
|
|
196
|
-
|
|
237
|
+
unknown_write(
|
|
238
|
+
f"departments.{method}",
|
|
239
|
+
_target(target, department_id),
|
|
240
|
+
UNKNOWN_OUTCOME,
|
|
241
|
+
{"department": None},
|
|
242
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
243
|
+
)
|
|
197
244
|
except HealthCloudHTTPError as error:
|
|
198
245
|
refusal = _not_found(error, department_id)
|
|
199
246
|
if refusal is not None:
|
|
200
247
|
raise refusal from error
|
|
248
|
+
if is_unknown_write_status(getattr(error, "status_code", None)):
|
|
249
|
+
unknown_write(
|
|
250
|
+
f"departments.{method}",
|
|
251
|
+
_target(target, department_id),
|
|
252
|
+
UNKNOWN_HTTP_OUTCOME,
|
|
253
|
+
{"department": None},
|
|
254
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
255
|
+
)
|
|
201
256
|
raise
|
|
202
257
|
_identity(department, target, department_id)
|
|
203
258
|
return _write_result(
|
|
@@ -225,10 +280,24 @@ def delete_department(dependencies: AmsDependencies, target: TenantTarget, depar
|
|
|
225
280
|
try:
|
|
226
281
|
resource.delete(department_id)
|
|
227
282
|
except HealthCloudNetworkError:
|
|
228
|
-
|
|
283
|
+
unknown_write(
|
|
284
|
+
"departments.delete",
|
|
285
|
+
_target(target, department_id),
|
|
286
|
+
UNKNOWN_OUTCOME,
|
|
287
|
+
{"deleted": None},
|
|
288
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
289
|
+
)
|
|
229
290
|
except HealthCloudHTTPError as error:
|
|
230
291
|
refusal = _not_found(error, department_id)
|
|
231
292
|
if refusal is not None:
|
|
232
293
|
raise refusal from error
|
|
294
|
+
if is_unknown_write_status(getattr(error, "status_code", None)):
|
|
295
|
+
unknown_write(
|
|
296
|
+
"departments.delete",
|
|
297
|
+
_target(target, department_id),
|
|
298
|
+
UNKNOWN_HTTP_OUTCOME,
|
|
299
|
+
{"deleted": None},
|
|
300
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
301
|
+
)
|
|
233
302
|
raise
|
|
234
303
|
return _write_result("departments.delete", target, department_id, {"deleted": True})
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from ..context import AmsDependencies
|
|
6
6
|
from ..domain_errors import DomainReadRefused
|
|
7
7
|
from ..output import protected_text
|
|
8
|
-
from ..results import succeeded
|
|
8
|
+
from ..results import is_unknown_write_status, succeeded, unknown_write
|
|
9
9
|
from ..target import TenantTarget
|
|
10
10
|
from .projections import policy_projection
|
|
11
11
|
|
|
@@ -28,6 +28,17 @@ UNKNOWN_OUTCOME = (
|
|
|
28
28
|
"policy's current server state before deciding whether to retry."
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
+
UNKNOWN_HTTP_OUTCOME = (
|
|
32
|
+
"The policy write was sent once and its outcome is now unknown: the server answered with an "
|
|
33
|
+
"error status before a readable result, so the request may have been processed. Run "
|
|
34
|
+
"`hc ams policies get` to check the policy's current server state before deciding whether to retry."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
UNKNOWN_RECONCILIATION = (
|
|
38
|
+
"This command did not re-read the policy after the uncertain write; run `hc ams policies list` "
|
|
39
|
+
"before doing anything else."
|
|
40
|
+
)
|
|
41
|
+
|
|
31
42
|
|
|
32
43
|
def _policy_id(value: str) -> str:
|
|
33
44
|
if not isinstance(value, str) or not value.strip():
|
|
@@ -195,11 +206,26 @@ def create_policy(
|
|
|
195
206
|
)
|
|
196
207
|
except HealthCloudNetworkError:
|
|
197
208
|
# Sent once: the request may have landed; the outcome is unknown, never retried here.
|
|
198
|
-
|
|
209
|
+
unknown_write(
|
|
210
|
+
"policies.create",
|
|
211
|
+
_target(target),
|
|
212
|
+
UNKNOWN_OUTCOME,
|
|
213
|
+
{"policy": None},
|
|
214
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
215
|
+
)
|
|
199
216
|
except HealthCloudHTTPError as error:
|
|
200
217
|
refusal = _create_not_found(error, department_id) or _conflict(error, None)
|
|
201
218
|
if refusal is not None:
|
|
202
219
|
raise refusal from error
|
|
220
|
+
if is_unknown_write_status(getattr(error, "status_code", None)):
|
|
221
|
+
# A 5xx/408 may have committed server-side: report it as unknown, never a retry shape.
|
|
222
|
+
unknown_write(
|
|
223
|
+
"policies.create",
|
|
224
|
+
_target(target),
|
|
225
|
+
UNKNOWN_HTTP_OUTCOME,
|
|
226
|
+
{"policy": None},
|
|
227
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
228
|
+
)
|
|
203
229
|
raise
|
|
204
230
|
# The write has already landed server-side; a mismatching readback must never be
|
|
205
231
|
# reported as a stopped no-op. Name the tenant that actually received it.
|
|
@@ -248,11 +274,25 @@ def update_policy(
|
|
|
248
274
|
try:
|
|
249
275
|
policy = resource.update(policy_id, **fields)
|
|
250
276
|
except HealthCloudNetworkError:
|
|
251
|
-
|
|
277
|
+
unknown_write(
|
|
278
|
+
"policies.update",
|
|
279
|
+
_target(target, policy_id),
|
|
280
|
+
UNKNOWN_OUTCOME,
|
|
281
|
+
{"policy": None},
|
|
282
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
283
|
+
)
|
|
252
284
|
except HealthCloudHTTPError as error:
|
|
253
285
|
refusal = _not_found(error, policy_id) or _conflict(error, policy_id)
|
|
254
286
|
if refusal is not None:
|
|
255
287
|
raise refusal from error
|
|
288
|
+
if is_unknown_write_status(getattr(error, "status_code", None)):
|
|
289
|
+
unknown_write(
|
|
290
|
+
"policies.update",
|
|
291
|
+
_target(target, policy_id),
|
|
292
|
+
UNKNOWN_HTTP_OUTCOME,
|
|
293
|
+
{"policy": None},
|
|
294
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
295
|
+
)
|
|
256
296
|
raise
|
|
257
297
|
_identity(policy, target, policy_id)
|
|
258
298
|
return _write_result("policies.update", target, policy_id, {"policy": policy_projection(policy)})
|
|
@@ -270,11 +310,25 @@ def set_policy_active(
|
|
|
270
310
|
try:
|
|
271
311
|
policy = resource.update(policy_id, active=active)
|
|
272
312
|
except HealthCloudNetworkError:
|
|
273
|
-
|
|
313
|
+
unknown_write(
|
|
314
|
+
operation,
|
|
315
|
+
_target(target, policy_id),
|
|
316
|
+
UNKNOWN_OUTCOME,
|
|
317
|
+
{"policy": None},
|
|
318
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
319
|
+
)
|
|
274
320
|
except HealthCloudHTTPError as error:
|
|
275
321
|
refusal = _not_found(error, policy_id) or _conflict(error, policy_id)
|
|
276
322
|
if refusal is not None:
|
|
277
323
|
raise refusal from error
|
|
324
|
+
if is_unknown_write_status(getattr(error, "status_code", None)):
|
|
325
|
+
unknown_write(
|
|
326
|
+
operation,
|
|
327
|
+
_target(target, policy_id),
|
|
328
|
+
UNKNOWN_HTTP_OUTCOME,
|
|
329
|
+
{"policy": None},
|
|
330
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
331
|
+
)
|
|
278
332
|
raise
|
|
279
333
|
_identity(policy, target, policy_id)
|
|
280
334
|
if policy.active is not active:
|
|
@@ -296,10 +350,24 @@ def delete_policy(dependencies: AmsDependencies, target: TenantTarget, policy_id
|
|
|
296
350
|
try:
|
|
297
351
|
resource.delete(policy_id)
|
|
298
352
|
except HealthCloudNetworkError:
|
|
299
|
-
|
|
353
|
+
unknown_write(
|
|
354
|
+
"policies.delete",
|
|
355
|
+
_target(target, policy_id),
|
|
356
|
+
UNKNOWN_OUTCOME,
|
|
357
|
+
{"deleted": None},
|
|
358
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
359
|
+
)
|
|
300
360
|
except HealthCloudHTTPError as error:
|
|
301
361
|
refusal = _in_use(error, policy_id) or _not_found(error, policy_id)
|
|
302
362
|
if refusal is not None:
|
|
303
363
|
raise refusal from error
|
|
364
|
+
if is_unknown_write_status(getattr(error, "status_code", None)):
|
|
365
|
+
unknown_write(
|
|
366
|
+
"policies.delete",
|
|
367
|
+
_target(target, policy_id),
|
|
368
|
+
UNKNOWN_HTTP_OUTCOME,
|
|
369
|
+
{"deleted": None},
|
|
370
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
371
|
+
)
|
|
304
372
|
raise
|
|
305
373
|
return _write_result("policies.delete", target, policy_id, {"deleted": True})
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from typing import NoReturn
|
|
6
|
+
|
|
7
|
+
from .domain_errors import DomainOutcomeUnknown
|
|
8
|
+
|
|
5
9
|
VERIFICATION_NOT_APPLICABLE = {
|
|
6
10
|
"saved_state": "not_applicable",
|
|
7
11
|
"side_effects": "not_applicable",
|
|
@@ -58,6 +62,55 @@ def unknown(
|
|
|
58
62
|
return envelope
|
|
59
63
|
|
|
60
64
|
|
|
65
|
+
def is_unknown_write_status(status: object) -> bool:
|
|
66
|
+
"""True when an HTTP status leaves a sent mutation's outcome unknown.
|
|
67
|
+
|
|
68
|
+
A 5xx (gateway or server error) and a 408 may both mean the server processed
|
|
69
|
+
the request before the answer was lost, so the write may have committed.
|
|
70
|
+
429 is deliberately excluded: the server said it did not process the request.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
return status == 408 or (isinstance(status, int) and 500 <= status < 600)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def unknown_write(
|
|
77
|
+
operation: str,
|
|
78
|
+
target: dict,
|
|
79
|
+
message: str,
|
|
80
|
+
data: dict,
|
|
81
|
+
*,
|
|
82
|
+
reconciliation_detail: str,
|
|
83
|
+
) -> NoReturn:
|
|
84
|
+
"""Raise the one unknown-outcome report for a sent catalog mutation.
|
|
85
|
+
|
|
86
|
+
The write was sent exactly once and its outcome cannot be known (the transport
|
|
87
|
+
failed or the server answered 5xx/408 before a readable result), so the
|
|
88
|
+
envelope says the write may have landed and carries ``mutation_replayed:
|
|
89
|
+
false`` plus a ``not_read`` reconciliation: this command never re-sends and
|
|
90
|
+
never re-reads on its own. It is raised (never returned), so the command exits
|
|
91
|
+
nonzero and the outcome is never mistaken for success.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
envelope = unknown(
|
|
95
|
+
operation,
|
|
96
|
+
target,
|
|
97
|
+
"transport",
|
|
98
|
+
message,
|
|
99
|
+
{
|
|
100
|
+
**data,
|
|
101
|
+
"mutation_replayed": False,
|
|
102
|
+
"reconciliation": {"status": "not_read", "detail": reconciliation_detail},
|
|
103
|
+
},
|
|
104
|
+
read_only=False,
|
|
105
|
+
)
|
|
106
|
+
envelope["verification"] = {
|
|
107
|
+
"saved_state": "unknown",
|
|
108
|
+
"side_effects": "unverified",
|
|
109
|
+
"runtime": "unverified",
|
|
110
|
+
}
|
|
111
|
+
raise DomainOutcomeUnknown("transport", message, envelope)
|
|
112
|
+
|
|
113
|
+
|
|
61
114
|
def failed(operation: str, target: dict, category: str, message: str, *, read_only: bool = False) -> dict:
|
|
62
115
|
envelope = {
|
|
63
116
|
"schema_version": 1,
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from ..context import AmsDependencies
|
|
6
6
|
from ..domain_errors import DomainReadRefused
|
|
7
|
-
from ..results import succeeded
|
|
7
|
+
from ..results import is_unknown_write_status, succeeded, unknown_write
|
|
8
8
|
from ..target import TenantTarget
|
|
9
9
|
from .projections import skill_projection
|
|
10
10
|
|
|
@@ -27,6 +27,17 @@ UNKNOWN_OUTCOME = (
|
|
|
27
27
|
"skill's current server state before deciding whether to retry."
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
+
UNKNOWN_HTTP_OUTCOME = (
|
|
31
|
+
"The skill write was sent once and its outcome is now unknown: the server answered with an "
|
|
32
|
+
"error status before a readable result, so the request may have been processed. Run "
|
|
33
|
+
"`hc ams skills get` to check the skill's current server state before deciding whether to retry."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
UNKNOWN_RECONCILIATION = (
|
|
37
|
+
"This command did not re-read the skill after the uncertain write; run `hc ams skills list` "
|
|
38
|
+
"before doing anything else."
|
|
39
|
+
)
|
|
40
|
+
|
|
30
41
|
|
|
31
42
|
def _skill_id(value: str) -> str:
|
|
32
43
|
if not isinstance(value, str) or not value.strip():
|
|
@@ -140,7 +151,13 @@ def create_skill(
|
|
|
140
151
|
)
|
|
141
152
|
except HealthCloudNetworkError:
|
|
142
153
|
# Sent once: the request may have landed; the outcome is unknown, never retried here.
|
|
143
|
-
|
|
154
|
+
unknown_write(
|
|
155
|
+
"skills.create",
|
|
156
|
+
_target(target),
|
|
157
|
+
UNKNOWN_OUTCOME,
|
|
158
|
+
{"skill": None},
|
|
159
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
160
|
+
)
|
|
144
161
|
except HealthCloudHTTPError as error:
|
|
145
162
|
# The server does not validate tool references (a 404 is a generic missing
|
|
146
163
|
# referenced record, never a body to echo).
|
|
@@ -150,6 +167,15 @@ def create_skill(
|
|
|
150
167
|
"A record referenced by the create (for example a tool) was not found in "
|
|
151
168
|
"this tenant; refused without changes.",
|
|
152
169
|
) from error
|
|
170
|
+
if is_unknown_write_status(getattr(error, "status_code", None)):
|
|
171
|
+
# A 5xx/408 may have committed server-side: report it as unknown, never a retry shape.
|
|
172
|
+
unknown_write(
|
|
173
|
+
"skills.create",
|
|
174
|
+
_target(target),
|
|
175
|
+
UNKNOWN_HTTP_OUTCOME,
|
|
176
|
+
{"skill": None},
|
|
177
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
178
|
+
)
|
|
153
179
|
raise
|
|
154
180
|
if skill.tenant_id != target.tenant_id:
|
|
155
181
|
# The write has already landed server-side; report it truthfully, never as a stopped no-op.
|
|
@@ -172,11 +198,25 @@ def update_skill(dependencies: AmsDependencies, target: TenantTarget, skill_id:
|
|
|
172
198
|
try:
|
|
173
199
|
skill = resource.update(skill_id, **fields)
|
|
174
200
|
except HealthCloudNetworkError:
|
|
175
|
-
|
|
201
|
+
unknown_write(
|
|
202
|
+
"skills.update",
|
|
203
|
+
_target(target, skill_id),
|
|
204
|
+
UNKNOWN_OUTCOME,
|
|
205
|
+
{"skill": None},
|
|
206
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
207
|
+
)
|
|
176
208
|
except HealthCloudHTTPError as error:
|
|
177
209
|
refusal = _not_found(error, skill_id)
|
|
178
210
|
if refusal is not None:
|
|
179
211
|
raise refusal from error
|
|
212
|
+
if is_unknown_write_status(getattr(error, "status_code", None)):
|
|
213
|
+
unknown_write(
|
|
214
|
+
"skills.update",
|
|
215
|
+
_target(target, skill_id),
|
|
216
|
+
UNKNOWN_HTTP_OUTCOME,
|
|
217
|
+
{"skill": None},
|
|
218
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
219
|
+
)
|
|
180
220
|
raise
|
|
181
221
|
_identity(skill, target, skill_id)
|
|
182
222
|
return _write_result("skills.update", target, skill_id, {"skill": skill_projection(skill)})
|
|
@@ -192,10 +232,24 @@ def delete_skill(dependencies: AmsDependencies, target: TenantTarget, skill_id:
|
|
|
192
232
|
try:
|
|
193
233
|
resource.delete(skill_id)
|
|
194
234
|
except HealthCloudNetworkError:
|
|
195
|
-
|
|
235
|
+
unknown_write(
|
|
236
|
+
"skills.delete",
|
|
237
|
+
_target(target, skill_id),
|
|
238
|
+
UNKNOWN_OUTCOME,
|
|
239
|
+
{"deleted": None},
|
|
240
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
241
|
+
)
|
|
196
242
|
except HealthCloudHTTPError as error:
|
|
197
243
|
refusal = _referenced_by_agents(error, skill_id) or _not_found(error, skill_id)
|
|
198
244
|
if refusal is not None:
|
|
199
245
|
raise refusal from error
|
|
246
|
+
if is_unknown_write_status(getattr(error, "status_code", None)):
|
|
247
|
+
unknown_write(
|
|
248
|
+
"skills.delete",
|
|
249
|
+
_target(target, skill_id),
|
|
250
|
+
UNKNOWN_HTTP_OUTCOME,
|
|
251
|
+
{"deleted": None},
|
|
252
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
253
|
+
)
|
|
200
254
|
raise
|
|
201
255
|
return _write_result("skills.delete", target, skill_id, {"deleted": True})
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from ..context import AmsDependencies
|
|
6
6
|
from ..domain_errors import DomainReadRefused
|
|
7
|
-
from ..results import succeeded
|
|
7
|
+
from ..results import is_unknown_write_status, succeeded, unknown_write
|
|
8
8
|
from ..target import TenantTarget
|
|
9
9
|
from .projections import team_projection
|
|
10
10
|
|
|
@@ -27,6 +27,17 @@ UNKNOWN_OUTCOME = (
|
|
|
27
27
|
"team's current server state before deciding whether to retry."
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
+
UNKNOWN_HTTP_OUTCOME = (
|
|
31
|
+
"The team write was sent once and its outcome is now unknown: the server answered with an "
|
|
32
|
+
"error status before a readable result, so the request may have been processed. Run "
|
|
33
|
+
"`hc ams teams get` to check the team's current server state before deciding whether to retry."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
UNKNOWN_RECONCILIATION = (
|
|
37
|
+
"This command did not re-read the team after the uncertain write; run `hc ams teams list` "
|
|
38
|
+
"before doing anything else."
|
|
39
|
+
)
|
|
40
|
+
|
|
30
41
|
|
|
31
42
|
def _team_id(value: str) -> str:
|
|
32
43
|
if not isinstance(value, str) or not value.strip():
|
|
@@ -142,7 +153,13 @@ def create_team(
|
|
|
142
153
|
)
|
|
143
154
|
except HealthCloudNetworkError:
|
|
144
155
|
# Sent once: the request may have landed; the outcome is unknown, never retried here.
|
|
145
|
-
|
|
156
|
+
unknown_write(
|
|
157
|
+
"teams.create",
|
|
158
|
+
_target(target),
|
|
159
|
+
UNKNOWN_OUTCOME,
|
|
160
|
+
{"team": None},
|
|
161
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
162
|
+
)
|
|
146
163
|
except HealthCloudHTTPError as error:
|
|
147
164
|
# A create 404 means the referenced department is missing (department_id is required).
|
|
148
165
|
if getattr(error, "status_code", None) == 404:
|
|
@@ -150,6 +167,15 @@ def create_team(
|
|
|
150
167
|
"not_found",
|
|
151
168
|
f"Department '{department_id}' was not found in this tenant; refused without changes.",
|
|
152
169
|
) from error
|
|
170
|
+
if is_unknown_write_status(getattr(error, "status_code", None)):
|
|
171
|
+
# A 5xx/408 may have committed server-side: report it as unknown, never a retry shape.
|
|
172
|
+
unknown_write(
|
|
173
|
+
"teams.create",
|
|
174
|
+
_target(target),
|
|
175
|
+
UNKNOWN_HTTP_OUTCOME,
|
|
176
|
+
{"team": None},
|
|
177
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
178
|
+
)
|
|
153
179
|
raise
|
|
154
180
|
# The write has already landed server-side; a mismatching readback must never be
|
|
155
181
|
# reported as a stopped no-op. Name the tenant that actually received it.
|
|
@@ -179,11 +205,25 @@ def update_team(
|
|
|
179
205
|
try:
|
|
180
206
|
team = resource.update(team_id, **fields)
|
|
181
207
|
except HealthCloudNetworkError:
|
|
182
|
-
|
|
208
|
+
unknown_write(
|
|
209
|
+
"teams.update",
|
|
210
|
+
_target(target, team_id),
|
|
211
|
+
UNKNOWN_OUTCOME,
|
|
212
|
+
{"team": None},
|
|
213
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
214
|
+
)
|
|
183
215
|
except HealthCloudHTTPError as error:
|
|
184
216
|
refusal = _not_found(error, team_id)
|
|
185
217
|
if refusal is not None:
|
|
186
218
|
raise refusal from error
|
|
219
|
+
if is_unknown_write_status(getattr(error, "status_code", None)):
|
|
220
|
+
unknown_write(
|
|
221
|
+
"teams.update",
|
|
222
|
+
_target(target, team_id),
|
|
223
|
+
UNKNOWN_HTTP_OUTCOME,
|
|
224
|
+
{"team": None},
|
|
225
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
226
|
+
)
|
|
187
227
|
raise
|
|
188
228
|
_identity(team, target, team_id)
|
|
189
229
|
return _write_result("teams.update", target, team_id, {"team": team_projection(team)})
|
|
@@ -199,10 +239,24 @@ def delete_team(dependencies: AmsDependencies, target: TenantTarget, team_id: st
|
|
|
199
239
|
try:
|
|
200
240
|
resource.delete(team_id)
|
|
201
241
|
except HealthCloudNetworkError:
|
|
202
|
-
|
|
242
|
+
unknown_write(
|
|
243
|
+
"teams.delete",
|
|
244
|
+
_target(target, team_id),
|
|
245
|
+
UNKNOWN_OUTCOME,
|
|
246
|
+
{"deleted": None},
|
|
247
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
248
|
+
)
|
|
203
249
|
except HealthCloudHTTPError as error:
|
|
204
250
|
refusal = _referenced_by_agents(error, team_id) or _not_found(error, team_id)
|
|
205
251
|
if refusal is not None:
|
|
206
252
|
raise refusal from error
|
|
253
|
+
if is_unknown_write_status(getattr(error, "status_code", None)):
|
|
254
|
+
unknown_write(
|
|
255
|
+
"teams.delete",
|
|
256
|
+
_target(target, team_id),
|
|
257
|
+
UNKNOWN_HTTP_OUTCOME,
|
|
258
|
+
{"deleted": None},
|
|
259
|
+
reconciliation_detail=UNKNOWN_RECONCILIATION,
|
|
260
|
+
)
|
|
207
261
|
raise
|
|
208
262
|
return _write_result("teams.delete", target, team_id, {"deleted": True})
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "healthcloud-cli"
|
|
7
|
-
version = "3.7.
|
|
7
|
+
version = "3.7.1"
|
|
8
8
|
description = "Command-line interface for the HealthCloud MCP gateway, generated from the shared MCP tool manifest."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.14"
|
|
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
|
{healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/data_sources/__init__.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/data_sources/commands.py
RENAMED
|
File without changes
|
|
File without changes
|
{healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/data_sources/operations.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/data_sources/projections.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/departments/projections.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/guardrails/operations.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/guardrails/projections.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/organization/__init__.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/organization/commands.py
RENAMED
|
File without changes
|
|
File without changes
|
{healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/organization/operations.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/ams/organization/projections.py
RENAMED
|
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
|
{healthcloud_cli-3.7.0 → healthcloud_cli-3.7.1}/healthcloud_cli/contracts/mcp-tools.manifest.json
RENAMED
|
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
|