healthcloud-cli 3.5.0__tar.gz → 3.6.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.
- healthcloud_cli-3.6.0/PKG-INFO +1641 -0
- healthcloud_cli-3.6.0/README.md +1623 -0
- healthcloud_cli-3.6.0/healthcloud_cli/admins/__init__.py +5 -0
- healthcloud_cli-3.6.0/healthcloud_cli/admins/commands.py +147 -0
- healthcloud_cli-3.6.0/healthcloud_cli/admins/group.py +22 -0
- healthcloud_cli-3.6.0/healthcloud_cli/admins/operations.py +169 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/agents/commands.py +370 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/agents/group.py +44 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/agents/operations.py +1048 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/agents/projections.py +15 -2
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/auth.py +45 -6
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/compatibility.py +29 -21
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/context.py +143 -16
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/data_sources/commands.py +2 -2
- healthcloud_cli-3.6.0/healthcloud_cli/ams/departments/commands.py +263 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/departments/group.py +34 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/departments/operations.py +234 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/departments/projections.py +4 -1
- healthcloud_cli-3.6.0/healthcloud_cli/ams/domain_errors.py +36 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/errors.py +24 -5
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/group.py +1 -1
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/guardrails/__init__.py +1 -1
- healthcloud_cli-3.6.0/healthcloud_cli/ams/guardrails/commands.py +225 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/guardrails/group.py +30 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/guardrails/operations.py +633 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/organization/commands.py +1 -1
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/output.py +30 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/policies/commands.py +270 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/policies/group.py +34 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/policies/operations.py +269 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/pretty.py +36 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/registry.py +2 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/results.py +29 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/sessions/__init__.py +5 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/sessions/commands.py +551 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/sessions/group.py +75 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/sessions/operations.py +1790 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/sessions/projections.py +241 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/skills/commands.py +195 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/skills/group.py +30 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/skills/operations.py +201 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/target.py +9 -3
- healthcloud_cli-3.6.0/healthcloud_cli/ams/teams/commands.py +205 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/teams/group.py +30 -0
- healthcloud_cli-3.6.0/healthcloud_cli/ams/teams/operations.py +208 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/tools/commands.py +2 -2
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/tuning.py +3 -3
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/cli.py +18 -78
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/contracts/mcp-tools.manifest.json +2 -2
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/output.py +56 -8
- healthcloud_cli-3.6.0/healthcloud_cli/tenants/__init__.py +5 -0
- healthcloud_cli-3.6.0/healthcloud_cli/tenants/commands.py +166 -0
- healthcloud_cli-3.6.0/healthcloud_cli/tenants/group.py +22 -0
- healthcloud_cli-3.6.0/healthcloud_cli/tenants/operations.py +180 -0
- healthcloud_cli-3.6.0/healthcloud_cli/util.py +110 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/pyproject.toml +1 -1
- healthcloud_cli-3.5.0/PKG-INFO +0 -790
- healthcloud_cli-3.5.0/README.md +0 -772
- healthcloud_cli-3.5.0/healthcloud_cli/ams/agents/commands.py +0 -92
- healthcloud_cli-3.5.0/healthcloud_cli/ams/agents/group.py +0 -18
- healthcloud_cli-3.5.0/healthcloud_cli/ams/agents/operations.py +0 -95
- healthcloud_cli-3.5.0/healthcloud_cli/ams/departments/commands.py +0 -61
- healthcloud_cli-3.5.0/healthcloud_cli/ams/departments/group.py +0 -18
- healthcloud_cli-3.5.0/healthcloud_cli/ams/departments/operations.py +0 -73
- healthcloud_cli-3.5.0/healthcloud_cli/ams/domain_errors.py +0 -9
- healthcloud_cli-3.5.0/healthcloud_cli/ams/guardrails/commands.py +0 -57
- healthcloud_cli-3.5.0/healthcloud_cli/ams/guardrails/group.py +0 -18
- healthcloud_cli-3.5.0/healthcloud_cli/ams/guardrails/operations.py +0 -70
- healthcloud_cli-3.5.0/healthcloud_cli/ams/policies/commands.py +0 -57
- healthcloud_cli-3.5.0/healthcloud_cli/ams/policies/group.py +0 -18
- healthcloud_cli-3.5.0/healthcloud_cli/ams/policies/operations.py +0 -68
- healthcloud_cli-3.5.0/healthcloud_cli/ams/skills/commands.py +0 -57
- healthcloud_cli-3.5.0/healthcloud_cli/ams/skills/group.py +0 -18
- healthcloud_cli-3.5.0/healthcloud_cli/ams/skills/operations.py +0 -68
- healthcloud_cli-3.5.0/healthcloud_cli/ams/teams/commands.py +0 -57
- healthcloud_cli-3.5.0/healthcloud_cli/ams/teams/group.py +0 -18
- healthcloud_cli-3.5.0/healthcloud_cli/ams/teams/operations.py +0 -68
- healthcloud_cli-3.5.0/healthcloud_cli/util.py +0 -29
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/.gitignore +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/hatch_build.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/__init__.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/__init__.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/agents/__init__.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/commands.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/data_sources/__init__.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/data_sources/group.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/data_sources/operations.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/data_sources/projections.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/departments/__init__.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/guardrails/projections.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/organization/__init__.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/organization/group.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/organization/operations.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/organization/projections.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/policies/__init__.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/policies/projections.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/session.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/skills/__init__.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/skills/projections.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/teams/__init__.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/teams/projections.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/tools/__init__.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/tools/group.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/tools/operations.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/tools/projections.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/ui.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/ams/windows_files.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/context.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/contracts/__init__.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/main.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/manifest.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/healthcloud_cli/params.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/scripts/__init__.py +0 -0
- {healthcloud_cli-3.5.0 → healthcloud_cli-3.6.0}/scripts/bundle_manifest.py +0 -0
|
@@ -0,0 +1,1641 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: healthcloud-cli
|
|
3
|
+
Version: 3.6.0
|
|
4
|
+
Summary: Command-line interface for the HealthCloud MCP gateway, generated from the shared MCP tool manifest.
|
|
5
|
+
Project-URL: Issues, https://github.com/healthcloudservices/healthcloud-services/issues
|
|
6
|
+
Project-URL: Repository, https://github.com/healthcloudservices/healthcloud-services
|
|
7
|
+
Author: Healthcheck Systems Inc
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: cli,health-cloud,healthcare,healthcloud,mcp
|
|
10
|
+
Requires-Python: >=3.14
|
|
11
|
+
Requires-Dist: click>=8.1
|
|
12
|
+
Requires-Dist: healthcloud-sdk>=3.14.0
|
|
13
|
+
Requires-Dist: healthcloud-tenantadmin>=1.1.1
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest>=9.0.3; extra == 'dev'
|
|
16
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# healthcloud-cli
|
|
20
|
+
|
|
21
|
+
Requires **Python 3.14 or newer**.
|
|
22
|
+
|
|
23
|
+
`hc` is an SDK-backed CLI: the primary surface is `hc ams`, which reads and
|
|
24
|
+
tunes one explicitly selected AMS tenant (catalog domains -- skills, policies,
|
|
25
|
+
guardrails, teams, departments, organization, data-sources, tools -- plus
|
|
26
|
+
read-only agent operations). The `hc mcp` subtree is the gateway fallback and
|
|
27
|
+
has exactly one subcommand per
|
|
28
|
+
MCP tool -- generated dynamically at startup from a shared manifest across
|
|
29
|
+
audiences such as `patient`, `fieldagent`, `ehr`, `vitals`, `diagnostics`,
|
|
30
|
+
`appointments`, `graph`, `person`, `provider`, `tenantadmin`, `connectors`,
|
|
31
|
+
and `telehealth`. Run `hc mcp tools list` to see the exact, current set for
|
|
32
|
+
your installed version. Nothing here is hand-written per tool: the same
|
|
33
|
+
manifest also drives the sibling NPM CLI (`@healthcloud CLI` in
|
|
34
|
+
`packages/cli/npm`), so command names match exactly between the two.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install healthcloud-cli
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This installs the `hc` console script plus its dependency, the
|
|
43
|
+
[`healthcloud-sdk`](../../pip) package, which does the actual HTTP/JSON-RPC
|
|
44
|
+
work against the MCP gateway.
|
|
45
|
+
|
|
46
|
+
## Local development
|
|
47
|
+
|
|
48
|
+
Use uv 0.12.5 and Python 3.14. The package's uv source override installs the
|
|
49
|
+
SDK checkout in editable mode; its committed lock includes the test and build
|
|
50
|
+
tools. Released package consumers can still use pip.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
cd packages/cli/pip
|
|
54
|
+
uv lock --check
|
|
55
|
+
uv sync --frozen --extra dev
|
|
56
|
+
uv run --frozen --extra dev python -m pytest
|
|
57
|
+
uv run --frozen hc --help
|
|
58
|
+
uv run --frozen hc mcp tools list --audience patient
|
|
59
|
+
uv run --frozen hc mcp patient get-patient --help
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
AMS help and shell-completion discovery are credential-free and offline: they
|
|
63
|
+
do not resolve root profiles, read target files, create SDK clients, or log in.
|
|
64
|
+
You can inspect the group, a leaf command, and completion candidates before
|
|
65
|
+
configuring credentials or a target:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
uv run --frozen hc ams --help
|
|
69
|
+
uv run --frozen hc ams inspect --help
|
|
70
|
+
_HC_COMPLETE=bash_complete COMP_WORDS="hc ams in" COMP_CWORD=2 uv run --frozen hc
|
|
71
|
+
_HC_COMPLETE=bash_complete COMP_WORDS="hc ams inspect --o" COMP_CWORD=3 uv run --frozen hc
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Operational validation starts only when Click invokes an AMS leaf callback.
|
|
75
|
+
`discover`, `agents`, `departments`, `teams`, `organization`, `skills`, `policies`, and `sessions`
|
|
76
|
+
(with `attachments` and `messages`) then
|
|
77
|
+
validate their explicit environment and tenant arguments; the remaining leaves
|
|
78
|
+
require `--target-file`. All apply the root, target, and authentication checks
|
|
79
|
+
described below. Deferring preflight does not relax those safety requirements.
|
|
80
|
+
|
|
81
|
+
The bundled tool manifest (`healthcloud_cli/contracts/mcp-tools.manifest.json`)
|
|
82
|
+
is a copy of `packages/cli/contracts/mcp-tools.manifest.json`. Re-sync it
|
|
83
|
+
after pulling manifest changes with:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
python scripts/bundle_manifest.py
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This also runs automatically as a hatchling build hook whenever you `python
|
|
90
|
+
-m build`, so a built wheel/sdist is always self-contained and never depends
|
|
91
|
+
on a relative path outside the installed package.
|
|
92
|
+
|
|
93
|
+
## Authentication & configuration
|
|
94
|
+
|
|
95
|
+
Every command accepts these global options (place them before the `mcp`
|
|
96
|
+
subcommand):
|
|
97
|
+
|
|
98
|
+
| Flag | Env var | Meaning |
|
|
99
|
+
|-------------------|--------------------|-----------------------------------------------|
|
|
100
|
+
| `--access-token` | `HC_ACCESS_TOKEN` | Bearer token sent to the MCP gateway |
|
|
101
|
+
| `--base-url` | `HC_MCP_BASE_URL` | Override the MCP gateway base URL |
|
|
102
|
+
| `--profile` | `HC_PROFILE` | Named profile (see below) |
|
|
103
|
+
| `--output` | -- | `pretty` (default) \| `json` \| `raw` |
|
|
104
|
+
| `--raw` | -- | Shortcut for `--output raw` |
|
|
105
|
+
| `--verbose` | -- | Print request diagnostics to stderr |
|
|
106
|
+
|
|
107
|
+
Precedence, highest wins: **explicit CLI flag > environment variable >
|
|
108
|
+
profile file value > built-in default** (`environment` defaults to `"dev"`;
|
|
109
|
+
`accessToken`/`baseUrl` default to unset). The resolved access token is never
|
|
110
|
+
written to logs, generated files, or test snapshots.
|
|
111
|
+
|
|
112
|
+
### Saved `hc login` session fallback
|
|
113
|
+
|
|
114
|
+
When no explicit token is given (`--access-token`, `HC_ACCESS_TOKEN`, or a
|
|
115
|
+
profile `accessToken`), `hc mcp` uses the saved `hc login` tenant-admin
|
|
116
|
+
session as the bearer, with the same locked refresh-near-expiry behavior as
|
|
117
|
+
`hc ams` (the refreshed session is persisted for the next command):
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
hc login --environment dev --tenant-id TENANT_ID # saves/refreshes the session
|
|
121
|
+
hc mcp tenantadmin get-current-tenant-admin # uses the saved session bearer
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
On `HTTP 401` when no explicit token and no saved session exist, the CLI
|
|
125
|
+
prints one hint line — `Hint: not logged in; run 'hc login', or pass
|
|
126
|
+
--access-token / HC_ACCESS_TOKEN.` — after the failure line, and exits `3`.
|
|
127
|
+
An explicit token always wins over the saved session; `hc logout` removes it
|
|
128
|
+
(local only).
|
|
129
|
+
|
|
130
|
+
### Optional profile file
|
|
131
|
+
|
|
132
|
+
`~/.healthcloud/cli/profiles.json`:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"dev-mia": {
|
|
137
|
+
"baseUrl": "https://dev-api-mcp.health.cloud",
|
|
138
|
+
"accessToken": "eyJ...",
|
|
139
|
+
"environment": "dev"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
A missing profiles file is not an error -- profiles are entirely optional. If
|
|
145
|
+
`--profile`/`HC_PROFILE` names a profile and the file (or that entry) is
|
|
146
|
+
missing, `hc` exits with a clear usage error rather than silently ignoring
|
|
147
|
+
it.
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# get-patient is self-service — the patient is resolved from the access
|
|
151
|
+
# token, so no --patient-id flag exists.
|
|
152
|
+
hc --profile dev-mia mcp patient get-patient
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Listing and inspecting tools
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# Local (bundled manifest, no network call)
|
|
159
|
+
hc mcp tools list
|
|
160
|
+
hc mcp tools list --audience patient
|
|
161
|
+
|
|
162
|
+
# Live gateway (requires --audience)
|
|
163
|
+
hc mcp tools list --audience patient --remote
|
|
164
|
+
|
|
165
|
+
# Full manifest entry for one tool (description, inputSchema, method/path)
|
|
166
|
+
hc mcp tools show patient add_insurance --output json
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Calling a tool
|
|
170
|
+
|
|
171
|
+
Every manifest tool is its own subcommand, grouped by audience:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
hc mcp patient get-patient
|
|
175
|
+
hc mcp appointments book-appointment --patient-id patient-123 --slot-id slot-456
|
|
176
|
+
|
|
177
|
+
# Every Diagnostics tool takes an explicit --patient-id — providers and
|
|
178
|
+
# field agents legitimately look up or record diagnostics for a patient
|
|
179
|
+
# other than themselves. When the caller holds a patient token, the backend
|
|
180
|
+
# enforces server-side that --patient-id must be their own (403 otherwise).
|
|
181
|
+
hc mcp diagnostics create-rapid-test --patient-id patient-123 --test-type covid-19 --result negative
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Run `--help` on any generated command to see its real manifest description
|
|
185
|
+
and arguments:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
hc mcp patient get-patient --help
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Argument flags
|
|
192
|
+
|
|
193
|
+
- **Primitive properties** (`string`/`number`/`integer`/`boolean`) become a
|
|
194
|
+
normal flag in kebab-case, e.g. `patient_id` -> `--patient-id`.
|
|
195
|
+
- `enum` values are validated; invalid values are rejected before any
|
|
196
|
+
network call.
|
|
197
|
+
- `minimum`/`maximum`/`exclusiveMinimum`/`exclusiveMaximum` are enforced
|
|
198
|
+
the same way.
|
|
199
|
+
- Boolean properties are a tri-state flag pair: `--flag/--no-flag`.
|
|
200
|
+
Passing neither leaves the value unset (falls back to the manifest
|
|
201
|
+
default or an `--input`/`--stdin` value, if any); passing one sets it
|
|
202
|
+
explicitly to `true`/`false`.
|
|
203
|
+
- **Object/array properties are never flattened.** They get a single
|
|
204
|
+
`--<prop>-json` flag that takes a raw JSON string, e.g.:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# create-medication is self-service — no --patient-id flag; the patient is
|
|
208
|
+
# resolved server-side from the access token.
|
|
209
|
+
hc mcp patient create-medication \
|
|
210
|
+
--name "Metformin" \
|
|
211
|
+
--dosage-json '{"value":10,"unit":"mg","frequency":"once_daily"}'
|
|
212
|
+
```
|
|
213
|
+
- Path parameters (from `{placeholders}` in the tool's HTTP path) are always
|
|
214
|
+
required, in addition to whatever `inputSchema.required` lists.
|
|
215
|
+
|
|
216
|
+
### Bulk input: `--input` / `--stdin`
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
hc mcp appointments book-appointment --input request.json
|
|
220
|
+
cat request.json | hc mcp appointments book-appointment --stdin
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`--input <file>` and `--stdin` both supply a *base* JSON object of
|
|
224
|
+
arguments. `--input` and `--stdin` cannot be combined. Merge precedence
|
|
225
|
+
(highest wins): **manifest defaults < `--input`/`--stdin` object < explicit
|
|
226
|
+
flags** -- i.e. an explicit flag you actually pass always overrides the same
|
|
227
|
+
key coming from a file or stdin, which in turn overrides the schema default.
|
|
228
|
+
|
|
229
|
+
After merging, the final object is validated against the tool's
|
|
230
|
+
`inputSchema` (required fields present, correct types, enum membership,
|
|
231
|
+
numeric bounds). Any validation failure prints a clear message to stderr and
|
|
232
|
+
exits **2**, without making a network call.
|
|
233
|
+
|
|
234
|
+
### `hc mcp call` -- generic escape hatch
|
|
235
|
+
|
|
236
|
+
`hc mcp call` dispatches to any MCP tool by its exact snake_case tool name --
|
|
237
|
+
including one not yet present in the bundled manifest (the tool name itself
|
|
238
|
+
is never validated against the manifest, it's just forwarded):
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# get_patient is self-service and takes no arguments — {} is a complete,
|
|
242
|
+
# valid call.
|
|
243
|
+
hc mcp call patient get_patient --input '{}'
|
|
244
|
+
hc mcp call patient get_patient --input-file request.json
|
|
245
|
+
cat request.json | hc mcp call patient get_patient --stdin
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Note the deliberate inconsistency versus named commands: here `--input` takes
|
|
249
|
+
an inline JSON **value**, not a file path -- use `--input-file` for a file.
|
|
250
|
+
Exactly one of `--input` / `--input-file` / `--stdin` may be given (or none,
|
|
251
|
+
meaning `{}`).
|
|
252
|
+
|
|
253
|
+
## Output modes
|
|
254
|
+
|
|
255
|
+
The MCP tool-call result is `{ content: [{type:"text", text: "..."}],
|
|
256
|
+
is_error: bool }` from the JSON-RPC response's `.result`, or the JSON-RPC
|
|
257
|
+
`.error` object if the gateway returned one.
|
|
258
|
+
|
|
259
|
+
- **`pretty`** (default): if `content[0].text` parses as JSON, pretty-prints
|
|
260
|
+
the parsed value; otherwise prints the raw text. On a JSON-RPC error,
|
|
261
|
+
prints `error.message` (and `error.code` with `--verbose`) to stderr.
|
|
262
|
+
- **`json`**: prints the tool result's parsed JSON (or raw text if
|
|
263
|
+
unparseable) to stdout, with no envelope.
|
|
264
|
+
- **`raw`**: prints the complete JSON-RPC envelope (`id`, `jsonrpc`,
|
|
265
|
+
`result`, `error`) as returned by the gateway.
|
|
266
|
+
|
|
267
|
+
Diagnostics and `--verbose` logs always go to stderr -- stdout stays
|
|
268
|
+
pipeable in every mode.
|
|
269
|
+
|
|
270
|
+
## Exit codes
|
|
271
|
+
|
|
272
|
+
| Code | Meaning |
|
|
273
|
+
|------|-----------------------------------------------------------------------|
|
|
274
|
+
| `0` | Success |
|
|
275
|
+
| `2` | Local input validation failure (bad JSON, missing required field, invalid enum/number, `--input`+`--stdin` both given, unknown option) -- no network call made |
|
|
276
|
+
| `3` | HTTP/network failure talking to the MCP gateway |
|
|
277
|
+
| `4` | JSON-RPC error response (`.error` is not `None`) |
|
|
278
|
+
| `5` | MCP tool result reported `is_error: true` |
|
|
279
|
+
|
|
280
|
+
## Building
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
uv lock --check
|
|
284
|
+
uv run --frozen python -m build --no-isolation
|
|
285
|
+
uv run --frozen python -m zipfile -l dist/*.whl # confirm the bundled manifest is included
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Deployed AMS administration (source checkout)
|
|
289
|
+
|
|
290
|
+
The reviewed AMS administration SDK is not published yet. Use this checkout's
|
|
291
|
+
editable SDK and tenant-admin sources; no released SDK version is claimed to
|
|
292
|
+
contain `ams_admin`. An installed SDK missing that capability is refused before
|
|
293
|
+
login. Ordinary MCP commands keep their existing behavior.
|
|
294
|
+
|
|
295
|
+
Prepare ignored SDK build READMEs from tracked inputs, then install the locked
|
|
296
|
+
editable packages (the CLI README itself is tracked):
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
node tooling/docs-compiler/prepare-package-readmes.mjs
|
|
300
|
+
cd packages/cli/pip
|
|
301
|
+
uv sync --frozen --extra dev
|
|
302
|
+
uv run --frozen hc ams --help
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Create one private tenant-admin session before interactive AMS work, inspect its
|
|
306
|
+
non-secret binding, and remove it when finished:
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
uv run --frozen hc login --environment dev --tenant-id TENANT_ID
|
|
310
|
+
uv run --frozen hc login --status
|
|
311
|
+
uv run --frozen hc logout
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
`hc login` reads `HC_AMS_ADMIN_EMAIL` and `HC_AMS_ADMIN_PASSWORD` when present,
|
|
315
|
+
otherwise it prompts (with hidden password input). It stores only the email,
|
|
316
|
+
tenant/environment binding, access/refresh tokens and expiry in the strict
|
|
317
|
+
owner-only `~/.healthcloud/cli/ams-session.json`; it never stores the password.
|
|
318
|
+
|
|
319
|
+
For CI and agents, log in non-interactively: pass `--email` and pipe the password
|
|
320
|
+
to `--password-stdin` (read as exactly one stdin line, never echoed, never a
|
|
321
|
+
flag value — there is no `--password` option):
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
printf '%s\n' "$HC_AMS_ADMIN_PASSWORD" | \
|
|
325
|
+
uv run --frozen hc login --environment dev --tenant-id TENANT_ID \
|
|
326
|
+
--email "$HC_AMS_ADMIN_EMAIL" --password-stdin
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
`--email` beats `HC_AMS_ADMIN_EMAIL` and `--password-stdin` beats
|
|
330
|
+
`HC_AMS_ADMIN_PASSWORD`. With `--password-stdin`, the piped line is always the
|
|
331
|
+
password: it is read before any prompt, so it can never be consumed as the
|
|
332
|
+
email, and the email must come from `--email`, `HC_AMS_ADMIN_EMAIL` or (only on
|
|
333
|
+
an interactive terminal) the email prompt. When stdin is not an interactive
|
|
334
|
+
terminal and neither `--password-stdin` nor `HC_AMS_ADMIN_PASSWORD` supplies the
|
|
335
|
+
password, `hc login` refuses with that guidance instead of attempting a hidden
|
|
336
|
+
prompt (no `GetPassWarning`, no echoed password); a missing email on a
|
|
337
|
+
non-interactive stdin (or a process with no stdin stream at all) is refused the
|
|
338
|
+
same way, naming `--email`/`--password-stdin`; every refusal happens before any
|
|
339
|
+
login request. `--status` cannot be combined with `--email` or `--password-stdin`.
|
|
340
|
+
Even on an interactive terminal, `--password-stdin` suppresses terminal echo
|
|
341
|
+
while the single line is read and restores it afterwards; a stdin stream that is
|
|
342
|
+
closed or errors mid-read is refused cleanly (exit 2, never a traceback).
|
|
343
|
+
AMS commands may omit both target flags while this session is present, except
|
|
344
|
+
that a `prod` session still needs `--environment prod` on every command. Explicit
|
|
345
|
+
flags and target files must match it exactly. A session within 60 seconds of
|
|
346
|
+
expiry is refreshed once before the operation, under a lock so concurrent
|
|
347
|
+
commands never spend the same refresh token (a second command waits up to 30 seconds, then stops), and
|
|
348
|
+
atomically replaced. Refresh
|
|
349
|
+
failure stops without an AMS call or password fallback and asks for a new login.
|
|
350
|
+
If the refreshed session cannot be saved, the current command still finishes and
|
|
351
|
+
prints a warning to run `hc login` again.
|
|
352
|
+
|
|
353
|
+
`hc login` works before the admin's email is verified. The current-admin
|
|
354
|
+
profile reports `email_verified` (invited admins start verified); confirm the
|
|
355
|
+
emailed code with `hc tenants verify-email`.
|
|
356
|
+
|
|
357
|
+
`~/.healthcloud` and `~/.healthcloud/cli` must be directories you own with mode
|
|
358
|
+
700; the CLI creates them that way, and refuses (naming the directory and the
|
|
359
|
+
`chmod 700` fix) if an existing one is shared. `hc logout` deletes the local file
|
|
360
|
+
only and reports `"server_session_revoked": false`: issued tokens stay valid
|
|
361
|
+
until they expire.
|
|
362
|
+
|
|
363
|
+
## Tenant registration: `hc tenants register`
|
|
364
|
+
|
|
365
|
+
Creates a tenant and its first (primary) tenant admin through the tenant-admin
|
|
366
|
+
SDK (`POST /tenants/register`) — CLI-first, never an MCP passthrough:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
printf '%s\n' "$ADMIN_PASSWORD" | \
|
|
370
|
+
uv run --frozen hc tenants register \
|
|
371
|
+
--tenant-id acmeclinic --name "Acme Clinic" \
|
|
372
|
+
--admin-first-name Ana --admin-last-name Doe \
|
|
373
|
+
--admin-email ana@example.test --password-stdin
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
- Requires a saved `hc login` session — the session token is attached to the
|
|
377
|
+
SDK before the call; without a session the command refuses with
|
|
378
|
+
"run `hc login` before registering a tenant" and makes no network call.
|
|
379
|
+
`--environment` must match the saved session's environment.
|
|
380
|
+
- `--tenant-id` is validated locally (lowercase a-z only, e.g. `acmeclinic`)
|
|
381
|
+
before any network call; anything else is refused without a request.
|
|
382
|
+
- The admin password comes only from a hidden interactive prompt or
|
|
383
|
+
`--password-stdin` (exactly one stdin line, echo-suppressed). There is no
|
|
384
|
+
`--password` flag, and the password is never echoed or logged.
|
|
385
|
+
- Output: a `tenants.register` envelope with the tenant ID, status, tier,
|
|
386
|
+
admin ID and `email_otp_sent`, plus a next-step hint pointing at
|
|
387
|
+
`hc tenants verify-email`. A 409 is reported as "A tenant with this ID
|
|
388
|
+
already exists"; other API errors surface the backend's safe message.
|
|
389
|
+
|
|
390
|
+
## Tenant email verification: `hc tenants verify-email` and `hc tenants resend-code`
|
|
391
|
+
|
|
392
|
+
Confirms (or re-sends) the registration OTP through the tenant-admin SDK
|
|
393
|
+
(`POST /auth/verify-email` / `POST /auth/resend-verification-code`) —
|
|
394
|
+
CLI-first, never an MCP passthrough:
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
uv run --frozen hc tenants verify-email \
|
|
398
|
+
--environment dev --tenant-id acmeclinic --email ana@example.test --otp 123456
|
|
399
|
+
uv run --frozen hc tenants resend-code \
|
|
400
|
+
--environment dev --tenant-id acmeclinic --email ana@example.test
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
- No saved `hc login` session is required: the emailed OTP is the proof, and
|
|
404
|
+
no bearer token is sent.
|
|
405
|
+
- `--tenant-id` is validated locally (lowercase a-z only) before any network
|
|
406
|
+
call; `--email` and `--otp` must be nonblank.
|
|
407
|
+
- `verify-email` output: a `tenants.verify-email` envelope with the verified
|
|
408
|
+
email and the exact next command to run —
|
|
409
|
+
`hc login --environment dev --tenant-id acmeclinic --email ana@example.test`
|
|
410
|
+
(the password is prompted, never a flag value). `email_verified` is `true`
|
|
411
|
+
only when the service answers `verified: true`; any other answer reports `false`.
|
|
412
|
+
- `resend-code` output: a `tenants.resend-code` envelope with `sent: true`.
|
|
413
|
+
- A wrong or expired code is reported as the server's safe message (e.g.
|
|
414
|
+
"Verification code is invalid or expired"); an unknown email is a
|
|
415
|
+
not-found error. Both are bounded JSON envelopes, never stack traces.
|
|
416
|
+
|
|
417
|
+
## Managing tenant admins: `hc admins create`, `hc admins list`, `hc admins get`
|
|
418
|
+
|
|
419
|
+
Adds and reads a tenant's admins through the tenant-admin SDK
|
|
420
|
+
(`POST /auth/register` mode=self, `GET /tenant-admins`, `GET /admins/{admin_id}`)
|
|
421
|
+
— CLI-first, never an MCP passthrough:
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
printf '%s\n' "$ADMIN_PASSWORD" | \
|
|
425
|
+
uv run --frozen hc admins create --first-name Bo --last-name Nguyen \
|
|
426
|
+
--email bo@example.test --password-stdin
|
|
427
|
+
uv run --frozen hc admins list
|
|
428
|
+
uv run --frozen hc admins get --admin-id ADMIN_ID
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
- All three commands require a saved `hc login` session; the session's tenant
|
|
432
|
+
is always the target (there is no `--tenant-id` flag). Without a session the
|
|
433
|
+
command refuses with "run `hc login` before managing tenant admins" and
|
|
434
|
+
makes no network call. `--environment` (optional) must match the saved
|
|
435
|
+
session's environment.
|
|
436
|
+
- `create` adds another admin to the saved session's tenant. The new admin's
|
|
437
|
+
password comes only from a hidden interactive prompt or `--password-stdin`
|
|
438
|
+
(exactly one stdin line, echo-suppressed); there is no `--password` flag and
|
|
439
|
+
the password is never echoed or logged. Its success envelope carries the
|
|
440
|
+
new `admin_id`, `email_otp_sent` and a next-step hint pointing at
|
|
441
|
+
`hc tenants verify-email`. A 409 is reported as "A tenant admin with this
|
|
442
|
+
email already exists for the tenant".
|
|
443
|
+
- `list` and `get` are read-only, unpaged and bounded: each admin is projected
|
|
444
|
+
to `id`, `name`, `email`, `status` and `is_primary` only — never phone
|
|
445
|
+
numbers, Cognito subs, permissions or audit fields. `list` emits
|
|
446
|
+
`data.admins` plus an unpaged `page` block; `get` emits `data.admin`. Both
|
|
447
|
+
omit the `verification` block, like every read.
|
|
448
|
+
- Failures are the shared bounded error envelopes: not-found (`get` on an
|
|
449
|
+
unknown ID), conflict (duplicate email on `create`), validation (blank
|
|
450
|
+
inputs or an environment mismatch, refused before any network call),
|
|
451
|
+
authentication (no saved session) and transport — never stack traces.
|
|
452
|
+
|
|
453
|
+
Bootstrap candidate IDs with the public SDK discovery endpoint. Without a saved
|
|
454
|
+
session both flags are required; a saved session supplies both together. No MCP
|
|
455
|
+
defaults, admin login, or automatic candidate selection are used:
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
uv run --frozen hc ams discover --environment dev --tenant-id TENANT_ID
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Read-only agent listing, inspection and export take flags instead of a target
|
|
462
|
+
file; both may be omitted together when a matching saved session exists. Without
|
|
463
|
+
one, they log in as tenant admin using `HC_AMS_ADMIN_EMAIL` /
|
|
464
|
+
`HC_AMS_ADMIN_PASSWORD`. They emit the same versioned result envelope as the
|
|
465
|
+
other read domains (`operation`, `outcome`, `schema_version`, `target`, `data`;
|
|
466
|
+
no `verification` block on reads); failures emit one categorized JSON error
|
|
467
|
+
envelope on stderr with the same categories (not found, target mismatch,
|
|
468
|
+
refusal, logged out), not the plain-text `Error: …` lines of earlier releases.
|
|
469
|
+
`agents list` carries bounded projections under `data.agents` plus a `page`
|
|
470
|
+
block; `agents get` carries the bounded projection under `data.agent`, whose
|
|
471
|
+
`assigned_references` holds both halves of an assignment: the nullable
|
|
472
|
+
`department_id`/`team_id` organization references and the five capability lists
|
|
473
|
+
(`tool_ids`, `skill_ids`, `guardrail_ids`, `policy_ids`, `data_source_ids`). The
|
|
474
|
+
legacy top-level `{"agents": …, "environment": …, "tenant_id": …}` shape is
|
|
475
|
+
intentionally replaced by this envelope — scripts must read the new shape.
|
|
476
|
+
Stdout carries bounded metadata only; `adapter` shows
|
|
477
|
+
whether an agent runs from the `file` or `db` config adapter (`unknown` when the
|
|
478
|
+
server does not say). `export` first re-checks the agent/tenant identity, then
|
|
479
|
+
saves the server's own JSON/YAML export byte-for-byte to a new owner-only `--out`
|
|
480
|
+
file (exact bytes preserved), refusing (and leaving no file) when the body is
|
|
481
|
+
not valid UTF-8; its stdout reports the envelope with the file `path`, `format`
|
|
482
|
+
and `sha256` under `data` and the `agent_id` in `target`. `export`
|
|
483
|
+
needs an AMS build that serves `/agents/{agent_id}/configuration/export`; that
|
|
484
|
+
route is not yet deployed to dev:
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
uv run --frozen hc ams agents list --environment dev --tenant-id TENANT_ID
|
|
488
|
+
uv run --frozen hc ams agents get --environment dev --tenant-id TENANT_ID --agent-id AGENT_ID
|
|
489
|
+
uv run --frozen hc ams agents export --environment dev --tenant-id TENANT_ID --agent-id AGENT_ID \
|
|
490
|
+
--format yaml --out agent.yaml
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
For example, `hc ams agents list` succeeds with (formatted for readability):
|
|
494
|
+
|
|
495
|
+
```json
|
|
496
|
+
{"schema_version": 1, "operation": "agents.list", "outcome": "succeeded",
|
|
497
|
+
"target": {"environment": "dev", "tenant_id": "TENANT_ID"},
|
|
498
|
+
"data": {"agents": [{"id": "AGENT_ID", "name": "…", "adapter": "file", "…": "…"}],
|
|
499
|
+
"page": {"kind": "unpaged", "returned_count": 1, "continuation": null}}}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
and a missing `--agent-id` fails with one envelope on stderr and exit 1:
|
|
503
|
+
|
|
504
|
+
```json
|
|
505
|
+
{"schema_version": 1, "operation": "agents.get", "outcome": "failed",
|
|
506
|
+
"target": {"environment": "dev", "tenant_id": "TENANT_ID", "agent_id": "AGENT_ID"},
|
|
507
|
+
"error": {"category": "not_found", "message": "Agent 'AGENT_ID' was not found."}}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
Agent lifecycle changes are two first-class, send-once write commands. They
|
|
511
|
+
require a saved `hc login` session (env-var credentials are not accepted for
|
|
512
|
+
writes) and an explicit `--expected-config-version`, `--confirm` acknowledgement:
|
|
513
|
+
|
|
514
|
+
```bash
|
|
515
|
+
uv run --frozen hc ams agents deactivate --environment dev --tenant-id TENANT_ID \
|
|
516
|
+
--agent-id AGENT_ID --expected-config-version N --confirm
|
|
517
|
+
uv run --frozen hc ams agents activate --environment dev --tenant-id TENANT_ID \
|
|
518
|
+
--agent-id AGENT_ID --expected-config-version N --confirm
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
Each command issues exactly one `GET /agents/{agent_id}`, local pre-checks,
|
|
522
|
+
exactly one `PUT /agents/{agent_id}`, and exactly one `GET` readback. The PUT
|
|
523
|
+
body is exactly `{"status": …, "active": …}` and nothing else — the server's
|
|
524
|
+
update schema accepts ~40 fields, but a lifecycle command is not a generic CRUD
|
|
525
|
+
fallback, so there is no `agents update`, no `--status`/`--active` flag, and no
|
|
526
|
+
`archive` here (deletion is its own reviewed command, below). `deactivate` writes `status: "disabled"` **and**
|
|
527
|
+
`active: false`; `activate` writes `status: "active"` **and** `active: true`.
|
|
528
|
+
Writing one field and reporting the other is a false claim, so both are written
|
|
529
|
+
and both are read back.
|
|
530
|
+
|
|
531
|
+
The result envelope reports three facts separately under `data`:
|
|
532
|
+
`data.evidence.saved` (`confirmed` only when the readback shows the exact pair
|
|
533
|
+
that was sent, for this agent in this tenant), `data.evidence.side_effects`
|
|
534
|
+
(always `unverified`: no side-effect readback route exists) and
|
|
535
|
+
`data.evidence.runtime` (always `unverified`: no serving route exists). Exit 0
|
|
536
|
+
requires that readback confirmation; a readback mismatch exits nonzero with
|
|
537
|
+
`"outcome": "failed"` and `verification.saved_state: "failed"` and says the new
|
|
538
|
+
state is **not** confirmed. A readback that is missing, cannot be read as an
|
|
539
|
+
agent object, describes a different agent, or lands in a different tenant is the
|
|
540
|
+
same unconfirmed failure (never an internal error): `data.eligibility.fields_tested_by_read_paths` is then `null`
|
|
541
|
+
because those fields could not be observed. `data.eligibility` states that the
|
|
542
|
+
fields the read-eligibility paths test now read the new pair, and warns
|
|
543
|
+
explicitly that
|
|
544
|
+
`get_default_agent_for_tenant` / `get_orchestrator_for_tenant` never test these
|
|
545
|
+
fields — a disabled agent can still be selected by those paths — and that
|
|
546
|
+
in-flight runs, WSS sessions and schedules are untouched by this route.
|
|
547
|
+
|
|
548
|
+
Pre-checks refuse with exit 1 and **no write**: a missing `--confirm` (exit 2
|
|
549
|
+
usage error, before login), an agent outside the explicit tenant, an already
|
|
550
|
+
in-effect state, an unsupported `archived`/`draft` target (named as having no
|
|
551
|
+
supported reversible transition here), or a `config_version` that no longer
|
|
552
|
+
matches `--expected-config-version`. That version check is a local best-effort
|
|
553
|
+
guard only: the route has no `If-Match` and the server never compares the
|
|
554
|
+
version, so it cannot prove atomicity and a request could still race between
|
|
555
|
+
the read and the PUT.
|
|
556
|
+
|
|
557
|
+
A transport error or `5xx` from the PUT is reported as `outcome: "unknown"`
|
|
558
|
+
with `mutation_replayed: false`, `verification.saved_state: "unknown"` and a
|
|
559
|
+
`reconciliation: {"status": "not_read", …}` block; the CLI sends no retry and
|
|
560
|
+
does not re-read automatically — run `hc ams agents get` before doing anything
|
|
561
|
+
else. A failed readback after an accepted PUT is reported the same way (unknown,
|
|
562
|
+
not success). A `2xx` PUT answer whose body cannot be read as an agent object is
|
|
563
|
+
also `outcome: "unknown"` (sent once, saved state unverified), never an internal
|
|
564
|
+
error and never a success.
|
|
565
|
+
|
|
566
|
+
Capability and organization assignments are one first-class, send-once write
|
|
567
|
+
command. It requires a saved `hc login` session (env-var credentials are not
|
|
568
|
+
accepted for writes), an explicit `--expected-config-version`, and the
|
|
569
|
+
`--confirm` acknowledgement of the target and the printed reviewed diff:
|
|
570
|
+
|
|
571
|
+
```bash
|
|
572
|
+
uv run --frozen hc ams agents assignments set --environment dev --tenant-id TENANT_ID \
|
|
573
|
+
--agent-id AGENT_ID --expected-config-version N --confirm \
|
|
574
|
+
--tool-ids TOOL_ID --tool-ids ANOTHER_TOOL_ID \
|
|
575
|
+
--clear-team
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
There is **no** assignment route on the server: assignments are plain fields on
|
|
579
|
+
the one `PUT /agents/{agent_id}` route, so this command issues exactly one
|
|
580
|
+
`GET /agents/{agent_id}`, local pre-checks, exactly one `PUT`, and exactly one
|
|
581
|
+
`GET` readback, and the PUT body is **exactly** the reference fields the
|
|
582
|
+
reviewed diff changes — never the whole agent payload, never a generic
|
|
583
|
+
`agents update`, never the legacy `tools`/`data_sources` name aliases, and never
|
|
584
|
+
a lifecycle, model or instruction field.
|
|
585
|
+
|
|
586
|
+
Each family is either omitted (unchanged) or given exactly once:
|
|
587
|
+
`--department-id`, `--team-id`, and the repeatable `--skill-ids`, `--tool-ids`,
|
|
588
|
+
`--policy-ids`, `--guardrail-ids`, `--data-source-ids` flags replace that family
|
|
589
|
+
exactly; `--clear-department`, `--clear-team` and `--clear-skill-ids`,
|
|
590
|
+
`--clear-tool-ids`, `--clear-policy-ids`, `--clear-guardrail-ids`,
|
|
591
|
+
`--clear-data-source-ids` empty or clear it (`[]` for a list family, `null` for
|
|
592
|
+
the two nullable scalars). The server de-duplicates ids, so the CLI does too.
|
|
593
|
+
`health_service_id`, `custom_tools`, `artifact_kinds`, lifecycle and `workflow`
|
|
594
|
+
are not part of this command: a health-service assignment has no `hc` read
|
|
595
|
+
surface, so it would not be reviewable.
|
|
596
|
+
|
|
597
|
+
The reviewed diff is printed to stderr before the single write (ids only, never
|
|
598
|
+
instruction text): each changed field with its `before` -> `after` value, plus
|
|
599
|
+
sorted `added`/`removed` id lists for the list families. It is omitted under
|
|
600
|
+
`--output json`/`raw` so those modes keep stderr machine-parsable; the same diff
|
|
601
|
+
is carried in the envelope under `data.changed` as
|
|
602
|
+
`{field: {before, after, added, removed}}` (`added`/`removed` are `null` for the
|
|
603
|
+
two scalar references).
|
|
604
|
+
|
|
605
|
+
The envelope reports `data.agent_id`, `data.changed`,
|
|
606
|
+
`data.config_version: {before, after}` (a version advance is informational
|
|
607
|
+
only), and three facts separately under `data.evidence`: `saved` (`confirmed`
|
|
608
|
+
only when the readback is this agent in this tenant and **every** changed
|
|
609
|
+
reference equals the intended after value), `side_effects` (always `unverified`)
|
|
610
|
+
and `runtime` (always `unverified`). `data.mutation_replayed` is always `false`.
|
|
611
|
+
The three side effects are disclosed, not verified: this route does **not**
|
|
612
|
+
regenerate or publish the agent's Application file (only the plan-apply path
|
|
613
|
+
publishes), `disable_direct_agent_mutations` does not gate this `PUT`, and the
|
|
614
|
+
server audit log records an `UPDATE` with the payload. Runtime enforcement of
|
|
615
|
+
the new references is unobservable: no serving readback route exists, so this
|
|
616
|
+
command never claims it.
|
|
617
|
+
|
|
618
|
+
`tool_ids` and `data_source_ids` address **unscoped global catalogs** by design;
|
|
619
|
+
the server checks tenant scope only for the department, team, skill, policy and
|
|
620
|
+
guardrail references. This command does not imply otherwise, and it does not
|
|
621
|
+
re-implement the server's reference checks.
|
|
622
|
+
|
|
623
|
+
Pre-checks refuse with exit 1 and **no write**: a missing `--confirm` (exit 2
|
|
624
|
+
usage error, before login and before any request), no assignment option at all
|
|
625
|
+
(exit 2), setting and clearing the same family together (exit 2), an agent
|
|
626
|
+
outside the explicit tenant, a request where every reference already equals the
|
|
627
|
+
read state ("already in effect"), or a `config_version` that no longer matches
|
|
628
|
+
`--expected-config-version`. That version check is a local best-effort guard
|
|
629
|
+
only: the route has no `If-Match` and the server never compares the version, so
|
|
630
|
+
it cannot prove atomicity and a request could still race between the read and
|
|
631
|
+
the PUT. Operation-specific approval here is the CLI's own `--confirm` plus the
|
|
632
|
+
reviewed diff, deliberately separate from the tuning/plan-apply approval path;
|
|
633
|
+
the server enforces tenant isolation, reference existence and the audit log, and
|
|
634
|
+
it has **no** assignment approval or consent entity.
|
|
635
|
+
|
|
636
|
+
A transport error or `5xx` from the PUT is reported as `outcome: "unknown"` with
|
|
637
|
+
`mutation_replayed: false`, `verification.saved_state: "unknown"` and a
|
|
638
|
+
`reconciliation: {"status": "not_read", …}` block; the CLI sends no retry and
|
|
639
|
+
does not re-read automatically — run `hc ams agents get` before doing anything
|
|
640
|
+
else. A `2xx` PUT answer whose body cannot be read as an agent object is also
|
|
641
|
+
`outcome: "unknown"`. A readback that is missing, cannot be read as an agent, is
|
|
642
|
+
a different agent or a different tenant, or does not show exactly the references
|
|
643
|
+
that were sent, is `outcome: "failed"` with `verification.saved_state: "failed"`
|
|
644
|
+
and a re-read pointer — never an internal error, never a replay.
|
|
645
|
+
|
|
646
|
+
Assignments are **not** reachable through the tuning path: a generated plan that
|
|
647
|
+
carries a reference field is refused by field name by
|
|
648
|
+
`hc ams sessions plans apply`, so a plan/tuning approval can never grant
|
|
649
|
+
capabilities.
|
|
650
|
+
|
|
651
|
+
Deleting an agent is one first-class, send-once destructive command. It requires
|
|
652
|
+
a saved `hc login` session (env-var credentials are not accepted for writes) and
|
|
653
|
+
the `--confirm` acknowledgement of the target and disclosure block it prints:
|
|
654
|
+
|
|
655
|
+
```bash
|
|
656
|
+
uv run --frozen hc ams agents delete --environment dev --tenant-id TENANT_ID \
|
|
657
|
+
--agent-id AGENT_ID --confirm
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
Before the delete it reads the agent once and prints the resolved destructive
|
|
661
|
+
target (`agent_id`, `name`, `type`, `status`, `active`, `config_version`) and the
|
|
662
|
+
disclosure block to stderr, so the operator approves an actual record, not a
|
|
663
|
+
phrase. The same disclosure is carried under `data.disclosure` in the envelope
|
|
664
|
+
(`irreversible`, `generated_file`, `dependency_guard`, `restore`,
|
|
665
|
+
`authorization`, `recovery_trail`), and it is the honest server contract:
|
|
666
|
+
|
|
667
|
+
- the delete is a **hard delete** of the agent row: no soft delete, no
|
|
668
|
+
tombstone, no retention window and no undelete route;
|
|
669
|
+
- the generated agent code file is removed from the runtime;
|
|
670
|
+
- the server guards **one** reference only — schedules that point at the agent
|
|
671
|
+
answer `409` and nothing is deleted. Default-agent and orchestrator selection,
|
|
672
|
+
engine bindings (`default_agent_id`), sessions and utility agents are **not**
|
|
673
|
+
guarded (there is no foreign key), so this command cannot detect them;
|
|
674
|
+
- `hc ams restore` **cannot** restore a deleted agent (it replays tuning fields
|
|
675
|
+
onto an existing agent). Export the configuration first with
|
|
676
|
+
`hc ams agents export` if you may need it;
|
|
677
|
+
- the route needs an authenticated tenant user only: no admin/role check is
|
|
678
|
+
applied;
|
|
679
|
+
- only a server audit row (`AuditAction.DELETE`) remains.
|
|
680
|
+
|
|
681
|
+
The sequence is exactly one `GET /agents/{agent_id}`, exactly one
|
|
682
|
+
`DELETE /agents/{agent_id}` (204, no body) and exactly one confirmation `GET`.
|
|
683
|
+
Only a `404` from that confirmation read proves the row is gone: the envelope
|
|
684
|
+
then reports `data.deleted: true`, `data.evidence.deleted.status: "confirmed"`
|
|
685
|
+
with `confirmation: "not_found"` and `confirmation_status: 404`, plus
|
|
686
|
+
`data.evidence.side_effects` and `data.evidence.runtime` (both always
|
|
687
|
+
`unverified`) and `data.mutation_replayed: false`. The command prints no promise
|
|
688
|
+
of restore and has no `--force`, no `--dry-run`, no interactive prompt and no
|
|
689
|
+
`--expected-config-version` — the route has no `If-Match` or version check, so a
|
|
690
|
+
version flag would advertise protection the server does not offer.
|
|
691
|
+
|
|
692
|
+
Refusals claim nothing and send nothing extra: a missing `--confirm` is an exit
|
|
693
|
+
2 usage error before login or any request; an agent outside the explicit tenant,
|
|
694
|
+
the tenant identity mismatch and a delete target that no longer exists are
|
|
695
|
+
refused before the `DELETE`; a `409` is a `conflict` refusal naming the referring
|
|
696
|
+
schedules; a `404` from the `DELETE` is `not_found`; and a `405` is the server's
|
|
697
|
+
refusal (`unsupported`). None of them is retried.
|
|
698
|
+
|
|
699
|
+
A transport error or `5xx` from the `DELETE` is `outcome: "unknown"` with
|
|
700
|
+
`mutation_replayed: false`, `verification.saved_state: "unknown"` and a
|
|
701
|
+
`reconciliation: {"status": "not_read", …}` block; nothing is replayed and the
|
|
702
|
+
agent is never restored — run `hc ams agents list` before doing anything else.
|
|
703
|
+
The same unknown outcome applies when the delete was accepted but the
|
|
704
|
+
confirmation read could not be read. A confirmation read that still returns the
|
|
705
|
+
agent (`200`) — or a `2xx` body that cannot be read as an agent — is
|
|
706
|
+
`outcome: "failed"` with `data.deleted: false` and
|
|
707
|
+
`verification.saved_state: "failed"`, never success.
|
|
708
|
+
|
|
709
|
+
Read-only department listing and inspection use the same explicit tenant-admin
|
|
710
|
+
targeting. They emit one versioned JSON result on stdout; failures emit one
|
|
711
|
+
categorized JSON result on stderr. Department descriptions, creator identities,
|
|
712
|
+
and evaluation-suite content are never included in the projection:
|
|
713
|
+
|
|
714
|
+
```bash
|
|
715
|
+
uv run --frozen hc ams departments list --environment dev --tenant-id TENANT_ID
|
|
716
|
+
uv run --frozen hc ams departments get --environment dev --tenant-id TENANT_ID \
|
|
717
|
+
--department-id DEPARTMENT_ID
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
Department reads are complete and unpaged.
|
|
721
|
+
|
|
722
|
+
Department writes are first-class, send-once commands that require a saved
|
|
723
|
+
`hc login` session (run `hc login` first; env-var credentials are not accepted
|
|
724
|
+
for writes). Each write is sent exactly once — the CLI never retries a write —
|
|
725
|
+
and the result envelope carries the server's authoritative readback of the
|
|
726
|
+
department (same bounded projection as reads):
|
|
727
|
+
|
|
728
|
+
```bash
|
|
729
|
+
uv run --frozen hc ams departments create --environment dev --tenant-id TENANT_ID --name "Clinical Operations" --created-by-user-id ADMIN_USER_ID [--description TEXT] [--parent-department-id PARENT_ID] [--key KEY] [--required-eval-suite-ids SUITE_ID ...]
|
|
730
|
+
uv run --frozen hc ams departments update --environment dev --tenant-id TENANT_ID --department-id DEPARTMENT_ID [--name NAME] [--description TEXT] [--parent-department-id PARENT_ID] [--key KEY] [--required-eval-suite-ids SUITE_ID ...]
|
|
731
|
+
uv run --frozen hc ams departments set-active --environment dev --tenant-id TENANT_ID --department-id DEPARTMENT_ID
|
|
732
|
+
uv run --frozen hc ams departments deactivate --environment dev --tenant-id TENANT_ID --department-id DEPARTMENT_ID
|
|
733
|
+
uv run --frozen hc ams departments delete --environment dev --tenant-id TENANT_ID --department-id DEPARTMENT_ID
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
- `create` succeeds with `operation: "departments.create"` and
|
|
737
|
+
`data.department` = the server's 201 readback. `--name` and
|
|
738
|
+
`--created-by-user-id` are required by the server contract (the tenant is
|
|
739
|
+
taken from the saved session's authenticated admin, never from a flag).
|
|
740
|
+
- `update` sends only the options you pass (an empty `--description ""`
|
|
741
|
+
clears it); it refuses with exit 1 (`validation`) when no field option is
|
|
742
|
+
given. `set-active` and `deactivate` mark the department active/inactive.
|
|
743
|
+
- `delete` succeeds with `data: {"deleted": true}` (the server answers 204
|
|
744
|
+
with no body) and is irreversible.
|
|
745
|
+
- A 404 for the requested department is a `not_found` refusal naming the ID
|
|
746
|
+
(on `create`, a 404 names the referenced department — typically the missing
|
|
747
|
+
`--parent-department-id`); a target/tenant or identity mismatch is a
|
|
748
|
+
`validation` refusal; both exit 1
|
|
749
|
+
without changes. The one exception is a tenant mismatch on the `create`
|
|
750
|
+
readback: the department has already been created server-side, so the CLI
|
|
751
|
+
reports a `validation` refusal that names the department, the tenant that
|
|
752
|
+
received it, and tells you to run `hc ams departments get` — it never claims
|
|
753
|
+
a landed write was a stopped no-op.
|
|
754
|
+
- If the transport times out or the connection drops mid-write, the write's
|
|
755
|
+
outcome is unknown: the CLI reports one `transport` error whose message says
|
|
756
|
+
the outcome is unknown and to check `hc ams departments get` before
|
|
757
|
+
retrying. It never re-sends the write on its own.
|
|
758
|
+
|
|
759
|
+
Read-only team listing and inspection are also complete and unpaged. The bounded
|
|
760
|
+
projection omits descriptions and creator identities; it includes evaluation-suite
|
|
761
|
+
IDs but never suite content:
|
|
762
|
+
|
|
763
|
+
```bash
|
|
764
|
+
uv run --frozen hc ams teams list --environment dev --tenant-id TENANT_ID
|
|
765
|
+
uv run --frozen hc ams teams get --environment dev --tenant-id TENANT_ID --team-id TEAM_ID
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
Team writes are first-class, send-once commands that require a saved `hc login`
|
|
769
|
+
session (run `hc login` first; env-var credentials are not accepted for writes).
|
|
770
|
+
Each write is sent exactly once — the CLI never retries a write — and the result
|
|
771
|
+
envelope carries the server's authoritative readback of the team (same bounded
|
|
772
|
+
projection as reads). Teams have no active/inactive lifecycle, so there are no
|
|
773
|
+
`set-active`/`deactivate` commands:
|
|
774
|
+
|
|
775
|
+
```bash
|
|
776
|
+
uv run --frozen hc ams teams create --environment dev --tenant-id TENANT_ID --name "Escalation Team" --department-id DEPARTMENT_ID --created-by-user-id ADMIN_USER_ID [--description TEXT] [--key KEY] [--required-eval-suite-ids SUITE_ID ...]
|
|
777
|
+
uv run --frozen hc ams teams update --environment dev --tenant-id TENANT_ID --team-id TEAM_ID [--name NAME] [--description TEXT] [--key KEY] [--required-eval-suite-ids SUITE_ID ...]
|
|
778
|
+
uv run --frozen hc ams teams delete --environment dev --tenant-id TENANT_ID --team-id TEAM_ID
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
- `create` succeeds with `operation: "teams.create"` and `data.team` = the
|
|
782
|
+
server's 201 readback. `--name`, `--department-id` and
|
|
783
|
+
`--created-by-user-id` are required by the server contract (the tenant is
|
|
784
|
+
taken from the saved session's authenticated admin, never from a flag).
|
|
785
|
+
- `update` sends only the options you pass (an empty `--description ""`
|
|
786
|
+
clears it; `--clear-required-eval-suite-ids` sends an empty list to clear
|
|
787
|
+
the required evaluation suites; `--key` accepts a nonblank key, it cannot be
|
|
788
|
+
blanked); it refuses with exit 1 (`validation`) when no field option is
|
|
789
|
+
given.
|
|
790
|
+
- `delete` succeeds with `data: {"deleted": true}` (the server answers 204
|
|
791
|
+
with no body) and is irreversible. When AI agents still reference the team,
|
|
792
|
+
the server refuses with 409 and the CLI reports one `conflict` refusal that
|
|
793
|
+
names the team and says agents reference it; no changes were made.
|
|
794
|
+
- A 404 for the requested team is a `not_found` refusal naming the ID (on
|
|
795
|
+
`create`, a 404 names the referenced department — the missing
|
|
796
|
+
`--department-id`); a target/tenant mismatch on an update readback is a
|
|
797
|
+
`validation` refusal that reports the landed write truthfully: it names the
|
|
798
|
+
team and the tenant that actually received it and points to `hc ams teams
|
|
799
|
+
get` — it never claims a landed write was stopped.
|
|
800
|
+
- If the transport times out or the connection drops mid-write, the write's
|
|
801
|
+
outcome is unknown: the CLI reports one `transport` error whose message says
|
|
802
|
+
the outcome is unknown and to check `hc ams teams get` before retrying. It
|
|
803
|
+
never re-sends the write on its own.
|
|
804
|
+
|
|
805
|
+
The organization hierarchy is the backend-built department/team/agent tree. The
|
|
806
|
+
CLI preserves its order and nesting without issuing entity reads, joining records,
|
|
807
|
+
or inferring missing relationships:
|
|
808
|
+
|
|
809
|
+
```bash
|
|
810
|
+
uv run --frozen hc ams organization hierarchy --environment dev --tenant-id TENANT_ID
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
Read-only skill catalog listing and inspection are complete and unpaged. The
|
|
814
|
+
bounded projection includes only skill IDs, names, tool IDs, and audit timestamps;
|
|
815
|
+
it omits descriptions, tenant identity, creator identity, arbitrary response
|
|
816
|
+
fields, and tool details. Tool-ID order is preserved:
|
|
817
|
+
|
|
818
|
+
```bash
|
|
819
|
+
uv run --frozen hc ams skills list --environment dev --tenant-id TENANT_ID
|
|
820
|
+
uv run --frozen hc ams skills get --environment dev --tenant-id TENANT_ID --skill-id SKILL_ID
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
The CLI does not assign skills. Skill commands do not
|
|
824
|
+
read guardrails. They make exactly one catalog GET after tenant-admin login
|
|
825
|
+
and does not paginate, retry, cache, join, or infer additional catalog data.
|
|
826
|
+
Skill reads refuse target files, root credentials or base URLs, raw output,
|
|
827
|
+
blank or dot-segment skill IDs, and installed SDKs that do not provide both
|
|
828
|
+
skill list and get methods. These local refusals happen before tenant-admin login
|
|
829
|
+
or catalog reads.
|
|
830
|
+
|
|
831
|
+
Skill writes are first-class, send-once commands that require a saved `hc login`
|
|
832
|
+
session (env-var credentials are not accepted for writes). Each write is sent
|
|
833
|
+
exactly once — the CLI never retries a write — and the result envelope carries
|
|
834
|
+
the server's authoritative readback of the skill (same bounded projection as
|
|
835
|
+
reads). Skills have no active/inactive lifecycle, so there are no
|
|
836
|
+
`set-active`/`deactivate` commands:
|
|
837
|
+
|
|
838
|
+
```bash
|
|
839
|
+
uv run --frozen hc ams skills create --environment dev --tenant-id TENANT_ID --name "Schedule Lookup" --created-by-user-id ADMIN_USER_ID [--description TEXT] [--tool-ids TOOL_ID ...]
|
|
840
|
+
uv run --frozen hc ams skills update --environment dev --tenant-id TENANT_ID --skill-id SKILL_ID [--name NAME] [--description TEXT] [--tool-ids TOOL_ID ... | --clear-tool-ids]
|
|
841
|
+
uv run --frozen hc ams skills delete --environment dev --tenant-id TENANT_ID --skill-id SKILL_ID
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
- `create` succeeds with `operation: "skills.create"` and `data.skill` = the
|
|
845
|
+
server's 201 readback. `--name` and `--created-by-user-id` are required by
|
|
846
|
+
the server contract; the tenant is taken from the saved session's
|
|
847
|
+
authenticated admin, never from a flag. The server does not validate that
|
|
848
|
+
`--tool-ids` reference real tools (blank tool IDs are rejected client-side).
|
|
849
|
+
- `update` sends only the options you pass (an empty `--description ""`
|
|
850
|
+
clears it; `--clear-tool-ids` sends an explicit empty list to clear the
|
|
851
|
+
skill's tool bindings); it refuses with exit 1 (`validation`) when no field
|
|
852
|
+
option is given.
|
|
853
|
+
- `delete` succeeds with `data: {"deleted": true}` (the server answers 204
|
|
854
|
+
with no body) and is irreversible. When AI agents still reference the skill
|
|
855
|
+
(via their `skill_ids`), the server refuses with 409 and the CLI reports one
|
|
856
|
+
`conflict` refusal that names the skill and says agents reference it; no
|
|
857
|
+
changes were made.
|
|
858
|
+
- A 404 for the requested skill is a `not_found` refusal naming the ID (on
|
|
859
|
+
`create`, a 404 is a generic referenced-record refusal — the server does not
|
|
860
|
+
validate tool references and the CLI does not either); a target/tenant
|
|
861
|
+
mismatch on a write readback is a `validation` refusal that reports the
|
|
862
|
+
landed write truthfully: it names the skill and the tenant that actually
|
|
863
|
+
received it and points to `hc ams skills get` — it never claims a landed
|
|
864
|
+
write was stopped.
|
|
865
|
+
- If the transport times out or the connection drops mid-write, the write's
|
|
866
|
+
outcome is unknown: the CLI reports one `transport` error whose message says
|
|
867
|
+
the outcome is unknown and to check `hc ams skills get` before retrying. It
|
|
868
|
+
never re-sends the write on its own.
|
|
869
|
+
|
|
870
|
+
Read-only policy catalog listing and inspection are complete and unpaged. The
|
|
871
|
+
bounded projection includes only policy IDs, names, department IDs, guardrail
|
|
872
|
+
IDs, active status, and audit timestamps. It never emits protected policy
|
|
873
|
+
content, descriptions, tenant or creator identity, arbitrary response fields,
|
|
874
|
+
or joined department/guardrail details. Guardrail-ID order is preserved:
|
|
875
|
+
|
|
876
|
+
```bash
|
|
877
|
+
uv run --frozen hc ams policies list --environment dev --tenant-id TENANT_ID
|
|
878
|
+
uv run --frozen hc ams policies get --environment dev --tenant-id TENANT_ID --policy-id POLICY_ID
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
The CLI does not export protected policy content and never emits it: policy
|
|
882
|
+
`--content` is write-only — it is sent to the server but never appears in any
|
|
883
|
+
command output, error message, or readback. Read commands make exactly one
|
|
884
|
+
catalog GET after tenant-admin login and do not paginate, retry, cache, join,
|
|
885
|
+
or infer referenced resources. Policy reads refuse target files, root
|
|
886
|
+
credentials or base URLs, raw output, blank or dot-segment policy IDs, and
|
|
887
|
+
installed SDKs without both policy methods before tenant-admin login or
|
|
888
|
+
catalog reads. Successful reads make no assignment, deployment, serving,
|
|
889
|
+
enforcement, or effectiveness claim.
|
|
890
|
+
|
|
891
|
+
Policy writes are first-class, send-once commands that require a saved `hc login`
|
|
892
|
+
session (env-var credentials are not accepted for writes). Each write is sent
|
|
893
|
+
exactly once — the CLI never retries a write — and the result envelope carries
|
|
894
|
+
the server's authoritative readback of the policy (same bounded projection as
|
|
895
|
+
reads: never content, description, tenant identity, or creator identity).
|
|
896
|
+
Policies have an `active` lifecycle flag, so `activate` and `deactivate`
|
|
897
|
+
commands exist (they PUT the `active` field):
|
|
898
|
+
|
|
899
|
+
```bash
|
|
900
|
+
uv run --frozen hc ams policies create --environment dev --tenant-id TENANT_ID --name "Appointment Safety" --department-id DEPARTMENT_ID --created-by-user-id ADMIN_USER_ID [--description TEXT] [--content TEXT] [--guardrail-ids GUARDRAIL_ID ...]
|
|
901
|
+
uv run --frozen hc ams policies update --environment dev --tenant-id TENANT_ID --policy-id POLICY_ID [--name NAME] [--description TEXT] [--content TEXT] [--guardrail-ids GUARDRAIL_ID ... | --clear-guardrail-ids]
|
|
902
|
+
uv run --frozen hc ams policies activate --environment dev --tenant-id TENANT_ID --policy-id POLICY_ID
|
|
903
|
+
uv run --frozen hc ams policies deactivate --environment dev --tenant-id TENANT_ID --policy-id POLICY_ID
|
|
904
|
+
uv run --frozen hc ams policies delete --environment dev --tenant-id TENANT_ID --policy-id POLICY_ID
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
- `create` succeeds with `operation: "policies.create"` and `data.policy` =
|
|
908
|
+
the server's 201 readback. `--name`, `--department-id` and
|
|
909
|
+
`--created-by-user-id` are required by the server contract; the tenant is
|
|
910
|
+
taken from the saved session's authenticated admin, never from a flag. The
|
|
911
|
+
server does not validate that `--guardrail-ids` reference real guardrails
|
|
912
|
+
(blank guardrail IDs are rejected client-side). `--content` is write-only:
|
|
913
|
+
it is sent but never echoed back.
|
|
914
|
+
- `update` sends only the options you pass (an empty `--description ""` or
|
|
915
|
+
`--content ""` clears it; `--clear-guardrail-ids` sends an explicit empty
|
|
916
|
+
list to clear the policy's guardrail bindings); it refuses with exit 1
|
|
917
|
+
(`validation`) when no field option is given.
|
|
918
|
+
- `activate`/`deactivate` PUT `active: true|false` and succeed only when the
|
|
919
|
+
readback confirms the requested flag.
|
|
920
|
+
- `delete` succeeds with `data: {"deleted": true}` (the server answers 204
|
|
921
|
+
with no body) and is irreversible. If the server refuses with 409, the CLI
|
|
922
|
+
reports one `conflict` refusal naming the policy; no changes were made.
|
|
923
|
+
- Any other 409 on `create`, `update`, `activate` or `deactivate` is one
|
|
924
|
+
write-worded `conflict` refusal (naming the policy where one was given) that
|
|
925
|
+
says the server refused the write and to review the policy's current server
|
|
926
|
+
state before retrying; no changes were made. Writes are never refused with
|
|
927
|
+
the read-era conflict wording.
|
|
928
|
+
- A 404 for the requested policy is a `not_found` refusal naming the ID (on
|
|
929
|
+
`create`, a 404 names the referenced `--department-id`); a target/tenant
|
|
930
|
+
mismatch on a write readback is a `validation` refusal that reports the
|
|
931
|
+
landed write truthfully: it names the policy and the tenant that actually
|
|
932
|
+
received it and points to `hc ams policies get` — it never claims a landed
|
|
933
|
+
write was stopped.
|
|
934
|
+
- If the transport times out or the connection drops mid-write, the write's
|
|
935
|
+
outcome is unknown: the CLI reports one `transport` error whose message says
|
|
936
|
+
the outcome is unknown and to check `hc ams policies get` before retrying.
|
|
937
|
+
It never re-sends the write on its own.
|
|
938
|
+
|
|
939
|
+
Governed authoring sessions and their messages are first-class commands over the
|
|
940
|
+
VibeCoder session routes. Creating a session and appending a message make no model
|
|
941
|
+
call; they only record authoring state. Sessions are tenant- and user-scoped by the
|
|
942
|
+
server, newest first, and expired sessions are not returned. `--limit` is the only
|
|
943
|
+
pagination the server offers (sessions default 50, maximum 100; messages default
|
|
944
|
+
100, maximum 200) — there are no offsets or cursors, and a limit outside that range
|
|
945
|
+
is refused locally:
|
|
946
|
+
|
|
947
|
+
```bash
|
|
948
|
+
uv run --frozen hc ams sessions list --environment dev --tenant-id TENANT_ID [--limit N]
|
|
949
|
+
uv run --frozen hc ams sessions get --environment dev --tenant-id TENANT_ID --session-id SESSION_ID
|
|
950
|
+
uv run --frozen hc ams sessions create --environment dev --tenant-id TENANT_ID [--agent-id AGENT_ID] [--mode plan|implement] [--initial-prompt TEXT]
|
|
951
|
+
uv run --frozen hc ams sessions messages list --environment dev --tenant-id TENANT_ID --session-id SESSION_ID [--limit N]
|
|
952
|
+
uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENANT_ID --session-id SESSION_ID --content TEXT [--mode plan|implement] [--idempotency-key KEY]
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
- `list` emits `operation: "sessions.list"`, `data.sessions` (bounded projections
|
|
956
|
+
of session ID, agent ID, operation, mode, status and audit timestamps) and a
|
|
957
|
+
`page` block (`{"kind": "limit", "limit": N, "returned_count": N,
|
|
958
|
+
"continuation": null}`). `get` emits `operation: "sessions.get"` and
|
|
959
|
+
`data.session`. The projections never include the session's protected
|
|
960
|
+
`initial_prompt`, tenant or user identity, or arbitrary response fields.
|
|
961
|
+
- `create` and `messages add` require a saved `hc login` session (env-var
|
|
962
|
+
credentials are not accepted for writes). Each write is sent exactly once and
|
|
963
|
+
the envelope carries the server's authoritative readback:
|
|
964
|
+
`operation: "sessions.create"` with `data.session`, and
|
|
965
|
+
`operation: "sessions.messages.add"` with `data.message` (message ID, role,
|
|
966
|
+
mode, plan ID, model, timestamps) plus `data.idempotency_key` when a key was
|
|
967
|
+
supplied. Protected free text — `--initial-prompt` and `--content` — is sent to
|
|
968
|
+
the server but never appears in the envelope, a refusal, a diagnostic or a log.
|
|
969
|
+
- `--initial-prompt` becomes the session's first `user` message in the same
|
|
970
|
+
server transaction; `hc ams sessions messages list` shows it afterwards.
|
|
971
|
+
- `--mode implement` is not available from the server yet: `create` reports one
|
|
972
|
+
`unsupported` refusal (the server answers 501) and creates nothing. A message
|
|
973
|
+
whose `--mode` differs from its session's mode is refused by the server with
|
|
974
|
+
409 and reported as one write-worded `conflict` refusal that says a message's
|
|
975
|
+
mode must match its session's mode and points at `hc ams sessions get`; no
|
|
976
|
+
message is added, and the read-era conflict wording is never used.
|
|
977
|
+
- A missing, expired or other user's session is a `not_found` refusal naming the
|
|
978
|
+
session on `get`, `messages list` and `messages add`; on `create`, a 404 names
|
|
979
|
+
the requested `--agent-id`.
|
|
980
|
+
- Messages with an `--idempotency-key` are replay-safe: the server answers 201
|
|
981
|
+
with the existing message instead of inserting a second one, and the command
|
|
982
|
+
reports that message (its original ID) — a replay is never reported as a new
|
|
983
|
+
write, and the command itself never re-sends a write.
|
|
984
|
+
- If the transport times out or the connection drops mid-write, the write's
|
|
985
|
+
outcome is unknown: the CLI reports one `transport` error whose message says so
|
|
986
|
+
and points at `hc ams sessions list`/`hc ams sessions messages list` before
|
|
987
|
+
retrying. A write readback that lands in a different tenant is reported
|
|
988
|
+
truthfully as `validation`, naming the tenant that actually received it — a
|
|
989
|
+
landed write is never reported as stopped.
|
|
990
|
+
- Reads refuse target files, root credentials or base URLs, raw output, blank or
|
|
991
|
+
dot-segment session IDs, out-of-range limits, and installed SDKs without the
|
|
992
|
+
session list/get and message list/add methods before tenant-admin login or any
|
|
993
|
+
read. The existing `hc ams plan|show|apply|restore` tuning commands and their
|
|
994
|
+
signatures are unchanged.
|
|
995
|
+
- These session and message commands do not attach files, apply or roll back
|
|
996
|
+
configuration, or send session history to a model. Plan review is its own
|
|
997
|
+
command group (below).
|
|
998
|
+
|
|
999
|
+
Session attachments are three commands over the live VibeCoder attachment routes.
|
|
1000
|
+
There is no download command: the server exposes no attachment content route, so
|
|
1001
|
+
the CLI never fetches, prints or reconstructs a file's bytes:
|
|
1002
|
+
|
|
1003
|
+
```bash
|
|
1004
|
+
uv run --frozen hc ams sessions attachments list --environment dev --tenant-id TENANT_ID --session-id SESSION_ID
|
|
1005
|
+
uv run --frozen hc ams sessions attachments upload --environment dev --tenant-id TENANT_ID --session-id SESSION_ID --file PATH
|
|
1006
|
+
uv run --frozen hc ams sessions attachments delete --environment dev --tenant-id TENANT_ID --session-id SESSION_ID --attachment-id ATTACHMENT_ID
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
- `list` emits `operation: "sessions.attachments.list"` and `data.attachments`,
|
|
1010
|
+
each a bounded projection of `id`, `filename`, `content_type`, `size_bytes`,
|
|
1011
|
+
`index_status` and `created_at`. The server offers no pagination on this
|
|
1012
|
+
route, so there is no `page` block and no `--limit`. The projections never
|
|
1013
|
+
include `storage_key`, `content_hash`, tenant or user identity, or any file
|
|
1014
|
+
content. An agent-backed session lists the agent attachments linked to the
|
|
1015
|
+
session; a session with no agent lists its draft attachments. The server
|
|
1016
|
+
guarantees no ordering, so the CLI claims none.
|
|
1017
|
+
- `upload` reads `--file` locally, reads the session once first, then sends the
|
|
1018
|
+
file exactly once as the multipart `file` part under its bare filename, and
|
|
1019
|
+
reports the server's authoritative readback as
|
|
1020
|
+
`operation: "sessions.attachments.upload"` with `data.attachment` (the same
|
|
1021
|
+
projection as `list`). It requires a saved `hc login` session. The file's
|
|
1022
|
+
bytes are never written to stdout, stderr, an error envelope, a diagnostic or
|
|
1023
|
+
a log; only its name and size appear. An agent-backed session derives the
|
|
1024
|
+
content type from the extension and stores an agent attachment (with an
|
|
1025
|
+
indexing status); a session with no agent stores a draft attachment and
|
|
1026
|
+
accepts any extension.
|
|
1027
|
+
- Before sending, `upload` refuses a missing or unreadable file and a file
|
|
1028
|
+
larger than 5 MiB without any request: those bounds apply to both paths. The
|
|
1029
|
+
extension allow-list (`txt`, `md`, `pdf`, `docx`) is the agent path's only,
|
|
1030
|
+
because that path derives the content type from the extension, so it is
|
|
1031
|
+
applied after the session read and only when the session has an `agent_id`; a
|
|
1032
|
+
draft upload of any other extension is sent, and the server answers. The
|
|
1033
|
+
session read is a separate route, so the upload POST itself is still sent
|
|
1034
|
+
exactly once. These local pre-checks mirror the server's own bounds; they
|
|
1035
|
+
never replace the server's answer. When the server
|
|
1036
|
+
does refuse, its status is reported as sent and never normalised: `413` (the
|
|
1037
|
+
route's stream cap) and `422` (the attachment service's size check, an unsafe
|
|
1038
|
+
or blank filename, or an unsupported extension on the agent path) both become
|
|
1039
|
+
one `validation` refusal naming the status and the server's own detail, and
|
|
1040
|
+
nothing is attached. A missing, expired or other user's session is a
|
|
1041
|
+
`not_found` refusal naming the session.
|
|
1042
|
+
- `delete` removes an attachment with a `204` (no body, so the envelope is built
|
|
1043
|
+
from the request identity, never a fabricated response). It requires a saved
|
|
1044
|
+
`hc login` session and reads the session once first, because the route has two
|
|
1045
|
+
different effects and the wording must be truthful: for an agent-backed
|
|
1046
|
+
session the file is only **detached** from the session and the agent
|
|
1047
|
+
attachment and its stored object survive (`data.effect: "detached"`), while a
|
|
1048
|
+
session with no agent has its draft attachment **deleted**
|
|
1049
|
+
(`data.effect: "deleted"`). `data` also carries `attachment_id`, and `detail`
|
|
1050
|
+
states the effect in words. The DELETE itself is sent exactly once. A missing
|
|
1051
|
+
session, or an attachment ID that is not attached to the session, is a
|
|
1052
|
+
`not_found` refusal; nothing is removed.
|
|
1053
|
+
- If the transport times out or the connection drops mid-write, the upload's or
|
|
1054
|
+
delete's outcome is unknown: the CLI reports one `transport` error saying so
|
|
1055
|
+
and pointing at `hc ams sessions attachments list` for that session before
|
|
1056
|
+
retrying. It never re-sends automatically; `list` is the reconciliation step.
|
|
1057
|
+
A refused or failed upload creates no session link, sends no message,
|
|
1058
|
+
generates no plan and makes no model call, and a landed write readback in
|
|
1059
|
+
another tenant is reported truthfully as `validation`.
|
|
1060
|
+
- Attachment commands refuse target files, root credentials or base URLs, raw
|
|
1061
|
+
output, blank or dot-segment session and attachment IDs, and installed SDKs
|
|
1062
|
+
without the attachment list/upload/delete methods before tenant-admin login
|
|
1063
|
+
or any request. Uploads are multipart requests; `APIClient.request` forwards
|
|
1064
|
+
`files`/`data` for them and every existing JSON caller's wire bytes are
|
|
1065
|
+
unchanged.
|
|
1066
|
+
|
|
1067
|
+
Exact create/configure plans are five commands over the existing VibeCoder plan
|
|
1068
|
+
routes: two review commands, one send-once apply, one send-once rollback and one
|
|
1069
|
+
read-only reconciliation. There is no approval receipt and no direct agent write:
|
|
1070
|
+
|
|
1071
|
+
```bash
|
|
1072
|
+
uv run --frozen hc ams sessions plans generate --environment dev --tenant-id TENANT_ID --session-id SESSION_ID [--operation modify|create] [--agent-type Application|Utility] [--prompt-file PATH] [--idempotency-key KEY] --out PATH
|
|
1073
|
+
uv run --frozen hc ams sessions plans get --environment dev --tenant-id TENANT_ID --plan-id PLAN_ID --out PATH
|
|
1074
|
+
uv run --frozen hc ams sessions plans apply --environment dev --tenant-id TENANT_ID --plan-id PLAN_ID --confirm-hash 64_HEX_PLAN_HASH [--expected-config-version N] --confirm
|
|
1075
|
+
uv run --frozen hc ams sessions plans rollback --environment dev --tenant-id TENANT_ID --plan-id PLAN_ID --expected-config-version N [--reason TEXT] --confirm
|
|
1076
|
+
uv run --frozen hc ams sessions plans reconcile --environment dev --tenant-id TENANT_ID --plan-id PLAN_ID
|
|
1077
|
+
```
|
|
1078
|
+
|
|
1079
|
+
- `generate` sends exactly one `POST /vibecoder/sessions/SESSION_ID/plan` with
|
|
1080
|
+
the explicit session ID; it makes no preflight read and no other request, no
|
|
1081
|
+
message write, no `PUT` and no apply. The server runs the planner once. It
|
|
1082
|
+
requires a saved `hc login` session and reports
|
|
1083
|
+
`operation: "sessions.plans.generate"` with `data.plan` and `data.out`.
|
|
1084
|
+
`get` sends exactly one `GET /vibecoder/plans/PLAN_ID` and reports
|
|
1085
|
+
`operation: "sessions.plans.get"`; a reload is immutable, so it returns the
|
|
1086
|
+
same `plan_hash` and diff however the session changed afterwards. `get` is a
|
|
1087
|
+
read and needs no saved `hc login` session.
|
|
1088
|
+
- `--out` is required and must be a new path: the review is written to a
|
|
1089
|
+
newly created owner-only file (mode `0600`, no symlinks, never overwritten).
|
|
1090
|
+
A refusal removes the reservation again, so a refused review never leaves a
|
|
1091
|
+
file behind.
|
|
1092
|
+
- The review file carries the exact target (`environment`, `tenant_id`,
|
|
1093
|
+
`session_id`, `agent_id` — explicit `null` for a creation plan — and
|
|
1094
|
+
`plan_id`), `operation`, `status`, `base_config_version` (`null` for create),
|
|
1095
|
+
`plan_hash`, `model`, `summary`, the advisory `review_warnings` with their
|
|
1096
|
+
own `severity`, `file_actions` as attachment **IDs only**, and the diff:
|
|
1097
|
+
`changes` with each returned field's `before` and `after` for a configure
|
|
1098
|
+
plan, or `definition` (name, description, type, model, `is_orchestrator`,
|
|
1099
|
+
department/team, instructions, greetings, opening suggestions) plus
|
|
1100
|
+
`references` (catalog IDs grouped by department/team/tool/skill/policy/
|
|
1101
|
+
guardrail/data-source) for a creation plan. `plan_json["original_prompt"]`
|
|
1102
|
+
is never projected, and `warnings`/`skipped_references` are reported as
|
|
1103
|
+
counts, not bodies.
|
|
1104
|
+
- stdout stays bounded and free-text-free: plan ID, `plan_hash`, operation,
|
|
1105
|
+
status, base config version, agent ID, model, sorted changed-field **names**
|
|
1106
|
+
(create: reference counts by kind) and finding counts. No prompt,
|
|
1107
|
+
instructions, greeting, summary, message content or plan value is printed to
|
|
1108
|
+
stdout, stderr, a diagnostic or an error string.
|
|
1109
|
+
- The prompt is protected free text and never comes from argv: `--prompt-file`
|
|
1110
|
+
reads one owner-only regular text file (no symlinks, at most 20000
|
|
1111
|
+
characters) and sends it once. `--prompt-file` is optional — the server falls
|
|
1112
|
+
back to the session's initial prompt or last message — and `--agent-type`
|
|
1113
|
+
applies to `--operation create` only (with `modify` it is a usage error
|
|
1114
|
+
before login or any request). `--prompt-file` is not mirrored as a local
|
|
1115
|
+
"prompt required" rule: a creation plan with no prompt anywhere keeps the
|
|
1116
|
+
server's own `422 "a prompt is required for agent creation"`.
|
|
1117
|
+
- It refuses rather than endorses. A configure plan that changes anything
|
|
1118
|
+
outside the existing tuning field set (`instructions`, `greetings`) is
|
|
1119
|
+
refused by field name — in particular capability/permission expansion
|
|
1120
|
+
(`tool_ids`, `skill_ids`, `policy_ids`, `guardrail_ids`, `data_source_ids`,
|
|
1121
|
+
`department_id`, `team_id`, `is_orchestrator`), lifecycle/`status`, model and
|
|
1122
|
+
file actions: permission assignment stays a separately reviewed capability.
|
|
1123
|
+
It also refuses a plan whose `status` is not `proposed`, whose `plan_hash` is
|
|
1124
|
+
not 64 hex characters, whose `session_id`/`tenant_id`/`operation` differ from
|
|
1125
|
+
the explicit target, whose `plan_json` carries unknown keys, or whose file
|
|
1126
|
+
actions name anything but an attachment ID. A `status: "invalid"` plan is
|
|
1127
|
+
refused instead of reviewed, and its `validation_errors` are reported by
|
|
1128
|
+
count and never quoted — that server text can contain protected field
|
|
1129
|
+
values, so read it in the AMS console. Non-`advisory` review findings are
|
|
1130
|
+
refused the same way; advisory findings are displayed in the review file.
|
|
1131
|
+
A creation plan that claims an `agent_id` is refused, and no existing agent
|
|
1132
|
+
ID is ever fabricated or required.
|
|
1133
|
+
- Server answers are preserved, not replaced: a missing, expired or other
|
|
1134
|
+
user's session or plan is a `not_found` refusal naming it, a `422` from the
|
|
1135
|
+
plan route is a `validation` refusal carrying the server's own bounded
|
|
1136
|
+
detail, and an unavailable planner (`503`) is a `transport` refusal with no
|
|
1137
|
+
file written. A dropped connection on `generate` reports one `transport`
|
|
1138
|
+
error saying the plan's outcome is unknown and points at
|
|
1139
|
+
`hc ams sessions messages list`/`plans get` before retrying; the request is
|
|
1140
|
+
never re-sent automatically.
|
|
1141
|
+
- Both commands refuse target files, root credentials or base URLs, raw output,
|
|
1142
|
+
blank or dot-segment session and plan IDs, a prompt file that is not a
|
|
1143
|
+
readable owner-only regular file, and installed SDKs without the plan
|
|
1144
|
+
generate/get methods before tenant-admin login (or, for `generate`, before
|
|
1145
|
+
any request). The existing `hc ams plan|show|apply|restore` tuning commands
|
|
1146
|
+
and their signatures are unchanged.
|
|
1147
|
+
|
|
1148
|
+
Applying, rolling back and reconciling one plan are separate capabilities on the
|
|
1149
|
+
same routes. The commands never retry, never replay, never send a second
|
|
1150
|
+
mutation and never fall back to `PUT /agents/{id}`:
|
|
1151
|
+
|
|
1152
|
+
- `apply` requires a saved `hc login` session, `--confirm` and the exact
|
|
1153
|
+
64-hex `--confirm-hash` from the review. It reads the plan **once**, runs its
|
|
1154
|
+
local pre-checks, sends **exactly one**
|
|
1155
|
+
`POST /vibecoder/plans/PLAN_ID/apply` with the reviewed `plan_hash`, the
|
|
1156
|
+
version the plan itself carries and `confirm: true`, then reads the plan (and
|
|
1157
|
+
the agent, when the plan names one) back. Approving a plan binds exactly what
|
|
1158
|
+
was reviewed: a configure plan whose changes fall outside `instructions`/
|
|
1159
|
+
`greetings`, or which carries file actions, is refused by field name even
|
|
1160
|
+
though the server would accept it, and a creation plan that claims an agent
|
|
1161
|
+
ID, reports existing-field changes or carries a file write/path action is
|
|
1162
|
+
refused the same way. A plan that is not `proposed`, carries
|
|
1163
|
+
`validation_errors`, has blocking review findings, or whose hash no longer
|
|
1164
|
+
matches `--confirm-hash` is refused before anything is sent.
|
|
1165
|
+
- A **configure** `apply` requires `--expected-config-version` equal to the
|
|
1166
|
+
plan's own `base_config_version`; a mismatch is a local refusal, never a stale
|
|
1167
|
+
send. A **creation** plan has no baseline (`base_config_version` is `null`),
|
|
1168
|
+
so `--expected-config-version` must **not** be supplied: the request sends `1`
|
|
1169
|
+
only because the pinned route schema requires `expected_config_version >= 1`
|
|
1170
|
+
while the server's creation branch never reads it. That `1` is a documented
|
|
1171
|
+
**protocol filler, not a baseline claim**, and the CLI never reports it as
|
|
1172
|
+
evidence.
|
|
1173
|
+
- `apply` reports three evidence sections independently and never collapses
|
|
1174
|
+
them into "applied": `data.evidence.saved` (the plan reloaded after the write
|
|
1175
|
+
is `applied`/`publish_failed`; for a configure plan the agent's
|
|
1176
|
+
`config_version` advanced by exactly one from the plan's own
|
|
1177
|
+
`base_config_version` and every reviewed field equals its `after` value; for
|
|
1178
|
+
a creation plan the created `agent_id` was read back in this tenant and no
|
|
1179
|
+
version baseline is claimed), `data.evidence.side_effects` (files are
|
|
1180
|
+
`unverified` because the pinned contract exposes no attachment/file readback,
|
|
1181
|
+
and publication is `not_required` when the server reports
|
|
1182
|
+
`publication_status: not_applicable`, otherwise `unknown`/`failed` — never
|
|
1183
|
+
"serving"), and `data.evidence.runtime` (always `unverified`, because there is
|
|
1184
|
+
no serving/runtime route). `data.mutation_replayed` is always `false`.
|
|
1185
|
+
- Exit codes are strict: `0` **only** when the write is provably saved and
|
|
1186
|
+
readback-confirmed (the `verification` block then reads `saved_state:
|
|
1187
|
+
confirmed`, `side_effects: unverified`, `runtime: unverified`). A readback
|
|
1188
|
+
mismatch, a saved-but-unpublished result, or an unknown outcome exits nonzero
|
|
1189
|
+
with the bounded JSON envelope on stderr.
|
|
1190
|
+
- `apply` surfaces the server's own answers and never its free text: a missing
|
|
1191
|
+
plan is a `not_found` refusal naming it; a `409` names only the structured
|
|
1192
|
+
reason code (`plan_hash_mismatch`, `already_applied`,
|
|
1193
|
+
`plan_not_applicable`, `attachments_changed`, `stale_baseline`,
|
|
1194
|
+
`concurrent_edit`) with bounded local wording; a `422` is reported by
|
|
1195
|
+
validation-error **count** only, because that server text can quote protected
|
|
1196
|
+
field values.
|
|
1197
|
+
- **Unknown outcome is never success and never a replay.** On a transport
|
|
1198
|
+
failure or any `5xx` from `/apply` (the mutation may have committed) the
|
|
1199
|
+
command sends nothing further, records `outcome: "unknown"`, runs the same
|
|
1200
|
+
read-only reconciliation `reconcile` performs, prints it together with
|
|
1201
|
+
`mutation_replayed: false`, and exits nonzero telling the operator to inspect
|
|
1202
|
+
before acting. The only supported follow-up is `plans reconcile`, or
|
|
1203
|
+
`plans rollback` once the state is known.
|
|
1204
|
+
- `rollback` requires a saved `hc login` session, `--confirm`,
|
|
1205
|
+
`--expected-config-version` equal to the agent's **current**
|
|
1206
|
+
`config_version`, and reads the plan and agent first. It requires the plan to
|
|
1207
|
+
be an applied configure plan (`status` `applied` or `publish_failed`, with an
|
|
1208
|
+
`agent_id`); a mismatch is refused locally, never sent. It then sends
|
|
1209
|
+
**exactly one** `POST /vibecoder/plans/PLAN_ID/rollback` (the pinned request
|
|
1210
|
+
schema requires `reason`, so `--reason` is sent as a bounded string, `""`
|
|
1211
|
+
when omitted, and is never echoed), reads both back, and reports the same
|
|
1212
|
+
three evidence sections. `--reason` is protected free text: it never reaches
|
|
1213
|
+
stdout, stderr, a diagnostic or an error string.
|
|
1214
|
+
- **Rollback is not restore and neither is deletion recovery.** `hc ams
|
|
1215
|
+
restore` is the tuning contract's selected-field restore: receipt-bound fields
|
|
1216
|
+
put back on the same agent. `hc ams sessions plans rollback` asks the server to
|
|
1217
|
+
revert the fields **one applied plan** changed, version-checked. A creation
|
|
1218
|
+
plan changed no existing field, so it is refused locally: deleting an agent a
|
|
1219
|
+
creation plan created is a different capability this CLI does not add. The
|
|
1220
|
+
existing `hc ams plan|show|apply|restore` tuning commands and their
|
|
1221
|
+
signatures, output and receipts are unchanged.
|
|
1222
|
+
- `reconcile` is the read-only recovery path: it sends no `POST`, no `PUT` and no
|
|
1223
|
+
`DELETE`. It reloads the plan, reads its agent when it names one, and reports
|
|
1224
|
+
the plan `status` and `plan_hash`, the `agent_id`, the agent's
|
|
1225
|
+
`config_version`, which planned field values are `present`/`absent`/`other`,
|
|
1226
|
+
and `mutation_replayed: false`; side effects and runtime stay `unverified`.
|
|
1227
|
+
It is what an operator runs after any uncertain outcome, before any further
|
|
1228
|
+
write.
|
|
1229
|
+
- All three refuse target files, root credentials or base URLs, raw output,
|
|
1230
|
+
blank or dot-segment plan IDs, an apply/rollback without `--confirm`, a
|
|
1231
|
+
malformed `--confirm-hash`, an out-of-range `--expected-config-version`, a
|
|
1232
|
+
`--reason` longer than 2000 characters, and installed SDKs without the plan
|
|
1233
|
+
get/apply/rollback methods before tenant-admin login or any request.
|
|
1234
|
+
|
|
1235
|
+
Guardrail catalog listing and inspection are complete and unpaged. The bounded
|
|
1236
|
+
projection includes only guardrail IDs, names, and audit timestamps. It never
|
|
1237
|
+
emits protected rules, descriptions, tenant or creator identity, arbitrary
|
|
1238
|
+
response fields, or joined policy/agent details:
|
|
1239
|
+
|
|
1240
|
+
```bash
|
|
1241
|
+
uv run --frozen hc ams guardrails list --environment dev --tenant-id TENANT_ID
|
|
1242
|
+
uv run --frozen hc ams guardrails get --environment dev --tenant-id TENANT_ID \
|
|
1243
|
+
--guardrail-id GUARDRAIL_ID
|
|
1244
|
+
```
|
|
1245
|
+
|
|
1246
|
+
Reads make exactly one catalog GET after tenant-admin login and do not paginate,
|
|
1247
|
+
retry, cache, join, or infer additional data. Guardrail reads refuse target
|
|
1248
|
+
files, root credentials or base URLs, raw output, blank or dot-segment guardrail
|
|
1249
|
+
IDs, and installed SDKs without both catalog methods before tenant-admin login or
|
|
1250
|
+
catalog reads.
|
|
1251
|
+
|
|
1252
|
+
Guardrail writes are first-class, send-once commands that require a saved
|
|
1253
|
+
`hc login` session (env-var credentials are not accepted for writes). Each write
|
|
1254
|
+
is sent exactly once — the CLI never retries a write — and the result envelope
|
|
1255
|
+
carries the server's authoritative readback of the guardrail (same bounded
|
|
1256
|
+
projection as reads: never rules, description, tenant identity, or creator
|
|
1257
|
+
identity). A guardrail has no `active` flag and no `config_version`, so there is
|
|
1258
|
+
no `activate`/`deactivate` command and no expected-version option: the server's
|
|
1259
|
+
write routes carry no `If-Match`, and the CLI will not advertise a guard the
|
|
1260
|
+
server does not offer.
|
|
1261
|
+
|
|
1262
|
+
```bash
|
|
1263
|
+
uv run --frozen hc ams guardrails create --environment dev --tenant-id TENANT_ID --name NAME --created-by-user-id ADMIN_USER_ID [--description TEXT] [--rules-file RULES_JSON_FILE]
|
|
1264
|
+
uv run --frozen hc ams guardrails update --environment dev --tenant-id TENANT_ID --guardrail-id GUARDRAIL_ID [--name NAME] [--description TEXT] [--rules-file RULES_JSON_FILE]
|
|
1265
|
+
uv run --frozen hc ams guardrails delete --environment dev --tenant-id TENANT_ID --guardrail-id GUARDRAIL_ID --confirm
|
|
1266
|
+
```
|
|
1267
|
+
|
|
1268
|
+
- `create` succeeds with `operation: "guardrails.create"` and
|
|
1269
|
+
`data.guardrail` = the server's 201 readback. `--name` and
|
|
1270
|
+
`--created-by-user-id` are required by the server contract; the tenant is
|
|
1271
|
+
taken from the saved session's authenticated admin, never from a flag.
|
|
1272
|
+
`--rules-file` names a regular JSON file holding the rules object with string
|
|
1273
|
+
keys. Rules never ride argv (so they stay out of the process list) and are
|
|
1274
|
+
never echoed: an unreadable, symlinked, duplicate-keyed or non-object file is
|
|
1275
|
+
refused with one fixed message that does not quote its content. Rules are
|
|
1276
|
+
write-only — they are sent but never appear in any command output, error
|
|
1277
|
+
message or readback.
|
|
1278
|
+
- `update` sends only the options you pass (an empty `--description ""` clears
|
|
1279
|
+
it; a `--rules-file` holding `{}` sends an empty rule set); it refuses with exit 1
|
|
1280
|
+
(`validation`) when no field option is given.
|
|
1281
|
+
- `delete` is a reviewed one-shot destructor and requires `--confirm`; without
|
|
1282
|
+
it the command exits 2 before login and sends nothing. With it the CLI makes
|
|
1283
|
+
exactly one `GET` for the guardrail, one `GET /agents` and one `GET /policies`
|
|
1284
|
+
to compute the affected references, prints the resolved target, those
|
|
1285
|
+
references and the disclosure block to stderr (pretty mode only; `--output
|
|
1286
|
+
json|raw` keeps stderr machine-parsable), sends exactly one `DELETE`, then
|
|
1287
|
+
makes exactly one confirmation `GET`. Only a 404 there proves the deletion:
|
|
1288
|
+
the command reports `data.deleted: true` and `verification.saved_state:
|
|
1289
|
+
confirmed` for a 404, `data.deleted: false` with `saved_state: failed` when
|
|
1290
|
+
the guardrail is still served, and `outcome: unknown` with
|
|
1291
|
+
`data.mutation_replayed: false` and a `not_read` reconciliation when the
|
|
1292
|
+
answer is 5xx or the transport fails. It never replays, retries or re-reads on
|
|
1293
|
+
its own, and it never claims a restore: `hc ams restore` replays tuning fields
|
|
1294
|
+
onto an existing agent and can never restore a deleted guardrail.
|
|
1295
|
+
- The server guards one reference only: an agent whose `guardrail_ids` contain
|
|
1296
|
+
the guardrail answers 409 and nothing is deleted (the CLI reports one
|
|
1297
|
+
`conflict` refusal). A policy whose `guardrail_ids` still name it is NOT
|
|
1298
|
+
guarded and is not repaired, so a later agent configuration read that resolves
|
|
1299
|
+
that policy fails; the disclosure says so, and the printed affected references
|
|
1300
|
+
name every policy that still points at the guardrail. When the agent or policy
|
|
1301
|
+
catalog cannot be read, the delete is refused (`nothing was changed`) rather
|
|
1302
|
+
than executed without its disclosure.
|
|
1303
|
+
- Any other 409 on `create` or `update` is one write-worded `conflict` refusal
|
|
1304
|
+
that says the server refused the write and to review the guardrail's current
|
|
1305
|
+
server state before retrying; no changes were made.
|
|
1306
|
+
- A 404 for the requested guardrail on `update` is a `not_found` refusal naming
|
|
1307
|
+
the ID; a target/tenant mismatch on a write readback is a `validation` refusal
|
|
1308
|
+
that reports the landed write truthfully: it names the guardrail and the
|
|
1309
|
+
tenant that actually received it and points to `hc ams guardrails get`.
|
|
1310
|
+
- If the transport times out or the connection drops mid-write, the write's
|
|
1311
|
+
outcome is unknown: the CLI reports one `transport` error whose message says
|
|
1312
|
+
the outcome is unknown and to check `hc ams guardrails get` before retrying.
|
|
1313
|
+
It never re-sends the write on its own.
|
|
1314
|
+
|
|
1315
|
+
Read-only tool catalog listing and inspection are complete and unpaged. The
|
|
1316
|
+
bounded projection includes only IDs, names, read-only and confirmation flags,
|
|
1317
|
+
agent types, required scopes, audit events, and activity labels:
|
|
1318
|
+
|
|
1319
|
+
```bash
|
|
1320
|
+
uv run --frozen hc ams tools list --environment dev --tenant-id TENANT_ID
|
|
1321
|
+
uv run --frozen hc ams tools get --environment dev --tenant-id TENANT_ID \
|
|
1322
|
+
--tool-id TOOL_ID
|
|
1323
|
+
```
|
|
1324
|
+
|
|
1325
|
+
When both target flags are omitted, these commands may use the matching saved
|
|
1326
|
+
tenant-admin session. Explicit flags must match that session. Tool output never
|
|
1327
|
+
includes descriptions, parameters, input schemas, PIP or connector routing, or
|
|
1328
|
+
arbitrary response fields. The CLI does not execute, assign, create, update, or
|
|
1329
|
+
delete tools; join agents or skills; or claim assignment, availability,
|
|
1330
|
+
authorization, deployment, serving, or runtime effectiveness. It makes exactly
|
|
1331
|
+
one catalog GET after tenant-admin login and does not paginate, retry, cache,
|
|
1332
|
+
join, or infer additional data. Tool reads refuse target files, root credentials
|
|
1333
|
+
or base URLs, raw output, blank or dot-segment IDs, and installed SDKs without
|
|
1334
|
+
both tool methods before tenant-admin login or catalog reads.
|
|
1335
|
+
|
|
1336
|
+
Read-only data-source catalog listing and inspection are complete and unpaged.
|
|
1337
|
+
The bounded projection includes only stable IDs and names:
|
|
1338
|
+
|
|
1339
|
+
```bash
|
|
1340
|
+
uv run --frozen hc ams data-sources list --environment dev --tenant-id TENANT_ID
|
|
1341
|
+
uv run --frozen hc ams data-sources get --environment dev --tenant-id TENANT_ID \
|
|
1342
|
+
--data-source-id DATA_SOURCE_ID
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
When both target flags are omitted, these commands may use the matching saved
|
|
1346
|
+
tenant-admin session. Explicit flags must match that session. Data-source output
|
|
1347
|
+
never includes descriptions or arbitrary response fields and does not imply
|
|
1348
|
+
access, patient consent, record availability, assignment, authorization, or
|
|
1349
|
+
runtime effectiveness. The CLI does not create, update, delete, assign, access
|
|
1350
|
+
records through, or execute data sources. It makes exactly one catalog GET after
|
|
1351
|
+
tenant-admin login and does not paginate, retry, cache, truncate, join, or infer
|
|
1352
|
+
additional data. Data-source reads refuse target files, root credentials or base
|
|
1353
|
+
URLs, raw output, blank or dot-segment IDs, and installed SDKs without both
|
|
1354
|
+
data-source methods before tenant-admin login or catalog reads.
|
|
1355
|
+
|
|
1356
|
+
### AMS output modes
|
|
1357
|
+
|
|
1358
|
+
The read commands (`hc ams skills|tools|policies|guardrails|data-sources|teams|departments list|get`
|
|
1359
|
+
and `hc ams organization hierarchy`) render in two modes (`--output` on the root
|
|
1360
|
+
`hc` command; `pretty` is the default):
|
|
1361
|
+
|
|
1362
|
+
- **`pretty`** (default): a compact human view. Lists print an `ID`/`NAME`
|
|
1363
|
+
table with an entry count; a single record prints `key: value` lines; the
|
|
1364
|
+
organization hierarchy prints an indented tree. Each failed AMS command
|
|
1365
|
+
prints one stderr line, `error: <message> (<category>)` (flag/argument
|
|
1366
|
+
usage errors from Click print Click's standard usage message instead).
|
|
1367
|
+
- **`json`**: the stable result envelope (below) on stdout. When stdout is not
|
|
1368
|
+
a terminal, the envelope is printed as one compact line; on a terminal it is
|
|
1369
|
+
indented. Errors keep the structured JSON envelope on stderr.
|
|
1370
|
+
|
|
1371
|
+
The JSON envelope carries `schema_version`, `operation`, `target`, `outcome`,
|
|
1372
|
+
and either `data` (succeeded) or `error: {category, message}` (failed). On
|
|
1373
|
+
read-only operations the envelope no longer includes the `verification` block:
|
|
1374
|
+
every field there is `not_applicable` on a read, so it was boilerplate. If a
|
|
1375
|
+
future mutating operation reintroduces it, `verification` reappears only with
|
|
1376
|
+
non-`not_applicable` content. Everything else about the envelope — field names,
|
|
1377
|
+
ordering semantics, categories — is unchanged and stays parseable.
|
|
1378
|
+
|
|
1379
|
+
`--raw`/`--output raw` stays refused for AMS (`Raw output is refused for AMS.`),
|
|
1380
|
+
and refusals keep the JSON error envelope on stderr.
|
|
1381
|
+
|
|
1382
|
+
```bash
|
|
1383
|
+
hc ams skills list --environment dev --tenant-id TENANT_ID # pretty table
|
|
1384
|
+
hc ams skills get --environment dev --tenant-id TENANT_ID --skill-id SKILL_ID # key: value
|
|
1385
|
+
hc --output json ams skills list --environment dev --tenant-id TENANT_ID # compact envelope
|
|
1386
|
+
hc --output json ams skills list --environment dev --tenant-id TENANT_ID | jq .data
|
|
1387
|
+
```
|
|
1388
|
+
|
|
1389
|
+
### What `hc ams` errors report
|
|
1390
|
+
|
|
1391
|
+
In `json` and `raw` modes each failed AMS command prints one JSON object on stderr with
|
|
1392
|
+
an `error.category` and a message naming the real cause; in `pretty` mode the
|
|
1393
|
+
same message is exactly one stderr line, `error: <message> (<category>)`:
|
|
1394
|
+
|
|
1395
|
+
- Local refusals keep their specific message and exit `1` with category
|
|
1396
|
+
`validation`: `Raw output is refused for AMS.`, `AMS target does not match the
|
|
1397
|
+
saved session; stopped before authentication.`, identity mismatches
|
|
1398
|
+
(`Skill/tenant identity mismatch; stopped.`), and dot-segment ID refusals.
|
|
1399
|
+
- No saved session and no target flags: category `authentication` with
|
|
1400
|
+
`Not logged in; run \`hc login --environment <env> --tenant-id <tenant>\`.`
|
|
1401
|
+
- A `get` whose requested ID does not exist (HTTP 404): category `not_found`
|
|
1402
|
+
naming the ID, e.g. `Skill 'nosuch' was not found.` (a 404 on `list` stays
|
|
1403
|
+
`validation`; departments keeps its legacy contract for all read statuses,
|
|
1404
|
+
while department write 404s are `not_found` refusals naming the ID).
|
|
1405
|
+
- HTTP 401/403 map to `authentication`/`authorization`; 409 to `conflict`;
|
|
1406
|
+
network failures to `transport`; local output failures to `local_io`.
|
|
1407
|
+
- Any unexpected internal error is category `internal` with a generic message;
|
|
1408
|
+
it is never labeled `validation` and never leaks exception details.
|
|
1409
|
+
|
|
1410
|
+
```bash
|
|
1411
|
+
hc --raw ams skills list --environment dev --tenant-id TENANT_ID # validation: raw refused
|
|
1412
|
+
hc ams skills list --tenant-id OTHER_TENANT # validation: target mismatch
|
|
1413
|
+
hc ams skills list # authentication: not logged in
|
|
1414
|
+
hc ams skills get --skill-id NOSUCH # not_found: names the ID
|
|
1415
|
+
```
|
|
1416
|
+
|
|
1417
|
+
Every other command requires an explicit local, non-secret target JSON file.
|
|
1418
|
+
Only the canonical HTTPS AMS origin for that environment is accepted; custom
|
|
1419
|
+
origins require separately verified support. Target and nested qualification keys
|
|
1420
|
+
are strict. Unknown/unavailable qualification may be represented by omitted
|
|
1421
|
+
records or empty qualification `{}` for inspection/UI, but blocks planning.
|
|
1422
|
+
|
|
1423
|
+
The following is a **schema example**, not evidence of deployed CareCo eligibility.
|
|
1424
|
+
Replace its assertions only after an operator has established the corresponding
|
|
1425
|
+
proof. `deployed_build` and `proof_ref` are references, never credentials:
|
|
1426
|
+
|
|
1427
|
+
```json
|
|
1428
|
+
{
|
|
1429
|
+
"schema_version": 1,
|
|
1430
|
+
"environment": "dev",
|
|
1431
|
+
"tenant_id": "TENANT_ID",
|
|
1432
|
+
"agent_id": "EXPLICIT_SELECTED_AGENT_ID",
|
|
1433
|
+
"api_origin": "https://dev-amsservices.health.cloud",
|
|
1434
|
+
"qualification": {
|
|
1435
|
+
"runtime": {"source": "OPERATOR_VERIFIED_RUNTIME", "proof_ref": "RUNTIME_PROOF"},
|
|
1436
|
+
"instructions": {"source": "agent.instructions", "proof_ref": "INSTRUCTION_SOURCE_PROOF"},
|
|
1437
|
+
"greetings": {"source": "agent.greetings", "proof_ref": "GREETING_SOURCE_PROOF"},
|
|
1438
|
+
"backend_model_context": {
|
|
1439
|
+
"deployed_build": "OPERATOR_VERIFIED_DEPLOYED_REVISION",
|
|
1440
|
+
"config_version": 7,
|
|
1441
|
+
"proof_ref": "FULL_INPUT_AND_WRITER_COVERAGE_REVIEW_PROOF",
|
|
1442
|
+
"current_build_confirmed": true,
|
|
1443
|
+
"fresh_session": true,
|
|
1444
|
+
"no_attachments_or_history": true,
|
|
1445
|
+
"full_inputs_approved": true,
|
|
1446
|
+
"change_path_without_logs": true
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
```
|
|
1451
|
+
|
|
1452
|
+
Qualification is an operator assertion with evidence, never authorization.
|
|
1453
|
+
Server-side tenant-admin authorization remains mandatory. No current-build API is
|
|
1454
|
+
established: the CLI cannot automatically detect an unreported deployment, verify
|
|
1455
|
+
actual runtime consumption, attest deployed all-writer coverage, or audit model
|
|
1456
|
+
inputs. A changed/unavailable build or agent configuration invalidates the review;
|
|
1457
|
+
the operator must refresh the record. The recorded config version must equal the
|
|
1458
|
+
inspected version before planning/apply/restore.
|
|
1459
|
+
`full_inputs_approved` means an operator approved non-sensitive **full planner and
|
|
1460
|
+
reviewer inputs**, including custom_tools, catalogs, all agent fields, and all other
|
|
1461
|
+
server context. Output projection does not sanitize server-side model context.
|
|
1462
|
+
`change_path_without_logs` requires evidence that the exact deterministic modify
|
|
1463
|
+
request uses the change path, avoiding the direct-answer branch's recent logs.
|
|
1464
|
+
Unknown effective sources and file-backed/ignored instructions block tuning.
|
|
1465
|
+
|
|
1466
|
+
Before `plan`, `apply` or selected-field `restore`, the operator must review evidence
|
|
1467
|
+
that the actual serving build includes **all writer version coverage**: ordinary
|
|
1468
|
+
agent updates and startup/migration repair writers advance `config_version`
|
|
1469
|
+
atomically with their changes, and tuning apply uses transactional compare-and-set
|
|
1470
|
+
against the expected version. Bind this coverage evidence and the full-input
|
|
1471
|
+
review together in the existing `qualification.backend_model_context.proof_ref`
|
|
1472
|
+
review bundle for its `deployed_build`, inspected `config_version` and
|
|
1473
|
+
`current_build_confirmed` assertion. AMS source prerequisites
|
|
1474
|
+
[#488](https://github.com/healthcloudservices/healthcloud-ams-services/pull/488) and
|
|
1475
|
+
[#497](https://github.com/healthcloudservices/healthcloud-ams-services/pull/497)
|
|
1476
|
+
are evidence leads; source tests, PR merges, or a numeric `config_version` alone
|
|
1477
|
+
cannot prove coverage in the serving build. If deployed identity or any writer's
|
|
1478
|
+
coverage is unknown, or a writer still bypasses version advancement, stop
|
|
1479
|
+
`plan`/`apply`/`restore`; bounded read-only inspection/UI may continue under their
|
|
1480
|
+
existing limits. Refresh this evidence after a changed/unknown build or agent
|
|
1481
|
+
configuration and recheck it before apply and selected-field restore. This is an
|
|
1482
|
+
operator prerequisite: the current CLI does not automatically attest deployment
|
|
1483
|
+
or all-writer coverage, and the record adds no server authorization boundary.
|
|
1484
|
+
|
|
1485
|
+
For CI and scripts with no saved session, credentials may still enter through
|
|
1486
|
+
`HC_AMS_ADMIN_EMAIL` and `HC_AMS_ADMIN_PASSWORD` in the current process, intended
|
|
1487
|
+
for composition with the existing `onepassword-env` wrapper; that compatibility
|
|
1488
|
+
path logs in once per invocation. There is no fallback identity, secret argument,
|
|
1489
|
+
arbitrary token import, resolved env file, keychain, or browser credential bridge. Root MCP
|
|
1490
|
+
`--access-token`, `--base-url`, their environment equivalents, and token/base-URL
|
|
1491
|
+
profiles are refused for AMS. Root profile environment values do not select AMS.
|
|
1492
|
+
|
|
1493
|
+
```bash
|
|
1494
|
+
uv run --frozen hc ams --target-file TARGET.json inspect
|
|
1495
|
+
uv run --frozen hc ams --target-file TARGET.json inspect --out PRIVATE_INSPECTION.json
|
|
1496
|
+
uv run --frozen hc ams --target-file TARGET.json plan --changes-file PRIVATE_CHANGES.json --out PRIVATE_RECEIPT.json
|
|
1497
|
+
uv run --frozen hc ams --target-file TARGET.json show --receipt PRIVATE_RECEIPT.json --out PRIVATE_DIFF.json
|
|
1498
|
+
uv run --frozen hc ams --target-file TARGET.json apply --receipt PRIVATE_RECEIPT.json --confirm-plan-hash DISPLAYED_HASH
|
|
1499
|
+
uv run --frozen hc ams --target-file TARGET.json restore --receipt PRIVATE_RECEIPT.json --out PRIVATE_RESTORE_RECEIPT.json
|
|
1500
|
+
uv run --frozen hc ams --target-file TARGET.json ui --repo EXISTING_AMS_WORKTREE
|
|
1501
|
+
```
|
|
1502
|
+
|
|
1503
|
+
Changes JSON accepts only nonempty subsets of `instructions` (string) and
|
|
1504
|
+
`greetings` (language-key to string map). Input changes/receipts must be regular,
|
|
1505
|
+
owner-only files (for example create under `umask 077`). Outputs use exclusive
|
|
1506
|
+
0600 creation on POSIX and an owner-only protected DACL on Windows, and refuse
|
|
1507
|
+
overwrite/symlinks; failed operations remove unfinished
|
|
1508
|
+
outputs. No whole-agent snapshot is saved. Free text appears only in explicitly
|
|
1509
|
+
requested private files for human review before any AI use. Default output in
|
|
1510
|
+
pretty/json/verbose modes contains bounded metadata and hashes; AMS refuses raw.
|
|
1511
|
+
|
|
1512
|
+
The planner receives `Update the agent configuration to exactly these field
|
|
1513
|
+
values: ` followed by sorted, compact requested JSON. Extra fields, files,
|
|
1514
|
+
validation errors, warnings/skipped references, blocking or unknown review
|
|
1515
|
+
findings, and approximate wording are refused. Each immutable field change must
|
|
1516
|
+
include `before` and exact `after` values so a modified receipt cannot redefine a
|
|
1517
|
+
restore. A server plan omitting `before` cannot safely support this workflow;
|
|
1518
|
+
review/replan rather than synthesizing server provenance. `show`/`apply` reload the
|
|
1519
|
+
immutable backend plan/hash. Before apply, recheck the deployed coverage and
|
|
1520
|
+
full-input qualification above. Apply requires the displayed hash, matching
|
|
1521
|
+
version and selected before-values, and sends `confirm=True` once. HTTP 401/403
|
|
1522
|
+
and 409 never trigger identity fallback or mutation replay.
|
|
1523
|
+
|
|
1524
|
+
On transport failure or HTTP 5xx, apply records an unknown outcome and reconciles through GET
|
|
1525
|
+
plan/agent without retrying the mutation. Confirmed saves require applied status,
|
|
1526
|
+
selected-field readback and exactly the next config version. The current public
|
|
1527
|
+
response omits publication_status: **saved/readback confirmed is reported separately
|
|
1528
|
+
from publication unknown and runtime unverified**, with nonzero exit status for
|
|
1529
|
+
complete success. `publish_failed` is reported separately. Do not replay a saved
|
|
1530
|
+
apply to resolve publication. A fresh invocation refuses an already consumed plan.
|
|
1531
|
+
|
|
1532
|
+
Restore creates a new reviewed plan from only the selected fields' authoritative
|
|
1533
|
+
before-values against the latest config version. Unrelated edits are preserved;
|
|
1534
|
+
a current same-field mismatch blocks restore. There is no force restore. Refresh
|
|
1535
|
+
operator qualification, including deployed all-writer coverage and full-input
|
|
1536
|
+
evidence, for the latest build/version first, then review the new
|
|
1537
|
+
restore receipt with `show` and confirm its new hash with `apply`.
|
|
1538
|
+
|
|
1539
|
+
UI launch validates the existing `test-client-ui/package.json` Vite script,
|
|
1540
|
+
refuses lifecycle hooks, and invokes `npm run dev -- --host 127.0.0.1` by argv
|
|
1541
|
+
with `shell=False`. It installs nothing and launches no backend. The allowlisted
|
|
1542
|
+
child environment includes toolchain essentials plus only these public target
|
|
1543
|
+
variables: `VITE_API_URL`, `VITE_HEALTHCLOUD_ENVIRONMENT`, `VITE_TUNING_TENANT_ID`,
|
|
1544
|
+
`VITE_TUNING_AGENT_ID`. Injected HC/OP credentials and other VITE/NODE/NPM options
|
|
1545
|
+
are excluded. It prints `/app/agents/{agent_id}`; browser login remains separate.
|
|
1546
|
+
The supplied existing worktree and its local frontend configuration remain
|
|
1547
|
+
operator-controlled.
|
|
1548
|
+
|
|
1549
|
+
AMS failures exit nonzero with safe categories, never backend exception bodies.
|
|
1550
|
+
In particular, apply may exit nonzero after a confirmed save because publication
|
|
1551
|
+
is unknown: inspect the emitted outcome before any next action.
|
|
1552
|
+
|
|
1553
|
+
On Windows, `chmod` does not establish file privacy. Create the private-input
|
|
1554
|
+
directory with an owner-only ACL before saving change JSON or receipts there:
|
|
1555
|
+
|
|
1556
|
+
```powershell
|
|
1557
|
+
New-Item -ItemType Directory -Path .\ams-private
|
|
1558
|
+
$amsUser = whoami
|
|
1559
|
+
icacls .\ams-private /inheritance:r /grant:r "${amsUser}:(OI)(CI)F"
|
|
1560
|
+
```
|
|
1561
|
+
|
|
1562
|
+
Native file handling checks the opened handle's owner and DACL, rejects grants to
|
|
1563
|
+
other principals and reparse points, and creates outputs with their restricted
|
|
1564
|
+
DACL already in place. Filesystems without usable ACLs fail closed.
|
|
1565
|
+
|
|
1566
|
+
The `Python CLI` and `Python CLI (Windows)` CI jobs run the non-live suite on
|
|
1567
|
+
Linux and Windows, including native file permissions and apply reconciliation.
|
|
1568
|
+
Both feed `SDK package checks`; a job failure or unexpected job skip blocks that aggregate.
|
|
1569
|
+
Run locally, sequentially, from `packages/cli/pip` with
|
|
1570
|
+
`uv run --frozen --extra dev python -m pytest -q -m 'not live'`.
|
|
1571
|
+
These synthetic checks do not establish live CareCo acceptance. That still needs
|
|
1572
|
+
an explicitly selected and qualified DEV agent, scoped credentials, and the
|
|
1573
|
+
attended before/after/restore procedure above.
|
|
1574
|
+
|
|
1575
|
+
For an optional real-HTTP reproduction with Postman CLI 1.75.0, start
|
|
1576
|
+
`postman simulation run postman/simulations/ams-tuning-failure.sim.yaml --port 4900 --no-history --output ndjson`
|
|
1577
|
+
from the repository root. In another terminal, run the same SDK verification
|
|
1578
|
+
helper used by the required loopback HTTP tests:
|
|
1579
|
+
|
|
1580
|
+
```bash
|
|
1581
|
+
uv run --project packages/cli/pip --frozen --extra dev python -c "import sys; sys.path.insert(0, 'packages/cli/pip/tests'); from test_ams_http import verify_uncertain_apply; print(verify_uncertain_apply('http://127.0.0.1:4900/ams/api/ams'))"
|
|
1582
|
+
```
|
|
1583
|
+
|
|
1584
|
+
The simulation log must show GET plan, GET agent, one POST apply returning 503,
|
|
1585
|
+
then GET plan and GET agent. Restart the stateful simulation before repeating;
|
|
1586
|
+
stop it with Ctrl+C afterward. This is a source-backed synthetic failure fixture,
|
|
1587
|
+
not the deployed service. The required CI tests exercise both pre-commit and
|
|
1588
|
+
post-commit 503 over loopback HTTP without requiring a Postman login or installation.
|
|
1589
|
+
|
|
1590
|
+
|
|
1591
|
+
### Installed CLI integration
|
|
1592
|
+
|
|
1593
|
+
From the repository root, with Python 3.14, uv 0.12.5 and Node.js available:
|
|
1594
|
+
|
|
1595
|
+
```bash
|
|
1596
|
+
python -m unittest discover -s tooling/cli-integration -p 'test_*.py' -v
|
|
1597
|
+
python tooling/cli-integration/run.py --output /tmp/hc-cli-integration
|
|
1598
|
+
```
|
|
1599
|
+
|
|
1600
|
+
The runner installs the SDK, tenant-admin SDK and CLI from the frozen CLI lock
|
|
1601
|
+
with `uv sync --no-editable` into a fresh temporary environment outside the
|
|
1602
|
+
checkout. It verifies installed module ownership, version, help for every AMS
|
|
1603
|
+
command, native shell completion, discovery and the
|
|
1604
|
+
inspect/plan/show/apply/restore journey. Those three local packages keep one
|
|
1605
|
+
version across the stacked release series, so the install passes
|
|
1606
|
+
`--refresh-package` for each of them: without it uv can restore a cached wheel
|
|
1607
|
+
built from an earlier commit and the suite silently exercises old code.
|
|
1608
|
+
|
|
1609
|
+
Negative journeys exercise denied login, identity/qualification/plan rejection,
|
|
1610
|
+
malformed responses, failed readback and uncertain writes without mutation
|
|
1611
|
+
replay.
|
|
1612
|
+
|
|
1613
|
+
The journey suite journals itself as it runs: the output directory holds
|
|
1614
|
+
`uv/journeys.json` (the ordered journey list), `uv/progress.jsonl` (each
|
|
1615
|
+
completed journey, flushed) and `uv/journeys/` (per-journey results and
|
|
1616
|
+
diagnostics). If the suite hits its timeout, the run adds `uv/timeout.json` plus
|
|
1617
|
+
a console summary naming the journeys that were in flight, so the retained
|
|
1618
|
+
evidence is never an empty directory that cannot say where the run stopped.
|
|
1619
|
+
|
|
1620
|
+
The `Installed CLI integration` workflow selects its uv-installed Linux job at
|
|
1621
|
+
job level for `packages/cli/**`, `packages/pip/**`,
|
|
1622
|
+
`packages/pip-tenantadmin/**`, its own tooling/workflow, docs compiler inputs
|
|
1623
|
+
(`tooling/docs-compiler/**`, `docs-src/**`, `docs/sdk/**`), MCP contracts
|
|
1624
|
+
(`contracts/mcp/**`), and root package/lock, Python/lock and Ruff inputs. Backend-only changes skip the install job;
|
|
1625
|
+
the small `CLI integration` result check still runs and verifies applicability.
|
|
1626
|
+
Invalid selection or an applicable failed/cancelled/skipped job fails it.
|
|
1627
|
+
The job retains only sanitized consumer/JUnit results for seven days.
|
|
1628
|
+
|
|
1629
|
+
All scenario records and credentials are synthetic. A test-only Python startup
|
|
1630
|
+
adapter preserves the canonical DEV AMS/tenant-admin origins for product
|
|
1631
|
+
validation while routing only those exact origins over real HTTP to an owned
|
|
1632
|
+
loopback fixture; other origins are refused before network access. Child
|
|
1633
|
+
processes use an isolated home and an allowlisted environment. The adapter is
|
|
1634
|
+
outside all shipped distributions. This boundary does not verify TLS or deployed
|
|
1635
|
+
service conformance. A verified save still exits 1 with `outcome="saved"` and
|
|
1636
|
+
`publication="unknown"`; publication and runtime acceptance remain unverified.
|
|
1637
|
+
|
|
1638
|
+
Required-check activation is tracked separately in
|
|
1639
|
+
[services #724](https://github.com/healthcloudservices/healthcloud-services/issues/724).
|
|
1640
|
+
Require `CLI integration` only after the workflow is merged and both relevant
|
|
1641
|
+
and unrelated-change Actions results and effective settings are verified.
|