@calltelemetry/ct-lab-mcp 0.8.90 → 0.8.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +4 -4
  2. package/dist/src/tools/appliance-claim-tools.d.ts.map +1 -1
  3. package/dist/src/tools/appliance-claim-tools.js +12 -10
  4. package/dist/src/tools/appliance-claim-tools.js.map +1 -1
  5. package/dist/src/tools/claim-creation-projection.js +2 -2
  6. package/dist/src/tools/claim-creation-projection.js.map +1 -1
  7. package/dist/src/tools/claim-custody.d.ts +175 -0
  8. package/dist/src/tools/claim-custody.d.ts.map +1 -0
  9. package/dist/src/tools/claim-custody.js +196 -0
  10. package/dist/src/tools/claim-custody.js.map +1 -0
  11. package/dist/src/tools/claim-kubernetes.d.ts +14 -0
  12. package/dist/src/tools/claim-kubernetes.d.ts.map +1 -0
  13. package/dist/src/tools/{slot-kubernetes.js → claim-kubernetes.js} +30 -26
  14. package/dist/src/tools/claim-kubernetes.js.map +1 -0
  15. package/dist/src/tools/claim-reconcile-gate.d.ts +56 -0
  16. package/dist/src/tools/claim-reconcile-gate.d.ts.map +1 -0
  17. package/dist/src/tools/claim-reconcile-gate.js +110 -0
  18. package/dist/src/tools/claim-reconcile-gate.js.map +1 -0
  19. package/dist/src/tools/claimed-import-qcow.d.ts +3 -0
  20. package/dist/src/tools/claimed-import-qcow.d.ts.map +1 -1
  21. package/dist/src/tools/claimed-import-qcow.js +17 -0
  22. package/dist/src/tools/claimed-import-qcow.js.map +1 -1
  23. package/dist/src/tools/index.js +1 -1
  24. package/dist/src/tools/index.js.map +1 -1
  25. package/dist/src/tools/kubernetes-backend.js +1 -1
  26. package/dist/src/tools/kubernetes-backend.js.map +1 -1
  27. package/dist/src/tools/{physical-slot.d.ts → legacy-reservation.d.ts} +79 -40
  28. package/dist/src/tools/legacy-reservation.d.ts.map +1 -0
  29. package/dist/src/tools/legacy-reservation.js +111 -0
  30. package/dist/src/tools/legacy-reservation.js.map +1 -0
  31. package/dist/src/tools/renewal-claim.d.ts +2 -1
  32. package/dist/src/tools/renewal-claim.d.ts.map +1 -1
  33. package/dist/src/tools/renewal-claim.js +19 -8
  34. package/dist/src/tools/renewal-claim.js.map +1 -1
  35. package/package.json +2 -2
  36. package/dist/src/tools/physical-slot.d.ts.map +0 -1
  37. package/dist/src/tools/physical-slot.js +0 -319
  38. package/dist/src/tools/physical-slot.js.map +0 -1
  39. package/dist/src/tools/slot-kubernetes.d.ts +0 -14
  40. package/dist/src/tools/slot-kubernetes.d.ts.map +0 -1
  41. package/dist/src/tools/slot-kubernetes.js.map +0 -1
  42. /package/dist/src/tools/{physical-slot-claim-spec-schema.v1.json → appliance-claim-spec-schema.v1.json} +0 -0
@@ -0,0 +1,196 @@
1
+ /**
2
+ * ApplianceClaim custody (REL-1746, supersedes physical-slot reservation Leases).
3
+ *
4
+ * The original-owner record lives ON the claim as annotation `calltelemetry.com/claim-custody`
5
+ * (canonical JSON, <= 64 KiB, strict parse), written in the SAME create call as the claim. No Lease is
6
+ * ever created, renewed or required: per-guest serialization is in-process keyed by (node, vmid) and
7
+ * cross-actor safety is the claim UID/resourceVersion plus the claim-occupancy scan. Deletion only
8
+ * requests claim deletion with UID + resourceVersion preconditions; the operator owns teardown.
9
+ * Claims that still hold a legacy reservation Lease are verified READ-ONLY (the operator migrates them).
10
+ */
11
+ import { randomUUID } from "node:crypto";
12
+ import { z } from "zod";
13
+ import { actualClaimIssue, claimCreationSha256, effectiveClaimSpec } from "./claim-creation-projection.js";
14
+ import { createClaimApi } from "./claim-kubernetes.js";
15
+ import { ContextSchema, GuestSchema, ProviderSchema, annotationJson, canonicalJson, claimGuest, digest, objectName, requireCustody, readLegacyReservation, sha, uuid, verifyLegacyClaim, legacyReservationLeaseName, } from "./legacy-reservation.js";
16
+ export { canonicalJson, claimGuest, objectName };
17
+ export const CUSTODY_ANNOTATION = "calltelemetry.com/claim-custody";
18
+ const CLAIMS = "/apis/calltelemetry.com/v1alpha1/namespaces/ct-lab/applianceclaims";
19
+ export const CustodySchema = z.object({
20
+ schema: z.literal("claim-custody/v1"), guest: GuestSchema,
21
+ phase: z.enum(["bound", "provider-bound", "retiring"]),
22
+ operationNonce: uuid, requestSha256: sha, context: ContextSchema,
23
+ provider: ProviderSchema.nullable(),
24
+ }).strict();
25
+ /** Strict custody parse: closed field set, duplicate keys rejected, provider/phase coherence, sorted volumes. */
26
+ export function parseCustody(text) {
27
+ const record = CustodySchema.parse(annotationJson(text));
28
+ if (record.provider)
29
+ requireCustody(canonicalJson(record.provider.volumes) === canonicalJson([...new Set(record.provider.volumes)].sort()), "provider volumes are not sorted and unique");
30
+ requireCustody(record.phase !== "provider-bound" || record.provider !== null, "provider phase lacks immutable provider binding");
31
+ requireCustody(record.phase !== "bound" || record.provider === null, "bound phase carries a provider binding");
32
+ return record;
33
+ }
34
+ /** The custody record of a claim, or null when the annotation is absent. Malformed records throw. */
35
+ export function claimCustody(claim) {
36
+ const text = claim?.metadata?.annotations?.[CUSTODY_ANNOTATION];
37
+ return text === undefined ? null : parseCustody(text);
38
+ }
39
+ export function creationOwnerSha(record) {
40
+ const { schema, guest, operationNonce, requestSha256, context } = CustodySchema.parse(record);
41
+ return digest({ schema, guest, operationNonce, requestSha256, context });
42
+ }
43
+ /** Verifies a live claim against its own custody record. */
44
+ export function verifyCustodyClaim(live, record, expectedUid) {
45
+ requireCustody(live?.apiVersion === "calltelemetry.com/v1alpha1" && live.kind === "ApplianceClaim"
46
+ && live.metadata?.namespace === "ct-lab" && objectName.safeParse(live.metadata.name).success
47
+ && uuid.safeParse(live.metadata.uid).success && (!expectedUid || live.metadata.uid === expectedUid)
48
+ && typeof live.metadata.resourceVersion === "string" && live.metadata.resourceVersion.length > 0
49
+ && canonicalJson(claimGuest(live)) === canonicalJson(record.guest)
50
+ && (actualClaimIssue(live) ?? null) === (record.context.issueKey ?? null)
51
+ && claimCreationSha256(live) === record.requestSha256, "claim UID, guest tuple or source lineage differs");
52
+ }
53
+ /**
54
+ * A claim with neither a custody annotation nor a legacy reservation Lease was created outside the guarded
55
+ * path: there is no original-owner record to retire it against. Distinct from a record that exists but
56
+ * fails identity checks.
57
+ */
58
+ export class NoClaimCustodyError extends Error {
59
+ claimName;
60
+ guest;
61
+ constructor(claimName, guest) {
62
+ super(`ApplianceClaim '${claimName}' has no claim-custody annotation (and no legacy reservation Lease) for ${guest.node}/${guest.vmid}: it was created outside the guarded create path, so the guard has no original-owner record to retire it against; nothing was modified`);
63
+ this.claimName = claimName;
64
+ this.guest = guest;
65
+ this.name = "NoClaimCustodyError";
66
+ }
67
+ }
68
+ const defaults = () => ({ api: createClaimApi(), now: Date.now, nonce: randomUUID });
69
+ const tails = new Map();
70
+ /** In-process serialization keyed by (node, vmid). Single MCP replica; cross-actor safety is claim UID/resourceVersion. */
71
+ function serialized(guest, action) {
72
+ const key = `${guest.node}/${guest.vmid}`;
73
+ const run = (tails.get(key) ?? Promise.resolve()).catch(() => { }).then(action);
74
+ const tail = run.catch(() => { });
75
+ tails.set(key, tail);
76
+ void tail.then(() => { if (tails.get(key) === tail)
77
+ tails.delete(key); });
78
+ return run;
79
+ }
80
+ const nodeAlias = (node) => node === "proxmox-lab" ? "proxmox" : ["proxmox-mini", "proxmox-minis-lab"].includes(node) ? "minis" : node === "proxmox-ibm64" ? "ibm64" : node;
81
+ async function noOtherClaim(deps, guest, ownUid) {
82
+ let next = "";
83
+ for (let page = 0; page < 10; page++) {
84
+ const result = await deps.api.request(`${CLAIMS}?limit=500${next ? `&continue=${encodeURIComponent(next)}` : ""}`, "GET");
85
+ requireCustody(result?.kind === "ApplianceClaimList" && result.apiVersion === "calltelemetry.com/v1alpha1" && Array.isArray(result.items), "claim occupancy list unavailable");
86
+ for (const claim of result.items) {
87
+ if (claim.metadata?.uid === ownUid)
88
+ continue;
89
+ const alias = nodeAlias(claim.spec?.node);
90
+ if (String(claim.spec?.vmid) === String(guest.vmid) && (alias === guest.node || !["minis", "proxmox", "ibm64", "esxi"].includes(alias)))
91
+ throw new Error("claim custody guest has another existing ApplianceClaim; custody unchanged");
92
+ }
93
+ next = result.metadata?.continue || "";
94
+ requireCustody(typeof next === "string", "claim occupancy pagination malformed");
95
+ if (!next)
96
+ return;
97
+ }
98
+ throw new Error("claim custody occupancy scan incomplete; custody unchanged");
99
+ }
100
+ function requireResumable(live, manifest, deps) {
101
+ requireCustody(!live.metadata.deletionTimestamp && live.spec?.closed !== true && live.spec?.state !== "Closed" && live.status?.phase !== "Closed", "bound claim is retiring");
102
+ requireCustody(Number.isInteger(live.spec?.ttlHours) && live.spec.ttlHours >= manifest.spec.ttlHours && live.spec.ttlHours <= 8760 && live.spec.claimType !== "fixture", "bound claim finite TTL differs");
103
+ if (live.status?.phase === "Ready" || live.status?.completionTime) {
104
+ const baseline = Date.parse(live.status?.completionTime || "");
105
+ requireCustody(Number.isFinite(baseline) && baseline + live.spec.ttlHours * 3_600_000 > deps.now(), "bound claim expired or Ready TTL baseline unavailable");
106
+ }
107
+ }
108
+ export async function createClaimWithCustody(manifest, deps = defaults()) {
109
+ requireCustody(manifest.metadata?.namespace === "ct-lab", "namespace must be ct-lab");
110
+ objectName.parse(manifest.metadata.name);
111
+ manifest = { ...manifest, spec: effectiveClaimSpec(manifest.spec) };
112
+ const guest = claimGuest(manifest);
113
+ requireCustody(typeof manifest.spec.vmName === "string" && manifest.spec.vmName.length > 0, "constructor must provide the actual VM name before claim POST");
114
+ requireCustody(Number.isInteger(manifest.spec?.ttlHours) && manifest.spec.ttlHours > 0 && manifest.spec.ttlHours <= 8760, "dynamic claim requires finite TTL");
115
+ const context = ContextSchema.parse({ kind: "mcp", ...(actualClaimIssue(manifest) ? { issueKey: actualClaimIssue(manifest) } : {}) });
116
+ const requestSha256 = claimCreationSha256(manifest);
117
+ const path = `${CLAIMS}/${manifest.metadata.name}`;
118
+ return serialized(guest, async () => {
119
+ const existing = await deps.api.request(path, "GET");
120
+ if (existing)
121
+ return resume(existing, manifest, guest, context, requestSha256, deps);
122
+ await noOtherClaim(deps, guest);
123
+ // A stale legacy reservation for a different (now absent) claim blocks reuse of the guest until the
124
+ // operator's legacy sweep removes it; we never delete it ourselves.
125
+ const stale = await readLegacyReservation(deps.api, guest);
126
+ requireCustody(!stale, `guest ${guest.node}/${guest.vmid} still has a legacy reservation Lease (${legacyReservationLeaseName(guest)}) for another claim; the operator migrates or sweeps it`);
127
+ const record = CustodySchema.parse({ schema: "claim-custody/v1", guest, phase: "bound", operationNonce: deps.nonce(), requestSha256, context, provider: null });
128
+ const body = { ...manifest, metadata: { ...manifest.metadata, annotations: { ...(manifest.metadata.annotations || {}), [CUSTODY_ANNOTATION]: canonicalJson(record) } } };
129
+ const response = await deps.api.request(CLAIMS, "POST", body);
130
+ requireCustody(uuid.safeParse(response?.metadata?.uid).success, "claim creation response lacks actual UID");
131
+ const live = await deps.api.request(path, "GET");
132
+ const held = claimCustody(live);
133
+ requireCustody(held && canonicalJson(held) === canonicalJson(record), "created custody annotation not independently confirmed");
134
+ verifyCustodyClaim(live, held, response.metadata.uid);
135
+ requireCustody(!live.metadata.deletionTimestamp, "new claim is already deleting");
136
+ await noOtherClaim(deps, guest, live.metadata.uid);
137
+ return { claim: live, custody: { source: "annotation", record: held, creationOwnerSha256: creationOwnerSha(held) }, reused: false };
138
+ });
139
+ }
140
+ async function resume(live, manifest, guest, context, requestSha256, deps) {
141
+ const held = claimCustody(live);
142
+ if (held) {
143
+ requireCustody(["bound", "provider-bound"].includes(held.phase) && held.requestSha256 === requestSha256
144
+ && canonicalJson(held.context) === canonicalJson(context) && canonicalJson(held.guest) === canonicalJson(guest), "existing claim custody is occupied by a different request");
145
+ verifyCustodyClaim(live, held);
146
+ requireResumable(live, manifest, deps);
147
+ await noOtherClaim(deps, guest, live.metadata.uid);
148
+ return { claim: live, custody: { source: "annotation", record: held, creationOwnerSha256: creationOwnerSha(held) }, reused: true };
149
+ }
150
+ const legacy = await readLegacyReservation(deps.api, guest);
151
+ if (!legacy)
152
+ throw new NoClaimCustodyError(manifest.metadata.name, guest);
153
+ const { record, lease } = legacy;
154
+ requireCustody(["bound", "provider-bound"].includes(record.phase) && record.requestSha256 === requestSha256
155
+ && canonicalJson(record.context) === canonicalJson(context) && record.intendedClaim.name === manifest.metadata.name, "legacy reservation is occupied by a different request");
156
+ verifyLegacyClaim(live, record, record.claim.uid);
157
+ requireResumable(live, manifest, deps);
158
+ await noOtherClaim(deps, guest, live.metadata.uid);
159
+ return { claim: live, custody: { source: "legacy-lease", record, legacyLease: { name: lease.metadata.name, uid: lease.metadata.uid }, migration: "operator-pending" }, reused: true };
160
+ }
161
+ export async function deleteClaimWithCustody(name, expectedUid, deps = defaults()) {
162
+ objectName.parse(name);
163
+ uuid.parse(expectedUid);
164
+ const live = await deps.api.request(`${CLAIMS}/${name}`, "GET");
165
+ requireCustody(live?.metadata?.uid === expectedUid, "original selected claim UID differs or is absent");
166
+ const guest = claimGuest(live);
167
+ return serialized(guest, async () => {
168
+ const selected = await deps.api.request(`${CLAIMS}/${name}`, "GET");
169
+ requireCustody(selected?.metadata?.uid === expectedUid, "original selected claim UID differs or is absent");
170
+ let source;
171
+ let phase;
172
+ const held = claimCustody(selected);
173
+ if (held) {
174
+ requireCustody(selected.metadata.name === name && canonicalJson(held.guest) === canonicalJson(guest), "claim does not own its custody record");
175
+ verifyCustodyClaim(selected, held, expectedUid);
176
+ source = "annotation";
177
+ phase = held.phase;
178
+ }
179
+ else {
180
+ const legacy = await readLegacyReservation(deps.api, guest);
181
+ if (!legacy)
182
+ throw new NoClaimCustodyError(name, guest);
183
+ requireCustody(["bound", "provider-bound", "retiring"].includes(legacy.record.phase) && legacy.record.claim?.uid === expectedUid && legacy.record.intendedClaim.name === name, "claim does not own the bound legacy reservation");
184
+ verifyLegacyClaim(selected, legacy.record, expectedUid);
185
+ source = "legacy-lease";
186
+ phase = legacy.record.phase;
187
+ }
188
+ await deps.api.request(`${CLAIMS}/${name}`, "DELETE", { apiVersion: "v1", kind: "DeleteOptions", preconditions: { uid: expectedUid, resourceVersion: selected.metadata.resourceVersion } });
189
+ return {
190
+ requested: true, expectedUid, custodySource: source, custodyPhase: phase, custodyRetained: true,
191
+ ...(source === "legacy-lease" ? { migration: "operator-pending" } : {}),
192
+ next: "The governed operator/controller owns teardown: it proves original claim, provider and owned-volume absence before releasing custody."
193
+ };
194
+ });
195
+ }
196
+ //# sourceMappingURL=claim-custody.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim-custody.js","sourceRoot":"","sources":["../../../src/tools/claim-custody.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export type ClaimMethod = "GET" | "POST" | "DELETE";
2
+ export interface ClaimApi {
3
+ request(path: string, method: ClaimMethod, body?: unknown): Promise<any | null>;
4
+ }
5
+ export declare class ClaimApiError extends Error {
6
+ readonly status: number | null;
7
+ constructor(status: number | null, message: string);
8
+ }
9
+ export declare function assertClaimPath(path: string, method: ClaimMethod): void;
10
+ /** Names the denied operation from the request itself. Remote error text is never echoed. */
11
+ export declare function describeClaimRequest(path: string, method: ClaimMethod): string;
12
+ export declare function claimFailureMessage(status: number | null, path: string, method: ClaimMethod): string;
13
+ export declare function createClaimApi(): ClaimApi;
14
+ //# sourceMappingURL=claim-kubernetes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim-kubernetes.d.ts","sourceRoot":"","sources":["../../../src/tools/claim-kubernetes.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAC;AACpD,MAAM,WAAW,QAAQ;IAAG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;CAAE;AAC9G,qBAAa,aAAc,SAAQ,KAAK;aACV,MAAM,EAAE,MAAM,GAAG,IAAI;gBAArB,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM;CACnE;AAGD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAcvE;AACD,6FAA6F;AAC7F,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,MAAM,CAO9E;AACD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,MAAM,CAIpG;AAOD,wBAAgB,cAAc,IAAI,QAAQ,CAgCzC"}
@@ -1,8 +1,8 @@
1
- /** Shared configured Kubernetes transport for physical-slot custody. Never retry an unknown write. */
1
+ /** Shared configured Kubernetes transport for ApplianceClaim custody (REL-1746). Never retry an unknown write. Leases are read-only legacy evidence. */
2
2
  import * as fs from "node:fs";
3
3
  import { fetch, Agent } from "undici";
4
4
  import { KubernetesCommandError, resolveLabKubernetesBackend, runLabKubectl } from "./kubernetes-backend.js";
5
- export class SlotApiError extends Error {
5
+ export class ClaimApiError extends Error {
6
6
  status;
7
7
  constructor(status, message) {
8
8
  super(message);
@@ -11,68 +11,72 @@ export class SlotApiError extends Error {
11
11
  }
12
12
  const MAX_BYTES = 1_048_576;
13
13
  const COLLECTIONS = ["/apis/coordination.k8s.io/v1/namespaces/ct-lab/leases", "/apis/calltelemetry.com/v1alpha1/namespaces/ct-lab/applianceclaims"];
14
- export function assertSlotPath(path, method) {
14
+ export function assertClaimPath(path, method) {
15
15
  const cleanPath = path.split("?")[0];
16
16
  if (path !== cleanPath && !/^\?limit=500(?:&continue=[A-Za-z0-9%._~-]+)?$/.test(path.slice(cleanPath.length)))
17
- throw new Error("physical slot query refused");
17
+ throw new Error("claim custody query refused");
18
18
  const collection = COLLECTIONS.find(p => cleanPath === p || cleanPath.startsWith(p + "/"));
19
19
  if (!collection)
20
- throw new Error("physical slot API path refused");
20
+ throw new Error("claim custody API path refused");
21
21
  const name = cleanPath.slice(collection.length + 1);
22
22
  if (cleanPath !== collection && (name.length > 253 || !name.split(".").every(label => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label))))
23
- throw new Error("physical slot object name refused");
24
- if (method === "POST" && cleanPath !== collection || method !== "POST" && cleanPath === collection && !(method === "GET" && collection === COLLECTIONS[1]))
25
- throw new Error("physical slot method refused");
26
- if (collection === COLLECTIONS[0] && method === "DELETE")
27
- throw new Error("MCP cannot release a durable physical reservation");
28
- if (collection === COLLECTIONS[1] && method === "PUT")
29
- throw new Error("physical slot adapter cannot replace a claim");
23
+ throw new Error("claim custody object name refused");
24
+ if (collection === COLLECTIONS[0]) {
25
+ // Legacy reservation Leases are read-only evidence: the operator owns migration and deletion.
26
+ if (method !== "GET" || cleanPath === collection || !name.startsWith("ct-physical-slot-reservation."))
27
+ throw new Error("MCP never creates, renews or releases a Lease; only legacy reservation reads are allowed");
28
+ return;
29
+ }
30
+ if (!["GET", "POST", "DELETE"].includes(method))
31
+ throw new Error("claim custody method refused");
32
+ if (method === "POST" && cleanPath !== collection || method !== "POST" && cleanPath === collection && method !== "GET")
33
+ throw new Error("claim custody method refused");
30
34
  }
31
35
  /** Names the denied operation from the request itself. Remote error text is never echoed. */
32
- export function describeSlotRequest(path, method) {
36
+ export function describeClaimRequest(path, method) {
33
37
  const cleanPath = path.split("?")[0];
34
38
  const match = /^\/apis\/([^/]+)\/[^/]+\/namespaces\/([^/]+)\/([^/]+)(\/[^/]+)?$/.exec(cleanPath);
35
39
  if (!match)
36
40
  return "an unrecognised resource";
37
41
  const [, group, namespace, resource, named] = match;
38
- const verb = method === "GET" ? (named ? "get" : "list") : method === "POST" ? "create" : method === "PUT" ? "update" : "delete";
42
+ const verb = method === "GET" ? (named ? "get" : "list") : method === "POST" ? "create" : "delete";
39
43
  return `${verb} ${resource}.${group} in namespace ${namespace}`;
40
44
  }
41
- export function slotFailureMessage(status, path, method) {
42
- const detail = describeSlotRequest(path, method);
45
+ export function claimFailureMessage(status, path, method) {
46
+ const detail = describeClaimRequest(path, method);
43
47
  const hint = status === 403 ? `; the service account needs RBAC to ${detail}` : "";
44
- return `physical slot Kubernetes request failed (${status ?? "unknown"}) on ${detail}${hint}; retain custody`;
48
+ return `claim custody Kubernetes request failed (${status ?? "unknown"}) on ${detail}${hint}; custody unchanged`;
45
49
  }
46
50
  function parse(text) {
47
51
  if (Buffer.byteLength(text) > MAX_BYTES)
48
- throw new Error("physical slot response exceeds bound");
52
+ throw new Error("claim custody response exceeds bound");
49
53
  const value = JSON.parse(text);
50
54
  if (!value || typeof value !== "object" || Array.isArray(value))
51
- throw new Error("physical slot response is not an object");
55
+ throw new Error("claim custody response is not an object");
52
56
  return value;
53
57
  }
54
- export function createSlotApi() {
58
+ export function createClaimApi() {
55
59
  return { async request(path, method, body) {
56
- assertSlotPath(path, method);
60
+ assertClaimPath(path, method);
57
61
  const backend = resolveLabKubernetesBackend();
58
62
  if (backend.mode === "in-cluster") {
59
63
  const token = fs.readFileSync(backend.tokenPath, "utf8").trim();
60
64
  if (!token)
61
- throw new Error("physical slot Kubernetes service identity refused");
65
+ throw new Error("claim custody Kubernetes service identity refused");
62
66
  const agent = new Agent({ connect: { ca: fs.readFileSync(backend.caPath), rejectUnauthorized: true } });
63
67
  try {
64
68
  const response = await fetch(`${backend.server}${path}`, { method, headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, body: body === undefined ? undefined : JSON.stringify(body), dispatcher: agent, signal: AbortSignal.timeout(10_000) });
65
69
  if (response.status === 404 && method === "GET")
66
70
  return null;
67
71
  if (!response.ok)
68
- throw new SlotApiError(response.status, slotFailureMessage(response.status, path, method));
72
+ throw new ClaimApiError(response.status, claimFailureMessage(response.status, path, method));
69
73
  return parse(await response.text());
70
74
  }
71
75
  finally {
72
76
  await agent.close();
73
77
  }
74
78
  }
75
- const command = method === "GET" ? "get" : method === "POST" ? "create" : method === "PUT" ? "replace" : "delete";
79
+ const command = method === "GET" ? "get" : method === "POST" ? "create" : "delete";
76
80
  try {
77
81
  const result = await runLabKubectl([command, "--raw", path, ...(body === undefined ? [] : ["-f", "-"])], {
78
82
  ...(body === undefined ? {} : { input: JSON.stringify(body) }),
@@ -86,10 +90,10 @@ export function createSlotApi() {
86
90
  if (method === "GET" && status === 404)
87
91
  return null;
88
92
  if (error instanceof KubernetesCommandError) {
89
- throw new SlotApiError(status, slotFailureMessage(status, path, method));
93
+ throw new ClaimApiError(status, claimFailureMessage(status, path, method));
90
94
  }
91
95
  throw error;
92
96
  }
93
97
  } };
94
98
  }
95
- //# sourceMappingURL=slot-kubernetes.js.map
99
+ //# sourceMappingURL=claim-kubernetes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim-kubernetes.js","sourceRoot":"","sources":["../../../src/tools/claim-kubernetes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Claim reconcile gate (UAT-1785).
3
+ *
4
+ * A claim is only a guest's protection if an ApplianceClaim operator is actually
5
+ * watching the cluster the claim was written to. When the claim plane and the
6
+ * operator's cluster differ (for example the operator moved to another cluster
7
+ * and the tooling still writes to the old one), a new claim is never reconciled:
8
+ * its status stays empty, and the operator that is running elsewhere sees the
9
+ * imported guest as unclaimed and destroys it once its grace window elapses,
10
+ * minutes into a UAT run while the claim and lease still look live.
11
+ *
12
+ * A profile=bare claim reaches status.phase=Ready within seconds of an operator
13
+ * seeing it, before any guest exists. Creating the guest only after that
14
+ * observation turns the silent mid-run loss into an immediate, actionable
15
+ * refusal that happens before a guest exists.
16
+ */
17
+ import { type CallToolResult } from "../types/mcp.js";
18
+ export declare const CLAIM_RECONCILE_TIMEOUT_ENV = "CT_LAB_CLAIM_RECONCILE_TIMEOUT_MS";
19
+ export declare const CLAIM_RECONCILE_DEFAULT_TIMEOUT_MS = 120000;
20
+ export declare const CLAIM_RECONCILE_POLL_MS = 3000;
21
+ export interface ClaimReconcileDeps {
22
+ getClaim: (args: {
23
+ name: string;
24
+ namespace: string;
25
+ }) => Promise<CallToolResult>;
26
+ sleep: (ms: number) => Promise<void>;
27
+ now: () => number;
28
+ timeoutMs: number;
29
+ pollMs: number;
30
+ }
31
+ export type ClaimReconcileOutcome = {
32
+ reconciled: true;
33
+ phase: "Ready";
34
+ } | {
35
+ reconciled: false;
36
+ reason: "not_reconciled" | "failed" | "closed";
37
+ phase: string | null;
38
+ detail: string;
39
+ };
40
+ export declare function claimReconcileTimeoutMs(env?: NodeJS.ProcessEnv): number;
41
+ /**
42
+ * Poll the exact claim until an operator has set it Ready. A Failed or Closed
43
+ * phase is final; an empty phase is retried until the timeout and then reported
44
+ * as "not reconciled" (no operator saw the claim).
45
+ */
46
+ export declare function awaitClaimReconciled(name: string, namespace: string, overrides?: Partial<ClaimReconcileDeps>): Promise<ClaimReconcileOutcome>;
47
+ /** Tool error for a claim no operator reconciled. The claim stays; nothing was imported. */
48
+ export declare function claimNotReconciledResult(outcome: Extract<ClaimReconcileOutcome, {
49
+ reconciled: false;
50
+ }>, context: {
51
+ operation: string;
52
+ claimName: string;
53
+ namespace: string;
54
+ timeoutMs: number;
55
+ }): CallToolResult;
56
+ //# sourceMappingURL=claim-reconcile-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim-reconcile-gate.d.ts","sourceRoot":"","sources":["../../../src/tools/claim-reconcile-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAyB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAG7E,eAAO,MAAM,2BAA2B,sCAAsC,CAAC;AAC/E,eAAO,MAAM,kCAAkC,SAAU,CAAC;AAC1D,eAAO,MAAM,uBAAuB,OAAQ,CAAC;AAG7C,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACjF,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAE,UAAU,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GACpC;IAAE,UAAU,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,gBAAgB,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhH,wBAAgB,uBAAuB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAQpF;AAsBD;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,OAAO,CAAC,kBAAkB,CAAM,GAC1C,OAAO,CAAC,qBAAqB,CAAC,CAiChC;AAED,4FAA4F;AAC5F,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,CAAC,qBAAqB,EAAE;IAAE,UAAU,EAAE,KAAK,CAAA;CAAE,CAAC,EAC9D,OAAO,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACtF,cAAc,CAgBhB"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Claim reconcile gate (UAT-1785).
3
+ *
4
+ * A claim is only a guest's protection if an ApplianceClaim operator is actually
5
+ * watching the cluster the claim was written to. When the claim plane and the
6
+ * operator's cluster differ (for example the operator moved to another cluster
7
+ * and the tooling still writes to the old one), a new claim is never reconciled:
8
+ * its status stays empty, and the operator that is running elsewhere sees the
9
+ * imported guest as unclaimed and destroys it once its grace window elapses,
10
+ * minutes into a UAT run while the claim and lease still look live.
11
+ *
12
+ * A profile=bare claim reaches status.phase=Ready within seconds of an operator
13
+ * seeing it, before any guest exists. Creating the guest only after that
14
+ * observation turns the silent mid-run loss into an immediate, actionable
15
+ * refusal that happens before a guest exists.
16
+ */
17
+ import { createToolErrorResult } from "../types/mcp.js";
18
+ import { handleGetApplianceClaim } from "./appliance-claim-tools.js";
19
+ export const CLAIM_RECONCILE_TIMEOUT_ENV = "CT_LAB_CLAIM_RECONCILE_TIMEOUT_MS";
20
+ export const CLAIM_RECONCILE_DEFAULT_TIMEOUT_MS = 120_000;
21
+ export const CLAIM_RECONCILE_POLL_MS = 3_000;
22
+ const CLAIM_RECONCILE_MAX_TIMEOUT_MS = 900_000;
23
+ export function claimReconcileTimeoutMs(env = process.env) {
24
+ const raw = env[CLAIM_RECONCILE_TIMEOUT_ENV]?.trim();
25
+ if (!raw)
26
+ return CLAIM_RECONCILE_DEFAULT_TIMEOUT_MS;
27
+ const parsed = Number(raw);
28
+ if (!Number.isInteger(parsed) || parsed < 0 || parsed > CLAIM_RECONCILE_MAX_TIMEOUT_MS) {
29
+ return CLAIM_RECONCILE_DEFAULT_TIMEOUT_MS;
30
+ }
31
+ return parsed;
32
+ }
33
+ function defaultDeps() {
34
+ return {
35
+ getClaim: (args) => handleGetApplianceClaim(args),
36
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
37
+ now: () => Date.now(),
38
+ timeoutMs: claimReconcileTimeoutMs(),
39
+ pollMs: CLAIM_RECONCILE_POLL_MS,
40
+ };
41
+ }
42
+ function claimOf(result) {
43
+ const block = result.content?.[0];
44
+ if (!block || block.type !== "text")
45
+ return null;
46
+ try {
47
+ return JSON.parse(block.text)?.claim ?? null;
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ /**
54
+ * Poll the exact claim until an operator has set it Ready. A Failed or Closed
55
+ * phase is final; an empty phase is retried until the timeout and then reported
56
+ * as "not reconciled" (no operator saw the claim).
57
+ */
58
+ export async function awaitClaimReconciled(name, namespace, overrides = {}) {
59
+ const deps = { ...defaultDeps(), ...overrides };
60
+ const deadline = deps.now() + deps.timeoutMs;
61
+ let phase = null;
62
+ let detail = "the claim has no status; no operator has reconciled it";
63
+ for (;;) {
64
+ let result = null;
65
+ try {
66
+ result = await deps.getClaim({ name, namespace });
67
+ }
68
+ catch (error) {
69
+ detail = `claim read failed: ${String(error?.message ?? error).slice(0, 160)}`;
70
+ }
71
+ if (result && !result.isError) {
72
+ const claim = claimOf(result);
73
+ const live = typeof claim?.status?.phase === "string" && claim.status.phase ? claim.status.phase : null;
74
+ phase = live;
75
+ if (live === "Ready")
76
+ return { reconciled: true, phase: "Ready" };
77
+ if (live === "Failed" || live === "Closed") {
78
+ return {
79
+ reconciled: false,
80
+ reason: live === "Failed" ? "failed" : "closed",
81
+ phase: live,
82
+ detail: String(claim?.status?.message ?? `claim phase is ${live}`).slice(0, 240),
83
+ };
84
+ }
85
+ detail = live ? `claim phase is '${live}', not Ready` : "the claim has no status; no operator has reconciled it";
86
+ }
87
+ else if (result) {
88
+ detail = "claim read returned an error";
89
+ }
90
+ if (deps.now() + deps.pollMs > deadline)
91
+ break;
92
+ await deps.sleep(deps.pollMs);
93
+ }
94
+ return { reconciled: false, reason: "not_reconciled", phase, detail };
95
+ }
96
+ /** Tool error for a claim no operator reconciled. The claim stays; nothing was imported. */
97
+ export function claimNotReconciledResult(outcome, context) {
98
+ const cause = outcome.reason === "not_reconciled"
99
+ ? "No ApplianceClaim operator reconciled it. The cluster this tool writes claims to (LAB_CLAIM_K8S_CONTEXT / LAB_CLAIM_K8S_SERVER) is not the cluster the operator watches, or the operator is not running there. An operator on another cluster would destroy an imported guest as unclaimed."
100
+ : `The operator set the claim to ${outcome.phase}.`;
101
+ return createToolErrorResult("CLAIM_NOT_RECONCILED", `${context.operation}: claim '${context.claimName}' did not reach Ready within ${Math.round(context.timeoutMs / 1000)}s (${outcome.detail}). ${cause} The image was NOT imported and no guest exists. The finite claim is retained; check the claim plane binding and the operator, then retry.`, {
102
+ operation: context.operation,
103
+ claimName: context.claimName,
104
+ namespace: context.namespace,
105
+ phase: outcome.phase,
106
+ guestCreated: false,
107
+ claimProtection: { disposition: "retained", guestAbsentVerified: true },
108
+ });
109
+ }
110
+ //# sourceMappingURL=claim-reconcile-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claim-reconcile-gate.js","sourceRoot":"","sources":["../../../src/tools/claim-reconcile-gate.ts"],"names":[],"mappings":"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"}
@@ -26,6 +26,7 @@
26
26
  import { z } from "zod";
27
27
  import { CallToolResult } from "../types/mcp.js";
28
28
  import { ToolContext } from "./list-hosts.js";
29
+ import { type ClaimReconcileOutcome } from "./claim-reconcile-gate.js";
29
30
  export declare const ClaimedImportQcowInputObjectSchema: z.ZodObject<{
30
31
  readonly assetId: z.ZodString;
31
32
  readonly hostAssetId: z.ZodString;
@@ -120,6 +121,8 @@ export interface ClaimedImportDeps {
120
121
  createClaim: (args: unknown) => Promise<CallToolResult>;
121
122
  deleteClaim: (args: unknown) => Promise<CallToolResult>;
122
123
  importQcow: (args: unknown, context: ToolContext) => Promise<CallToolResult>;
124
+ /** Observes the created claim reaching Ready (an operator is watching this plane) before any guest exists. */
125
+ awaitClaimReconciled?: (name: string, namespace: string) => Promise<ClaimReconcileOutcome>;
123
126
  }
124
127
  export declare function handleClaimedImportQcow(rawArgs: ClaimedImportQcowRawInput | undefined, context: ToolContext, deps?: ClaimedImportDeps): Promise<CallToolResult>;
125
128
  export declare const CLAIMED_IMPORT_QCOW_TOOL_DESCRIPTION = "Import a published or private-candidate appliance QCOW2 as a Proxmox lab guest AND claim it, in one sanctioned step. The strict appliance operator destroys every guest no ApplianceClaim names within ~5 minutes, so this tool creates a profile=bare ApplianceClaim (no seed, no license, no bootstrap; names node + VMID + VM name; finite TTL) FIRST, then runs the canonical lab-inventory vm provision --source published-qcow apply (digest-bound, leased, journaled, receipted; the guest then awaits vm_repair, finalize and uat_access), and retains finite claim protection if the import fails, throws or has an uncertain outcome. Reconcile the exact canonical operation before explicit governed teardown. Dry-run is the default: it runs the canonical import plan and previews the claim. Requires vmid, vmPrefix and node (minis|proxmox|ibm64, matching hostAssetId). Renew with renew_appliance_claim; release with delete_appliance_claim. The guest disk is thin-only: thick or undeterminable storage is refused with THICK_STORAGE_REFUSED before the claim is created.";
@@ -1 +1 @@
1
- {"version":3,"file":"claimed-import-qcow.d.ts","sourceRoot":"","sources":["../../../src/tools/claimed-import-qcow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAA2C,MAAM,iBAAiB,CAAC;AAC1F,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAiC9C,eAAO,MAAM,kCAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuBpC,CAAC;AAEZ,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kCAAkC,CAAC,CAAC;AAE3F,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACxD,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACxD,UAAU,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CAC9E;AAoBD,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,yBAAyB,GAAG,SAAS,EAC9C,OAAO,EAAE,WAAW,EACpB,IAAI,GAAE,iBAA+B,GACpC,OAAO,CAAC,cAAc,CAAC,CAqKzB;AAED,eAAO,MAAM,oCAAoC,siCACo/B,CAAC"}
1
+ {"version":3,"file":"claimed-import-qcow.d.ts","sourceRoot":"","sources":["../../../src/tools/claimed-import-qcow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAA2C,MAAM,iBAAiB,CAAC;AAC1F,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG9C,OAAO,EAA2E,KAAK,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AA+BhJ,eAAO,MAAM,kCAAkC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuBpC,CAAC;AAEZ,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kCAAkC,CAAC,CAAC;AAE3F,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACxD,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACxD,UAAU,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7E,8GAA8G;IAC9G,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;CAC5F;AAqBD,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,yBAAyB,GAAG,SAAS,EAC9C,OAAO,EAAE,WAAW,EACpB,IAAI,GAAE,iBAA+B,GACpC,OAAO,CAAC,cAAc,CAAC,CAqLzB;AAED,eAAO,MAAM,oCAAoC,siCACo/B,CAAC"}
@@ -27,6 +27,7 @@ import { z } from "zod";
27
27
  import { ProxmoxErrorCode, createToolErrorResult } from "../types/mcp.js";
28
28
  import { canonicalOperationAvailable } from "./canonical-guard.js";
29
29
  import { guardCanonicalThinStorage } from "./thin-storage-policy.js";
30
+ import { awaitClaimReconciled, claimNotReconciledResult, claimReconcileTimeoutMs } from "./claim-reconcile-gate.js";
30
31
  import { reservedIdentityError } from "./sha-channel.js";
31
32
  import { ImportQcowInputObjectSchema, ImportQcowInputSchema, handleProxmoxImportQcow, importQcowVersionFromUrl, } from "./import-qcow.js";
32
33
  import { buildApplianceClaimManifest, handleCreateApplianceClaim, handleDeleteApplianceClaim, normalizeClaimNode, CreateApplianceClaimInputSchema, } from "./appliance-claim-tools.js";
@@ -65,6 +66,7 @@ const defaultDeps = {
65
66
  createClaim: (args) => handleCreateApplianceClaim(args),
66
67
  deleteClaim: (args) => handleDeleteApplianceClaim(args),
67
68
  importQcow: (args, context) => handleProxmoxImportQcow(args, context),
69
+ awaitClaimReconciled: (name, namespace) => awaitClaimReconciled(name, namespace),
68
70
  };
69
71
  function textOf(result) {
70
72
  const block = result.content?.[0];
@@ -165,6 +167,21 @@ export async function handleClaimedImportQcow(rawArgs, context, deps = defaultDe
165
167
  if (created.isError) {
166
168
  return createToolErrorResult(ProxmoxErrorCode.UPSTREAM_API_ERROR, `claim creation failed; the image was NOT imported: ${textOf(created)}`, { operation: "claimed_import_qcow", claimName: built.claimName });
167
169
  }
170
+ // UAT-1785: the claim only protects the guest if an operator watches the plane
171
+ // it was written to. Require the operator to reconcile (Ready) it BEFORE the
172
+ // guest exists; otherwise refuse here instead of losing the guest mid-run.
173
+ const claimNamespace = built.manifest.metadata.namespace ?? "ct-lab";
174
+ if (deps.awaitClaimReconciled) {
175
+ const reconciled = await deps.awaitClaimReconciled(built.claimName, claimNamespace);
176
+ if (!reconciled.reconciled) {
177
+ return claimNotReconciledResult(reconciled, {
178
+ operation: "claimed_import_qcow",
179
+ claimName: built.claimName,
180
+ namespace: claimNamespace,
181
+ timeoutMs: claimReconcileTimeoutMs(),
182
+ });
183
+ }
184
+ }
168
185
  let imported;
169
186
  try {
170
187
  imported = await deps.importQcow(importArgs, context);