@deepdockai/cli 1.0.22 → 1.0.25
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.
- package/CHANGELOG.md +14 -0
- package/README.md +8 -8
- package/dist/deepdock.js +48 -12
- package/package.json +1 -1
- package/skill/references/cli.md +1 -1
- package/skill/references/clients/deepdock.py +12 -6
- package/skill/references/clients/deepdock.ts +27 -10
- package/skill/references/concepts.md +2 -1
- package/skill/references/errors.md +9 -3
- package/skill/references/idempotency.md +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.25
|
|
4
|
+
|
|
5
|
+
- Three new error codes for writes over a customer system's own HTTP interface: `applied_unverified` (409, exit 2: the write landed but its read-back did not finish; `error.created` names the entry as `{ chartId }`, or null), `credentials_rejected` and `certificate_changed` (503, exit 3: a person at the practice renews the login or confirms the certificate in the Deepdock client). An earlier CLI already exits with the same codes through its status fallback.
|
|
6
|
+
- `system_version_unverified` can carry `error.reason: unreported` with `error.version: null`, and a system's `writes` can read `unverified` with reason `unreported`: such a write is verified for one exact release, and the system reported none.
|
|
7
|
+
- `error.uncertain` is documented on `outcome_unknown`. The skill's error reference describes all of it.
|
|
8
|
+
|
|
9
|
+
## 1.0.24
|
|
10
|
+
|
|
11
|
+
- `devices list` and `incidents list` read every page of the API's paged answer (`limit` and `cursor`, following `nextCursor`), so no device or incident past the first 500 or 100 stays invisible. Output is unchanged. They stop after 200 pages with a warning on stderr. This needs the API that pages these lists; an older answer is refused with exit 3.
|
|
12
|
+
|
|
13
|
+
## 1.0.23
|
|
14
|
+
|
|
15
|
+
- An incident's program version is named `programVersion` instead of `version`, in `GET /v1/incidents`, in `incidents list --json` and in the skill's concepts. Read the new name where you read the old one; the value is unchanged.
|
|
16
|
+
|
|
3
17
|
## 1.0.22
|
|
4
18
|
|
|
5
19
|
- `keys create --write-env` writes `DEEPDOCK_BASE_URL=<url>` beside the key when `DEEPDOCK_BASE_URL` points the CLI at another API than production, since a key works only on the API it was created on and the bundled clients default to production. A production key points such a line, left by an earlier key, at production. `meta.written.baseUrl` names the URL when the line was written. Revoking a key removes only the key line.
|
package/README.md
CHANGED
|
@@ -154,14 +154,14 @@ your code will receive; `auth`, `org select` and `keys create` build it from nam
|
|
|
154
154
|
failure is one document on stderr, `{ "error": { "code", "message" }, "meta": { "hint" } }`,
|
|
155
155
|
with the API's error object unchanged. Switch on `code` or on the exit code, never on `message`.
|
|
156
156
|
|
|
157
|
-
| Exit | Meaning
|
|
158
|
-
| ---- |
|
|
159
|
-
| 0 | done
|
|
160
|
-
| 1 | something on the command line to fix (`invalid_usage`, `org_required`)
|
|
161
|
-
| 2 | the API refused the request; do not repeat it unchanged (`invalid_request`, `not_found`, `forbidden`, `rate_limited` and the other refusals)
|
|
162
|
-
| 3 | retry later (`device_offline`, `device_syncing`, `device_outdated`, `system_version_unverified`, `action_paused`, `access_outdated`, `access_missing`, `system_unavailable`, `target_busy`, `temporarily_unavailable`, `internal_error`, `outcome_unknown`, `connection_lost`) |
|
|
163
|
-
| 4 | the CLI is too old for the API (`client_outdated`): run `npx @deepdockai/cli@latest`
|
|
164
|
-
| 5 | sign-in needed (`sign_in_needed`, `approval_denied`, `approval_expired`); `auth status` when nobody is signed in
|
|
157
|
+
| Exit | Meaning |
|
|
158
|
+
| ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
159
|
+
| 0 | done |
|
|
160
|
+
| 1 | something on the command line to fix (`invalid_usage`, `org_required`) |
|
|
161
|
+
| 2 | the API refused the request; do not repeat it unchanged (`invalid_request`, `not_found`, `forbidden`, `rate_limited` and the other refusals) |
|
|
162
|
+
| 3 | retry later (`device_offline`, `device_syncing`, `device_outdated`, `system_version_unverified`, `action_paused`, `access_outdated`, `access_missing`, `credentials_rejected`, `certificate_changed`, `system_unavailable`, `target_busy`, `temporarily_unavailable`, `internal_error`, `outcome_unknown`, `connection_lost`) |
|
|
163
|
+
| 4 | the CLI is too old for the API (`client_outdated`): run `npx @deepdockai/cli@latest` |
|
|
164
|
+
| 5 | sign-in needed (`sign_in_needed`, `approval_denied`, `approval_expired`); `auth status` when nobody is signed in |
|
|
165
165
|
|
|
166
166
|
The CLI never retries on its own: exit 3 says retry, you decide when. `deepdock commands --json`
|
|
167
167
|
prints the whole command tree.
|
package/dist/deepdock.js
CHANGED
|
@@ -10866,6 +10866,7 @@ var EXIT_CODES = {
|
|
|
10866
10866
|
rate_limited: 2,
|
|
10867
10867
|
target_rejected: 2,
|
|
10868
10868
|
readback_failed: 2,
|
|
10869
|
+
applied_unverified: 2,
|
|
10869
10870
|
idempotency_conflict: 2,
|
|
10870
10871
|
conflict: 2,
|
|
10871
10872
|
already_member: 2,
|
|
@@ -10880,6 +10881,8 @@ var EXIT_CODES = {
|
|
|
10880
10881
|
action_paused: 3,
|
|
10881
10882
|
access_outdated: 3,
|
|
10882
10883
|
access_missing: 3,
|
|
10884
|
+
credentials_rejected: 3,
|
|
10885
|
+
certificate_changed: 3,
|
|
10883
10886
|
system_unavailable: 3,
|
|
10884
10887
|
target_busy: 3,
|
|
10885
10888
|
temporarily_unavailable: 3,
|
|
@@ -11429,7 +11432,7 @@ import { spawn } from "node:child_process";
|
|
|
11429
11432
|
import { homedir, hostname } from "node:os";
|
|
11430
11433
|
|
|
11431
11434
|
// src/version.ts
|
|
11432
|
-
var VERSION = "1.0.
|
|
11435
|
+
var VERSION = "1.0.25";
|
|
11433
11436
|
|
|
11434
11437
|
// src/runtime.ts
|
|
11435
11438
|
var DEFAULT_BASE_URL = "https://api.deepdock.ai";
|
|
@@ -12160,6 +12163,7 @@ var EXIT_CODES_HELP = `Exit codes
|
|
|
12160
12163
|
2 the API refused the request; do not repeat it unchanged
|
|
12161
12164
|
3 retry later (device_offline, device_syncing, device_outdated,
|
|
12162
12165
|
system_version_unverified, action_paused, access_outdated, access_missing,
|
|
12166
|
+
credentials_rejected, certificate_changed,
|
|
12163
12167
|
system_unavailable, target_busy, temporarily_unavailable, internal_error,
|
|
12164
12168
|
outcome_unknown, connection_lost)
|
|
12165
12169
|
4 the CLI is too old for the API (client_outdated)
|
|
@@ -12828,6 +12832,40 @@ var registerAuth = (program, run) => {
|
|
|
12828
12832
|
auth.command("logout").description("Revoke the sign-in and the API keys this CLI created").option("--keep-keys", "revoke only the sign-in and leave the API keys active").action((options, cmd) => run(cmd, (ctx) => logout(ctx, options)));
|
|
12829
12833
|
};
|
|
12830
12834
|
|
|
12835
|
+
// src/commands/pages.ts
|
|
12836
|
+
var PAGE_LIMIT = 500;
|
|
12837
|
+
var MAX_PAGES = 200;
|
|
12838
|
+
var unreadable = () => new CliError({
|
|
12839
|
+
code: "connection_lost",
|
|
12840
|
+
message: "The API answered a list in a shape this CLI does not read.",
|
|
12841
|
+
hint: "Update the CLI with `npm install -g @deepdockai/cli@latest`, then retry."
|
|
12842
|
+
});
|
|
12843
|
+
var readAllPages = async (ctx, path, org) => {
|
|
12844
|
+
const items = [];
|
|
12845
|
+
let cursor = null;
|
|
12846
|
+
let requestId;
|
|
12847
|
+
for (let page = 1;page <= MAX_PAGES; page++) {
|
|
12848
|
+
const query = new URLSearchParams({ limit: String(PAGE_LIMIT) });
|
|
12849
|
+
if (cursor !== null)
|
|
12850
|
+
query.set("cursor", cursor);
|
|
12851
|
+
const url = `${path}?${query.toString()}`;
|
|
12852
|
+
const response = org ? await ctx.call({ method: "GET", path: url, org }) : await ctx.callInOrg({ method: "GET", path: url });
|
|
12853
|
+
const data = response.data;
|
|
12854
|
+
if (!isRecord(data) || !Array.isArray(data.items))
|
|
12855
|
+
throw unreadable();
|
|
12856
|
+
const next = data.nextCursor;
|
|
12857
|
+
if (next !== null && (typeof next !== "string" || next === ""))
|
|
12858
|
+
throw unreadable();
|
|
12859
|
+
items.push(...data.items);
|
|
12860
|
+
requestId ??= response.requestId;
|
|
12861
|
+
if (next === null)
|
|
12862
|
+
return { items, requestId };
|
|
12863
|
+
cursor = next;
|
|
12864
|
+
}
|
|
12865
|
+
ctx.note(`Warning: stopped after ${MAX_PAGES} pages; the list is longer than the ${items.length} rows shown.`);
|
|
12866
|
+
return { items, requestId };
|
|
12867
|
+
};
|
|
12868
|
+
|
|
12831
12869
|
// src/commands/devices.ts
|
|
12832
12870
|
var meta = (requestId) => requestId ? { requestId } : {};
|
|
12833
12871
|
var updateNotes = (ctx, devices) => {
|
|
@@ -12840,19 +12878,18 @@ var updateNotes = (ctx, devices) => {
|
|
|
12840
12878
|
}
|
|
12841
12879
|
};
|
|
12842
12880
|
var list = async (ctx) => {
|
|
12843
|
-
const
|
|
12844
|
-
const devices = Array.isArray(response.data) ? response.data : [];
|
|
12881
|
+
const { items: devices, requestId } = await readAllPages(ctx, "/partner/devices");
|
|
12845
12882
|
if (devices.length === 0) {
|
|
12846
12883
|
return {
|
|
12847
|
-
data:
|
|
12848
|
-
meta: meta(
|
|
12884
|
+
data: devices,
|
|
12885
|
+
meta: meta(requestId),
|
|
12849
12886
|
text: "No devices are paired. Pair one with `deepdock devices pair <code>`."
|
|
12850
12887
|
};
|
|
12851
12888
|
}
|
|
12852
12889
|
updateNotes(ctx, devices);
|
|
12853
12890
|
return {
|
|
12854
|
-
data:
|
|
12855
|
-
meta: meta(
|
|
12891
|
+
data: devices,
|
|
12892
|
+
meta: meta(requestId),
|
|
12856
12893
|
text: deviceTable(devices, ctx.rt.now(), ctx.out)
|
|
12857
12894
|
};
|
|
12858
12895
|
};
|
|
@@ -12906,8 +12943,8 @@ var release = async (ctx, id) => {
|
|
|
12906
12943
|
if (!ctx.opts.yes) {
|
|
12907
12944
|
let label = "";
|
|
12908
12945
|
try {
|
|
12909
|
-
const
|
|
12910
|
-
const found =
|
|
12946
|
+
const { items } = await readAllPages(ctx, "/partner/devices", org.slug);
|
|
12947
|
+
const found = items.filter(isRecord).find((device) => field(device, "id") === deviceId);
|
|
12911
12948
|
label = found ? field(found, "label") : "";
|
|
12912
12949
|
} catch {}
|
|
12913
12950
|
const named = label ? ` (${stripControl(label)})` : "";
|
|
@@ -12966,10 +13003,9 @@ var registerDevices = (program, run) => {
|
|
|
12966
13003
|
var registerIncidents = (program, run) => {
|
|
12967
13004
|
const incidents = program.command("incidents").description("Incidents that paused an action on one of your devices");
|
|
12968
13005
|
incidents.command("list").description("List open incidents and those closed within 30 days").action((_options, cmd) => run(cmd, async (ctx) => {
|
|
12969
|
-
const
|
|
12970
|
-
const items = Array.isArray(response.data) ? response.data : [];
|
|
13006
|
+
const { items, requestId } = await readAllPages(ctx, "/partner/incidents");
|
|
12971
13007
|
const text = items.length === 0 ? "No incidents." : genericTable(items, ctx.out, INCIDENT_COLUMNS);
|
|
12972
|
-
return { data:
|
|
13008
|
+
return { data: items, meta: { requestId }, text };
|
|
12973
13009
|
}));
|
|
12974
13010
|
};
|
|
12975
13011
|
|
package/package.json
CHANGED
package/skill/references/cli.md
CHANGED
|
@@ -84,7 +84,7 @@ A failure is one document on stderr: the API's error object unchanged plus what
|
|
|
84
84
|
| 0 | done | |
|
|
85
85
|
| 1 | something on the command line to fix | `invalid_usage` (a bad flag, an unknown slug, a question with no terminal and no `--yes`, an env file git does not ignore), `org_required` |
|
|
86
86
|
| 2 | the API refused the request; do not repeat it unchanged | `invalid_request`, `not_found`, `device_not_found`, `pairing_code_invalid`, `other_partner_build`, `rate_limited`, `forbidden`, `conflict` and the other refusals (not `access_outdated` and `access_missing`, see exit 3) |
|
|
87
|
-
| 3 | retry later (`access_outdated` and `access_missing`: after the customer repeats "Erneut einrichten" in the Deepdock client) | `device_offline`, `device_syncing`, `device_outdated`, `system_version_unverified`, `action_paused`, `access_outdated`, `access_missing`, `system_unavailable`, `target_busy`, `temporarily_unavailable`, `internal_error`, `outcome_unknown`, `connection_lost` |
|
|
87
|
+
| 3 | retry later (`access_outdated` and `access_missing`: after the customer repeats "Erneut einrichten" in the Deepdock client) | `device_offline`, `device_syncing`, `device_outdated`, `system_version_unverified`, `action_paused`, `access_outdated`, `access_missing`, `credentials_rejected`, `certificate_changed`, `system_unavailable`, `target_busy`, `temporarily_unavailable`, `internal_error`, `outcome_unknown`, `connection_lost` |
|
|
88
88
|
| 4 | the CLI is too old for the API: run `npx @deepdockai/cli@latest` | `client_outdated` |
|
|
89
89
|
| 5 | sign-in needed: run `deepdock auth login` | `sign_in_needed`, `approval_denied`, `approval_expired` |
|
|
90
90
|
|
|
@@ -20,14 +20,17 @@ class DeepdockError(Exception):
|
|
|
20
20
|
|
|
21
21
|
def __init__(self, status: int, code: str, message: str, request_id: Optional[str] = None,
|
|
22
22
|
systems: Optional[List[str]] = None, version: Optional[str] = None,
|
|
23
|
-
reason: Optional[str] = None
|
|
23
|
+
reason: Optional[str] = None, created: Optional[Dict[str, Any]] = None,
|
|
24
|
+
uncertain: Optional[bool] = None):
|
|
24
25
|
super().__init__(message)
|
|
25
26
|
self.status = status
|
|
26
27
|
self.code = code
|
|
27
28
|
self.request_id = request_id
|
|
28
29
|
self.systems = systems
|
|
29
|
-
self.version = version # system_version_unverified: the version the system last reported
|
|
30
|
-
self.reason = reason # system_version_unverified: unverified_version or
|
|
30
|
+
self.version = version # system_version_unverified: the version the system last reported, None when it reported none
|
|
31
|
+
self.reason = reason # system_version_unverified: unverified_version, unparseable or unreported
|
|
32
|
+
self.created = created # applied_unverified, outcome_unknown with no rollback: {"chartId"} or None
|
|
33
|
+
self.uncertain = uncertain # outcome_unknown: True when the write may or may not have landed
|
|
31
34
|
|
|
32
35
|
|
|
33
36
|
class DeepdockTransportError(Exception):
|
|
@@ -67,7 +70,7 @@ class Deepdock:
|
|
|
67
70
|
if isinstance(body, dict) and "error" in body:
|
|
68
71
|
e = body["error"]
|
|
69
72
|
raise DeepdockError(r.status_code, e["code"], e["message"], e.get("requestId"), e.get("systems"),
|
|
70
|
-
e.get("version"), e.get("reason"))
|
|
73
|
+
e.get("version"), e.get("reason"), e.get("created"), e.get("uncertain"))
|
|
71
74
|
if not isinstance(body, dict) or "data" not in body:
|
|
72
75
|
raise DeepdockTransportError(f"unexpected body with status {r.status_code}")
|
|
73
76
|
return body["data"], r.headers
|
|
@@ -166,8 +169,11 @@ def attempt(dd: Deepdock, row: PendingWrite) -> PendingWrite:
|
|
|
166
169
|
if row.conflict_retries < CONFLICT_RETRIES:
|
|
167
170
|
return replace(row, status="sent", conflict_retries=row.conflict_retries + 1)
|
|
168
171
|
return replace(row, status="unknown")
|
|
169
|
-
|
|
170
|
-
|
|
172
|
+
# applied_unverified: the entry exists but was not verified; never write it again under a new
|
|
173
|
+
# key. result["created"] names it for the check.
|
|
174
|
+
if e.code in ("outcome_unknown", "applied_unverified"):
|
|
175
|
+
return replace(row, status="unknown", request_id=e.request_id,
|
|
176
|
+
result={"created": e.created, "uncertain": e.uncertain})
|
|
171
177
|
# invalid_input, target_rejected, action-specific 422s, not_found, readback_failed, auth and
|
|
172
178
|
# resolution codes: nothing was written. Fix the cause; a changed input gets a new key.
|
|
173
179
|
return replace(row, status="refused", refused_code=e.code, result={"message": str(e)})
|
|
@@ -13,15 +13,21 @@ export class DeepdockError extends Error {
|
|
|
13
13
|
readonly code: string
|
|
14
14
|
readonly requestId?: string
|
|
15
15
|
readonly systems?: string[]
|
|
16
|
-
/** system_version_unverified: the version the system last reported. */
|
|
17
|
-
readonly version?: string
|
|
18
|
-
/** system_version_unverified: `unverified_version` or `
|
|
16
|
+
/** system_version_unverified: the version the system last reported, `null` when it reported none. */
|
|
17
|
+
readonly version?: string | null
|
|
18
|
+
/** system_version_unverified: `unverified_version`, `unparseable` or `unreported`. */
|
|
19
19
|
readonly reason?: string
|
|
20
|
+
/** applied_unverified, outcome_unknown on a system with no rollback: the entry, or `null` when not known. */
|
|
21
|
+
readonly created?: { chartId?: string } | null
|
|
22
|
+
/** outcome_unknown: `true` when the write may or may not have landed. */
|
|
23
|
+
readonly uncertain?: boolean
|
|
20
24
|
|
|
21
25
|
constructor(status: number, code: string, message: string, requestId?: string,
|
|
22
26
|
systems?: string[],
|
|
23
|
-
version?: string,
|
|
27
|
+
version?: string | null,
|
|
24
28
|
reason?: string,
|
|
29
|
+
created?: { chartId?: string } | null,
|
|
30
|
+
uncertain?: boolean,
|
|
25
31
|
) {
|
|
26
32
|
super(message)
|
|
27
33
|
this.name = 'DeepdockError'
|
|
@@ -31,6 +37,8 @@ export class DeepdockError extends Error {
|
|
|
31
37
|
this.systems = systems
|
|
32
38
|
this.version = version
|
|
33
39
|
this.reason = reason
|
|
40
|
+
this.created = created
|
|
41
|
+
this.uncertain = uncertain
|
|
34
42
|
}
|
|
35
43
|
}
|
|
36
44
|
|
|
@@ -42,7 +50,7 @@ export class DeepdockTransportError extends Error {
|
|
|
42
50
|
}
|
|
43
51
|
}
|
|
44
52
|
|
|
45
|
-
type ErrorBody = { code: string; message: string; requestId?: string; systems?: string[]; version?: string; reason?: string }
|
|
53
|
+
type ErrorBody = { code: string; message: string; requestId?: string; systems?: string[]; version?: string | null; reason?: string; created?: { chartId?: string } | null; uncertain?: boolean }
|
|
46
54
|
type Envelope<T> = { data: T } | { error: ErrorBody }
|
|
47
55
|
|
|
48
56
|
export type Page<Item> = { items: Item[]; nextCursor: string | null }
|
|
@@ -56,11 +64,12 @@ export type SystemAccess = 'ok' | 'outdated' | 'missing' | 'password_missing' |
|
|
|
56
64
|
/**
|
|
57
65
|
* Whether writes on the system run. `unverified` makes writes answer system_version_unverified:
|
|
58
66
|
* `unverified_version` clears once Deepdock verifies the version; `unparseable` does not clear by
|
|
59
|
-
* waiting (contact Deepdock)
|
|
67
|
+
* waiting (contact Deepdock); `unreported` clears once the device reports the system's version (a
|
|
68
|
+
* write verified for one exact release needs it).
|
|
60
69
|
*/
|
|
61
70
|
export type SystemWrites =
|
|
62
71
|
| { state: 'verified' | 'unknown' | 'partial' }
|
|
63
|
-
| { state: 'unverified'; reason: 'unverified_version' | 'unparseable' }
|
|
72
|
+
| { state: 'unverified'; reason: 'unverified_version' | 'unparseable' | 'unreported' }
|
|
64
73
|
|
|
65
74
|
export type Device = {
|
|
66
75
|
id: string
|
|
@@ -165,8 +174,8 @@ export class Deepdock {
|
|
|
165
174
|
throw new DeepdockTransportError(`non-JSON answer with status ${res.status}`)
|
|
166
175
|
}
|
|
167
176
|
if (body !== null && typeof body === 'object' && 'error' in body) {
|
|
168
|
-
const { code, message, requestId, systems, version, reason } = body.error
|
|
169
|
-
throw new DeepdockError(res.status, code, message, requestId, systems, version, reason)
|
|
177
|
+
const { code, message, requestId, systems, version, reason, created, uncertain } = body.error
|
|
178
|
+
throw new DeepdockError(res.status, code, message, requestId, systems, version, reason, created, uncertain)
|
|
170
179
|
}
|
|
171
180
|
if (body === null || typeof body !== 'object' || !('data' in body)) {
|
|
172
181
|
throw new DeepdockTransportError(`unexpected body with status ${res.status}`)
|
|
@@ -305,7 +314,15 @@ export const attempt = async (dd: Deepdock, row: PendingWrite): Promise<PendingW
|
|
|
305
314
|
? { ...row, status: 'sent', conflictRetries: row.conflictRetries + 1 }
|
|
306
315
|
: { ...row, status: 'unknown' }
|
|
307
316
|
}
|
|
308
|
-
|
|
317
|
+
// applied_unverified: the entry exists but was not verified; never write it again under a new
|
|
318
|
+
// key. `result.created` names it for the check.
|
|
319
|
+
if (err.code === 'outcome_unknown' || err.code === 'applied_unverified')
|
|
320
|
+
return {
|
|
321
|
+
...row,
|
|
322
|
+
status: 'unknown',
|
|
323
|
+
requestId: err.requestId,
|
|
324
|
+
result: { created: err.created, uncertain: err.uncertain },
|
|
325
|
+
}
|
|
309
326
|
// invalid_input, target_rejected, action-specific 422s, not_found, readback_failed, auth and
|
|
310
327
|
// resolution codes: nothing was written. Fix the cause; a changed input gets a new key.
|
|
311
328
|
return { ...row, status: 'refused', refusedCode: err.code, result: { message: err.message } }
|
|
@@ -95,7 +95,8 @@ Each pause opens an **incident** (`GET /v1/incidents`). Only Deepdock lifts a pa
|
|
|
95
95
|
customer's site see that an action is paused but cannot resume it; there is no partner call to
|
|
96
96
|
resume. A stopped or missing system needs no resume: its incident closes once the system is back.
|
|
97
97
|
|
|
98
|
-
An incident has `id`, `system`, `
|
|
98
|
+
An incident has `id`, `system`, `programVersion` (the version of the vendor's program its devices
|
|
99
|
+
run, `null` when they reported none), `action` (`null` for a system-level incident), `reason`,
|
|
99
100
|
`openedAt`, `closedAt`, and `devices` with `deviceId`, `pausedAt`, `resumedAt` and `inactiveAt`. It
|
|
100
101
|
closes once none of its devices is still paused. `GET /v1/incidents` lists incidents that are open
|
|
101
102
|
or closed within the last 30 days, newest first, at most 100.
|
|
@@ -62,10 +62,12 @@ dashboard-only.
|
|
|
62
62
|
| `device_offline` | 503 | the device is not connected, is shutting down, did not answer within 20 s, or the call was cut off inside Deepdock by a restart or deploy. **A write may still have landed** | yes, with backoff (seconds to minutes); a write only with the same key | "the customer PC is offline; we will retry" |
|
|
63
63
|
| `device_syncing` | 503 | the device has not reported its Deepdock client yet (just paired, or its report did not come through); Deepdock asks it again. Nothing was written | yes, after `Retry-After` (5 s); a write with the same key | "the customer PC is connecting; retrying" |
|
|
64
64
|
| `device_outdated` | 409 | the device's Deepdock client is too old for this call. It updates itself when a newer version is released for your organisation, once nobody is signed in on the PC. Nothing was written | after the update (`outdated` on `GET /v1/devices/{deviceId}` turns `false`); retrying before it gets the same answer. A write keeps its key | "the Deepdock client on the customer PC needs an update" |
|
|
65
|
-
| `system_version_unverified` | 409 | a write on a system whose vendor version Deepdock has not verified yet. Nothing was sent to the device; reads still work. `error.version` is the version the system last reported and `error.reason` is `unverified_version`,
|
|
65
|
+
| `system_version_unverified` | 409 | a write on a system whose vendor version Deepdock has not verified yet. Nothing was sent to the device; reads still work. `error.version` is the version the system last reported (null when it reported none) and `error.reason` is `unverified_version`, `unparseable` when that version is not dotted digits, or `unreported` when the system reported no version: a write over the system's own HTTP interface is verified for one exact release and needs it | `unverified_version`: after Deepdock has verified that version (`writes` on the system in `GET /v1/devices/{deviceId}` turns `verified`); retrying before it gets the same answer. `unparseable`: retrying does not help, contact Deepdock; only a different reported version changes it. `unreported`: later, once the device reports the system's version. A write keeps its key | "the customer's system was updated to a version Deepdock has not yet verified; changes will work once it has" |
|
|
66
66
|
| `action_paused` | 503 | the action is paused on the device (`GET .../systems` shows `reason`); only Deepdock resumes it | later, when `GET .../systems` shows it active; do not loop | "this feature is paused while Deepdock checks the customer system" |
|
|
67
67
|
| `system_unavailable` | 503 | the customer's program is stopped or missing on the device | later; often resolves when the customer restarts the program | "the customer's software is not running" |
|
|
68
68
|
| `target_busy` | 503 | the customer's system is busy or unreachable | yes, with backoff | "the customer's system is busy; retrying" |
|
|
69
|
+
| `credentials_rejected` | 503 | the customer's system refused the device's login. Nothing was sent | later, after a person at the customer has renewed the access in the Deepdock client; do not loop. A write keeps its key | "the connection to the customer's software needs to be renewed at the practice" |
|
|
70
|
+
| `certificate_changed` | 503 | the customer's system shows a certificate other than the one the device trusts. Nothing was sent | later, after a person at the customer has confirmed the new certificate in the Deepdock client; do not loop. A write keeps its key | "the connection to the customer's software needs to be confirmed at the practice" |
|
|
69
71
|
| `temporarily_unavailable` | 503 | Deepdock could not check the key or reach its own database (sent with `Retry-After: 5`) | yes, with backoff | "temporary problem; retrying" |
|
|
70
72
|
|
|
71
73
|
## Write outcomes
|
|
@@ -73,8 +75,9 @@ dashboard-only.
|
|
|
73
75
|
| Code | Status | Meaning | Retry | Show |
|
|
74
76
|
|---|---|---|---|---|
|
|
75
77
|
| `readback_failed` | 409 | the write's result did not match what the action expects, so nothing was saved; **the action is now paused** and an incident is open | no; the next calls answer `action_paused` | "the change was not saved; Deepdock is checking the customer system" |
|
|
78
|
+
| `applied_unverified` | 409 | a write on a system with no rollback was applied, but its read-back did not finish. The entry exists: `error.created` names it as `{ chartId }`, or is null when the entry is not known | **no.** The entry is there; writing again creates a second one. Check it in the customer system | "the change was saved but could not be confirmed; please check it in the customer system" |
|
|
76
79
|
| `idempotency_conflict` | 409 | the key was used with a different input, its write is still running, or its outcome is unknown. One code, three causes; `message` must not be parsed to tell them apart | retry with the **same key and the same input** a bounded number of times with backoff (a running write finishes within seconds); if it still conflicts, handle it like `outcome_unknown`: read back, then decide | "a previous attempt is still in progress" |
|
|
77
|
-
| `outcome_unknown` | 500 | the system did not confirm the write; it may or may not have landed. The key stays blocked | **no.** Read the record back (a search) to see whether it landed before writing again under a new key; involve a human when the read cannot tell | "we could not confirm the change; please check in the customer system before trying again" |
|
|
80
|
+
| `outcome_unknown` | 500 | the system did not confirm the write; it may or may not have landed. The key stays blocked. On a system with no rollback, `error.uncertain` is true and `error.created` names the entry when one is known | **no.** Read the record back (a search) to see whether it landed before writing again under a new key; involve a human when the read cannot tell | "we could not confirm the change; please check in the customer system before trying again" |
|
|
78
81
|
| `internal_error` | 500 | something failed inside Deepdock; `error.requestId` is set | once, later; a write with the same key | "something went wrong"; log `requestId` for Deepdock support |
|
|
79
82
|
|
|
80
83
|
## A reaction table for code
|
|
@@ -104,6 +107,8 @@ const REACTION: Record<string, Reaction> = {
|
|
|
104
107
|
payload_too_large: 'fix-code',
|
|
105
108
|
access_outdated: 'wait-setup',
|
|
106
109
|
access_missing: 'wait-setup',
|
|
110
|
+
credentials_rejected: 'wait-setup',
|
|
111
|
+
certificate_changed: 'wait-setup',
|
|
107
112
|
device_offline: 'retry-same-key',
|
|
108
113
|
device_syncing: 'retry-same-key',
|
|
109
114
|
device_outdated: 'retry-later',
|
|
@@ -113,6 +118,7 @@ const REACTION: Record<string, Reaction> = {
|
|
|
113
118
|
target_busy: 'retry-same-key',
|
|
114
119
|
temporarily_unavailable: 'retry-same-key',
|
|
115
120
|
readback_failed: 'wait-paused',
|
|
121
|
+
applied_unverified: 'check-then-decide', // the entry exists; reading it back shows it
|
|
116
122
|
idempotency_conflict: 'retry-same-key', // bounded; then 'check-then-decide'
|
|
117
123
|
outcome_unknown: 'check-then-decide',
|
|
118
124
|
internal_error: 'retry-same-key',
|
|
@@ -120,7 +126,7 @@ const REACTION: Record<string, Reaction> = {
|
|
|
120
126
|
const reactionFor = (code: string): Reaction => REACTION[code] ?? 'stop'
|
|
121
127
|
```
|
|
122
128
|
|
|
123
|
-
Action-specific 422 codes beyond the ones above (any the OpenAPI description lists) map to `fix-input`. Add them from the document, not from memory. `wait-setup` means a person at the customer must repeat the database access setup ("Erneut einrichten")
|
|
129
|
+
Action-specific 422 codes beyond the ones above (any the OpenAPI description lists) map to `fix-input`. Add them from the document, not from memory. `wait-setup` means a person at the customer must act in the Deepdock client first: repeat the database access setup ("Erneut einrichten") for `access_*`, renew the login for `credentials_rejected`, confirm the new certificate for `certificate_changed`; there is no `Retry-After`, so do not loop. Check `access` on the system in `GET .../systems` (`ok` again), then retry; a write keeps its key. `retry-same-key` means the
|
|
124
130
|
pending write stays persisted and is retried from that state with backoff, also across process
|
|
125
131
|
restarts; a device can be offline for hours, so the retry loop lives in a job or queue, not in the
|
|
126
132
|
request that started the write.
|
|
@@ -74,6 +74,7 @@ pending ──send──▶ sent ──2xx (fresh or replayed)──────
|
|
|
74
74
|
│ ──idempotency_conflict, retries left ───▶ sent (retry, same key, same input)
|
|
75
75
|
│ ──idempotency_conflict, retries spent ──▶ unknown (read back before anything else)
|
|
76
76
|
│ ──outcome_unknown ──────────────────────▶ unknown (read back before anything else)
|
|
77
|
+
│ ──applied_unverified ───────────────────▶ unknown (the entry exists: check it by result.created, never write it again)
|
|
77
78
|
│ ──invalid_input, target_rejected, action-specific 422, not_found, readback_failed ▶ refused (key is free; new input gets a new key)
|
|
78
79
|
│ ──action_paused, system_unavailable ────▶ sent (retry once the action is active again)
|
|
79
80
|
```
|