@manifest-network/manifest-agent-core 0.9.0
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/README.md +39 -0
- package/dist/close-lease.d.ts +33 -0
- package/dist/close-lease.d.ts.map +1 -0
- package/dist/close-lease.js +138 -0
- package/dist/close-lease.js.map +1 -0
- package/dist/deploy-app.d.ts +24 -0
- package/dist/deploy-app.d.ts.map +1 -0
- package/dist/deploy-app.js +446 -0
- package/dist/deploy-app.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -0
- package/dist/internals/classify-deploy-error.d.ts +41 -0
- package/dist/internals/classify-deploy-error.d.ts.map +1 -0
- package/dist/internals/classify-deploy-error.js +79 -0
- package/dist/internals/classify-deploy-error.js.map +1 -0
- package/dist/internals/classify-deploy-response.d.ts +56 -0
- package/dist/internals/classify-deploy-response.d.ts.map +1 -0
- package/dist/internals/classify-deploy-response.js +33 -0
- package/dist/internals/classify-deploy-response.js.map +1 -0
- package/dist/internals/connection.d.ts +76 -0
- package/dist/internals/connection.d.ts.map +1 -0
- package/dist/internals/connection.js +94 -0
- package/dist/internals/connection.js.map +1 -0
- package/dist/internals/evaluate-readiness.d.ts +55 -0
- package/dist/internals/evaluate-readiness.d.ts.map +1 -0
- package/dist/internals/evaluate-readiness.js +131 -0
- package/dist/internals/evaluate-readiness.js.map +1 -0
- package/dist/internals/find-sku-uuid.d.ts +40 -0
- package/dist/internals/find-sku-uuid.d.ts.map +1 -0
- package/dist/internals/find-sku-uuid.js +20 -0
- package/dist/internals/find-sku-uuid.js.map +1 -0
- package/dist/internals/format-success.d.ts +35 -0
- package/dist/internals/format-success.d.ts.map +1 -0
- package/dist/internals/format-success.js +80 -0
- package/dist/internals/format-success.js.map +1 -0
- package/dist/internals/guarded-fetch.d.ts +138 -0
- package/dist/internals/guarded-fetch.d.ts.map +1 -0
- package/dist/internals/guarded-fetch.js +242 -0
- package/dist/internals/guarded-fetch.js.map +1 -0
- package/dist/internals/humanize-denom.d.ts +45 -0
- package/dist/internals/humanize-denom.d.ts.map +1 -0
- package/dist/internals/humanize-denom.js +105 -0
- package/dist/internals/humanize-denom.js.map +1 -0
- package/dist/internals/inspect-image.d.ts +31 -0
- package/dist/internals/inspect-image.d.ts.map +1 -0
- package/dist/internals/inspect-image.js +345 -0
- package/dist/internals/inspect-image.js.map +1 -0
- package/dist/internals/lease-items.d.ts +46 -0
- package/dist/internals/lease-items.d.ts.map +1 -0
- package/dist/internals/lease-items.js +58 -0
- package/dist/internals/lease-items.js.map +1 -0
- package/dist/internals/lease-state.d.ts +32 -0
- package/dist/internals/lease-state.d.ts.map +1 -0
- package/dist/internals/lease-state.js +80 -0
- package/dist/internals/lease-state.js.map +1 -0
- package/dist/internals/render-deployment-plan.d.ts +22 -0
- package/dist/internals/render-deployment-plan.d.ts.map +1 -0
- package/dist/internals/render-deployment-plan.js +135 -0
- package/dist/internals/render-deployment-plan.js.map +1 -0
- package/dist/internals/render-intent-recap.d.ts +43 -0
- package/dist/internals/render-intent-recap.d.ts.map +1 -0
- package/dist/internals/render-intent-recap.js +136 -0
- package/dist/internals/render-intent-recap.js.map +1 -0
- package/dist/internals/render-partial-success-prompt.d.ts +26 -0
- package/dist/internals/render-partial-success-prompt.d.ts.map +1 -0
- package/dist/internals/render-partial-success-prompt.js +53 -0
- package/dist/internals/render-partial-success-prompt.js.map +1 -0
- package/dist/internals/save-manifest.d.ts +105 -0
- package/dist/internals/save-manifest.d.ts.map +1 -0
- package/dist/internals/save-manifest.js +122 -0
- package/dist/internals/save-manifest.js.map +1 -0
- package/dist/internals/secret-denylist.d.ts +42 -0
- package/dist/internals/secret-denylist.d.ts.map +1 -0
- package/dist/internals/secret-denylist.js +59 -0
- package/dist/internals/secret-denylist.js.map +1 -0
- package/dist/internals/spec-normalize.d.ts +84 -0
- package/dist/internals/spec-normalize.d.ts.map +1 -0
- package/dist/internals/spec-normalize.js +169 -0
- package/dist/internals/spec-normalize.js.map +1 -0
- package/dist/internals/verify-domain-state.d.ts +20 -0
- package/dist/internals/verify-domain-state.d.ts.map +1 -0
- package/dist/internals/verify-domain-state.js +63 -0
- package/dist/internals/verify-domain-state.js.map +1 -0
- package/dist/internals/verify-recover.d.ts +120 -0
- package/dist/internals/verify-recover.d.ts.map +1 -0
- package/dist/internals/verify-recover.js +91 -0
- package/dist/internals/verify-recover.js.map +1 -0
- package/dist/manage-domain.d.ts +36 -0
- package/dist/manage-domain.d.ts.map +1 -0
- package/dist/manage-domain.js +230 -0
- package/dist/manage-domain.js.map +1 -0
- package/dist/troubleshoot.d.ts +23 -0
- package/dist/troubleshoot.d.ts.map +1 -0
- package/dist/troubleshoot.js +124 -0
- package/dist/troubleshoot.js.map +1 -0
- package/dist/types.d.ts +294 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +0 -0
- package/package.json +56 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import { decode } from "./internals/lease-state.js";
|
|
2
|
+
import { classifyDeployError } from "./internals/classify-deploy-error.js";
|
|
3
|
+
import { extractRunningEndpoints, formatEndpointAsUrl, normalizeFredUrl } from "./internals/connection.js";
|
|
4
|
+
import { classifyDeployResponse } from "./internals/classify-deploy-response.js";
|
|
5
|
+
import { findSkuUuid } from "./internals/find-sku-uuid.js";
|
|
6
|
+
import { EMPTY_DENOM_MAP, loadChainDenomMap } from "./internals/humanize-denom.js";
|
|
7
|
+
import { renderDeploymentPlan } from "./internals/render-deployment-plan.js";
|
|
8
|
+
import { isStackSpec, summarizeSpec, validateSpec } from "./internals/spec-normalize.js";
|
|
9
|
+
import { renderIntentRecap } from "./internals/render-intent-recap.js";
|
|
10
|
+
import { renderPartialSuccessPrompt } from "./internals/render-partial-success-prompt.js";
|
|
11
|
+
import { ManifestMCPError, ManifestMCPErrorCode, cosmosEstimateFee, setItemCustomDomain, stopApp } from "@manifest-network/manifest-mcp-core";
|
|
12
|
+
import { AuthTimestampTracker, buildManifestPreview, checkDeploymentReadiness, createAuthToken, createLeaseDataSignMessage, createSignMessage, deployApp as deployApp$1 } from "@manifest-network/manifest-mcp-fred";
|
|
13
|
+
//#region src/deploy-app.ts
|
|
14
|
+
/**
|
|
15
|
+
* Public entry point: orchestrate a Manifest-Network app deployment from
|
|
16
|
+
* a typed `DeploySpec` through the plan/confirm/broadcast/save flow.
|
|
17
|
+
*
|
|
18
|
+
* Architect's α-locked composition (post-PR-3 sub-plan Q1):
|
|
19
|
+
*
|
|
20
|
+
* Happy path: `fredDeployApp` (workspace MCP-tool function) is called
|
|
21
|
+
* atomically for create-lease + manifest upload + (optional) set-
|
|
22
|
+
* item-custom-domain. agent-core wraps the call with planning, user
|
|
23
|
+
* confirmation, progress events, and post-success persistence.
|
|
24
|
+
*
|
|
25
|
+
* Recovery path: when fred's atomic deployApp throws or the lease
|
|
26
|
+
* reaches a non-recoverable state, agent-core renders a recovery
|
|
27
|
+
* prompt (typed `RecoveryOption[]`), invokes `onFailure`, and
|
|
28
|
+
* dispatches the user's `RecoveryChoice` to inline closures that
|
|
29
|
+
* call core's decomposed primitives (`setItemCustomDomain` for
|
|
30
|
+
* `retry_set_domain`; `stopApp` for `close_lease`).
|
|
31
|
+
*
|
|
32
|
+
* E-hybrid runtime-context (post-PR-3 sub-plan Q5):
|
|
33
|
+
*
|
|
34
|
+
* `opts: DeployAppOptions` carries `clientManager` (chain ops),
|
|
35
|
+
* `walletProvider` (ADR-036 auth-token construction), optional
|
|
36
|
+
* `fetchFn` (HTTP override for fred's upload), and the chain-data /
|
|
37
|
+
* denomMap injection for humanization. agent-core composes the
|
|
38
|
+
* auth-token callbacks internally from `walletProvider` so callers
|
|
39
|
+
* don't need to know about ADR-036 plumbing.
|
|
40
|
+
*
|
|
41
|
+
* Auth-callback construction follows fred's `AuthTokenService` pattern
|
|
42
|
+
* (verified against `packages/fred/src/http/auth.ts` per TL2.1 silent-
|
|
43
|
+
* fix discipline):
|
|
44
|
+
*
|
|
45
|
+
* 1. `timestamps.next()` → monotonic replay-safe timestamp.
|
|
46
|
+
* 2. `createSignMessage(address, leaseUuid, timestamp)` → message.
|
|
47
|
+
* 3. `walletProvider.signArbitrary(address, message)` → `{ pub_key,
|
|
48
|
+
* signature }` (cosmjs convention; `pub_key.value` is base64).
|
|
49
|
+
* 4. `createAuthToken(address, leaseUuid, timestamp, pub_key.value,
|
|
50
|
+
* signature[, metaHashHex])` → token string.
|
|
51
|
+
*/
|
|
52
|
+
/**
|
|
53
|
+
* Orchestrate a deployment. See module-level docstring for the architect-
|
|
54
|
+
* locked composition + E-hybrid runtime-context contract.
|
|
55
|
+
*
|
|
56
|
+
* @throws `ManifestMCPError(INVALID_CONFIG)` for spec / wallet validation.
|
|
57
|
+
* @throws `ManifestMCPError(INVALID_CONFIG)` when `onConfirm` returns
|
|
58
|
+
* `'no'` or `onPlan` returns `'cancel'`.
|
|
59
|
+
*
|
|
60
|
+
* Errors from fred's broadcast or core's recovery primitives surface as
|
|
61
|
+
* typed `ManifestMCPError`s. Partial-success failures with applicable
|
|
62
|
+
* recovery options route through `onFailure(envelope, options)` — the
|
|
63
|
+
* callback's return value drives recovery dispatch via the inline
|
|
64
|
+
* closures in `dispatchRecovery`. Non-partial or inform-only failures
|
|
65
|
+
* (no recovery choices to present, per `handleBroadcastFailure`'s F3
|
|
66
|
+
* branch) throw directly as `ManifestMCPError(TX_FAILED)` without
|
|
67
|
+
* invoking `onFailure`.
|
|
68
|
+
*/
|
|
69
|
+
async function deployApp(spec, callbacks, opts) {
|
|
70
|
+
try {
|
|
71
|
+
validateSpec(spec);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, err instanceof Error ? err.message : `Invalid spec: ${String(err)}`);
|
|
74
|
+
}
|
|
75
|
+
if (typeof opts.walletProvider.signArbitrary !== "function") throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, "opts.walletProvider must implement signArbitrary for ADR-036 auth tokens.");
|
|
76
|
+
const walletAddress = await opts.walletProvider.getAddress();
|
|
77
|
+
const clientAddress = await opts.clientManager.getAddress();
|
|
78
|
+
if (walletAddress !== clientAddress) throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, `opts.walletProvider and opts.clientManager are bound to different addresses (walletProvider=${walletAddress}, clientManager=${clientAddress}); they must reference the same wallet to avoid creating an orphaned lease on the clientManager wallet when ADR-036 auth (signed by walletProvider) fails.`);
|
|
79
|
+
const tenantAddress = walletAddress;
|
|
80
|
+
const denomMap = opts.denomMap ?? (opts.chainDataFile ? await loadChainDenomMap(opts.chainDataFile) : EMPTY_DENOM_MAP);
|
|
81
|
+
const chainId = opts.clientManager.getConfig().chainId;
|
|
82
|
+
const activeChain = /mainnet|main/i.test(chainId) ? "mainnet" : "testnet";
|
|
83
|
+
const queryClient = await opts.clientManager.getQueryClient();
|
|
84
|
+
let readiness = evaluateReadinessFromRaw(await checkDeploymentReadiness(queryClient, tenantAddress, {
|
|
85
|
+
image: primaryImage(spec),
|
|
86
|
+
size: requestedSize(spec)
|
|
87
|
+
}), opts.clientManager.getConfig().gasPrice ?? "1umfx", denomMap);
|
|
88
|
+
callbacks.onProgress?.({
|
|
89
|
+
kind: "readiness_evaluated",
|
|
90
|
+
readiness
|
|
91
|
+
});
|
|
92
|
+
if (readiness.status === "block") {
|
|
93
|
+
`${readiness.reasons.join("; ")}`;
|
|
94
|
+
throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, `Readiness check failed: ${readiness.reasons.join("; ")}`);
|
|
95
|
+
}
|
|
96
|
+
let preview = await buildManifestPreview(buildManifestPreviewInput(spec, requestedSize(spec)));
|
|
97
|
+
let summary = summarizeSpec(spec);
|
|
98
|
+
let fees = await estimateFees(opts, spec, preview.meta_hash_hex);
|
|
99
|
+
let plan = {
|
|
100
|
+
summary,
|
|
101
|
+
readiness,
|
|
102
|
+
fees
|
|
103
|
+
};
|
|
104
|
+
let confirmedSpec = spec;
|
|
105
|
+
const block = renderDeploymentPlan({
|
|
106
|
+
plan,
|
|
107
|
+
denomMap,
|
|
108
|
+
image: primaryImage(spec),
|
|
109
|
+
size: requestedSize(spec),
|
|
110
|
+
metaHash: preview.meta_hash_hex,
|
|
111
|
+
customDomain: customDomainOf(spec),
|
|
112
|
+
customDomainService: customDomainServiceOf(spec)
|
|
113
|
+
});
|
|
114
|
+
callbacks.onProgress?.({
|
|
115
|
+
kind: "deployment_plan_rendered",
|
|
116
|
+
block
|
|
117
|
+
});
|
|
118
|
+
if (callbacks.onPlan) {
|
|
119
|
+
const verdict = await callbacks.onPlan(plan);
|
|
120
|
+
if (verdict === "cancel") throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, "User cancelled deployment at plan step.");
|
|
121
|
+
if (verdict !== "confirm") {
|
|
122
|
+
confirmedSpec = applyPlanEdit(confirmedSpec, verdict);
|
|
123
|
+
try {
|
|
124
|
+
validateSpec(confirmedSpec);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, err instanceof Error ? `Post-edit spec failed validation: ${err.message}` : `Post-edit spec failed validation: ${String(err)}`);
|
|
127
|
+
}
|
|
128
|
+
readiness = evaluateReadinessFromRaw(await checkDeploymentReadiness(queryClient, tenantAddress, {
|
|
129
|
+
image: primaryImage(confirmedSpec),
|
|
130
|
+
size: requestedSize(confirmedSpec)
|
|
131
|
+
}), opts.clientManager.getConfig().gasPrice ?? "1umfx", denomMap);
|
|
132
|
+
callbacks.onProgress?.({
|
|
133
|
+
kind: "readiness_evaluated",
|
|
134
|
+
readiness
|
|
135
|
+
});
|
|
136
|
+
if (readiness.status === "block") throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, `Post-edit readiness check failed: ${readiness.reasons.join("; ")}`);
|
|
137
|
+
preview = await buildManifestPreview(buildManifestPreviewInput(confirmedSpec, requestedSize(confirmedSpec)));
|
|
138
|
+
summary = summarizeSpec(confirmedSpec);
|
|
139
|
+
fees = await estimateFees(opts, confirmedSpec, preview.meta_hash_hex);
|
|
140
|
+
plan = {
|
|
141
|
+
summary,
|
|
142
|
+
readiness,
|
|
143
|
+
fees
|
|
144
|
+
};
|
|
145
|
+
const editedBlock = renderDeploymentPlan({
|
|
146
|
+
plan,
|
|
147
|
+
denomMap,
|
|
148
|
+
image: primaryImage(confirmedSpec),
|
|
149
|
+
size: requestedSize(confirmedSpec),
|
|
150
|
+
metaHash: preview.meta_hash_hex,
|
|
151
|
+
customDomain: customDomainOf(confirmedSpec),
|
|
152
|
+
customDomainService: customDomainServiceOf(confirmedSpec)
|
|
153
|
+
});
|
|
154
|
+
callbacks.onProgress?.({
|
|
155
|
+
kind: "deployment_plan_rendered",
|
|
156
|
+
block: editedBlock
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const recapBlock = { text: renderIntentRecap({
|
|
161
|
+
spec: confirmedSpec,
|
|
162
|
+
activeChain
|
|
163
|
+
}) };
|
|
164
|
+
if (callbacks.onConfirm) {
|
|
165
|
+
if (await callbacks.onConfirm(recapBlock) !== "yes") throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, "User declined to proceed at intent-recap step.");
|
|
166
|
+
}
|
|
167
|
+
callbacks.onProgress?.({ kind: "user_confirmed" });
|
|
168
|
+
const signArbitrary = opts.walletProvider.signArbitrary.bind(opts.walletProvider);
|
|
169
|
+
const timestamps = new AuthTimestampTracker();
|
|
170
|
+
const getAuthToken = async (address, leaseUuid) => {
|
|
171
|
+
const ts = await timestamps.next();
|
|
172
|
+
const { pub_key, signature } = await signArbitrary(address, createSignMessage(address, leaseUuid, ts));
|
|
173
|
+
return createAuthToken(address, leaseUuid, ts, pub_key.value, signature);
|
|
174
|
+
};
|
|
175
|
+
const getLeaseDataAuthToken = async (address, leaseUuid, metaHashHex) => {
|
|
176
|
+
const ts = await timestamps.next();
|
|
177
|
+
const { pub_key, signature } = await signArbitrary(address, createLeaseDataSignMessage(leaseUuid, metaHashHex, ts));
|
|
178
|
+
return createAuthToken(address, leaseUuid, ts, pub_key.value, signature, metaHashHex);
|
|
179
|
+
};
|
|
180
|
+
callbacks.onProgress?.({ kind: "deploy_app_broadcast" });
|
|
181
|
+
const fredInput = buildFredDeployInput(confirmedSpec, requestedSize(confirmedSpec));
|
|
182
|
+
let fredResult;
|
|
183
|
+
try {
|
|
184
|
+
fredResult = await deployApp$1(opts.clientManager, getAuthToken, getLeaseDataAuthToken, fredInput, opts.fetchFn);
|
|
185
|
+
} catch (err) {
|
|
186
|
+
return await handleBroadcastFailure(err, confirmedSpec, callbacks, opts);
|
|
187
|
+
}
|
|
188
|
+
const classification = classifyDeployResponse(fredResult);
|
|
189
|
+
callbacks.onProgress?.({
|
|
190
|
+
kind: "deploy_response_classified",
|
|
191
|
+
outcome: classification.outcome
|
|
192
|
+
});
|
|
193
|
+
if (classification.outcome !== "active") {
|
|
194
|
+
const detail = classification.errorSummary ?? (classification.stateName !== void 0 ? `Lease ${classification.leaseUuid ?? "<no-uuid>"} returned non-active state ${classification.stateName}` : `fred deployApp returned non-active outcome '${classification.outcome}'`);
|
|
195
|
+
throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, `${detail}. Full routing (needs_wait → wait_for_app_ready polling; failed → FailureEnvelope) deferred to ENG-185 scope item #6.`);
|
|
196
|
+
}
|
|
197
|
+
callbacks.onProgress?.({
|
|
198
|
+
kind: "app_ready_confirmed",
|
|
199
|
+
leaseUuid: fredResult.lease_uuid
|
|
200
|
+
});
|
|
201
|
+
const persistedPath = await tryPersistManifest({
|
|
202
|
+
leaseUuid: fredResult.lease_uuid,
|
|
203
|
+
image: primaryImage(confirmedSpec),
|
|
204
|
+
size: requestedSize(confirmedSpec),
|
|
205
|
+
metaHash: preview.meta_hash_hex,
|
|
206
|
+
chainId,
|
|
207
|
+
manifestJson: preview.manifest_json,
|
|
208
|
+
customDomain: fredResult.custom_domain,
|
|
209
|
+
customDomainService: fredResult.service_name,
|
|
210
|
+
dataDir: opts.dataDir,
|
|
211
|
+
callbacks
|
|
212
|
+
});
|
|
213
|
+
let leaseStateDecoded;
|
|
214
|
+
if (fredResult.state === void 0) leaseStateDecoded = "LEASE_STATE_ACTIVE";
|
|
215
|
+
else {
|
|
216
|
+
const decoded = decode(fredResult.state);
|
|
217
|
+
if (decoded === void 0) throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, `Unrecognized lease state from fred deployApp response: ${String(fredResult.state)}. Cannot safely classify; refusing to silently coerce to ACTIVE.`);
|
|
218
|
+
leaseStateDecoded = decoded;
|
|
219
|
+
}
|
|
220
|
+
const endpointUrls = extractRunningEndpoints(fredResult.connection).map(formatEndpointAsUrl);
|
|
221
|
+
const fallbackUrl = typeof fredResult.url === "string" ? normalizeFredUrl(fredResult.url) : "";
|
|
222
|
+
const result = {
|
|
223
|
+
leaseUuid: fredResult.lease_uuid,
|
|
224
|
+
providerUuid: fredResult.provider_uuid,
|
|
225
|
+
leaseState: leaseStateDecoded,
|
|
226
|
+
urls: endpointUrls.length > 0 ? endpointUrls : fallbackUrl.length > 0 ? [fallbackUrl] : [],
|
|
227
|
+
...fredResult.custom_domain ? { customDomain: fredResult.custom_domain } : {},
|
|
228
|
+
manifestPath: persistedPath ?? ""
|
|
229
|
+
};
|
|
230
|
+
callbacks.onProgress?.({
|
|
231
|
+
kind: "success_rendered",
|
|
232
|
+
result
|
|
233
|
+
});
|
|
234
|
+
callbacks.onComplete?.(result);
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
function primaryImage(spec) {
|
|
238
|
+
if (isStackSpec(spec)) {
|
|
239
|
+
for (const svc of Object.values(spec.services)) if (svc?.image) return svc.image;
|
|
240
|
+
return "";
|
|
241
|
+
}
|
|
242
|
+
return spec.image ?? "";
|
|
243
|
+
}
|
|
244
|
+
function requestedSize(spec) {
|
|
245
|
+
const recorded = spec.size;
|
|
246
|
+
return typeof recorded === "string" && recorded.length > 0 ? recorded : "small";
|
|
247
|
+
}
|
|
248
|
+
function customDomainOf(spec) {
|
|
249
|
+
return spec.customDomain;
|
|
250
|
+
}
|
|
251
|
+
function customDomainServiceOf(spec) {
|
|
252
|
+
if (isStackSpec(spec)) return spec.serviceName;
|
|
253
|
+
}
|
|
254
|
+
function evaluateReadinessFromRaw(raw, gasPrice, denomMap) {
|
|
255
|
+
const rawAny = raw;
|
|
256
|
+
return {
|
|
257
|
+
status: "ok",
|
|
258
|
+
reasons: [],
|
|
259
|
+
suggestedActions: [],
|
|
260
|
+
walletBalances: rawAny.wallet_balances ?? [],
|
|
261
|
+
credits: rawAny.credits ?? null,
|
|
262
|
+
sku: rawAny.sku ?? null
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function buildManifestPreviewInput(spec, size) {
|
|
266
|
+
if (isStackSpec(spec)) return {
|
|
267
|
+
size,
|
|
268
|
+
services: spec.services
|
|
269
|
+
};
|
|
270
|
+
const single = spec;
|
|
271
|
+
return {
|
|
272
|
+
size,
|
|
273
|
+
image: single.image,
|
|
274
|
+
port: typeof single.port === "number" ? single.port : Array.isArray(single.port) ? single.port[0] : void 0,
|
|
275
|
+
env: single.env
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
async function estimateFees(opts, spec, metaHashHex) {
|
|
279
|
+
const size = requestedSize(spec);
|
|
280
|
+
const { skuUuid } = await findSkuUuid(opts.clientManager, size);
|
|
281
|
+
const itemArg = isStackSpec(spec) && spec.serviceName ? `${skuUuid}:1:${spec.serviceName}` : `${skuUuid}:1`;
|
|
282
|
+
let createLeaseEstimate;
|
|
283
|
+
try {
|
|
284
|
+
createLeaseEstimate = await cosmosEstimateFee(opts.clientManager, "billing", "create-lease", [
|
|
285
|
+
"--meta-hash",
|
|
286
|
+
metaHashHex,
|
|
287
|
+
itemArg
|
|
288
|
+
]);
|
|
289
|
+
} catch (err) {
|
|
290
|
+
const msg = `Failed to estimate create-lease fee: ${err instanceof Error ? err.message : String(err)}`;
|
|
291
|
+
if (err instanceof ManifestMCPError) throw new ManifestMCPError(err.code, msg);
|
|
292
|
+
throw new ManifestMCPError(ManifestMCPErrorCode.SIMULATION_FAILED, msg);
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
createLease: {
|
|
296
|
+
coins: createLeaseEstimate.fee.amount.map((c) => ({
|
|
297
|
+
denom: c.denom,
|
|
298
|
+
amount: c.amount
|
|
299
|
+
})),
|
|
300
|
+
gas: Number(createLeaseEstimate.fee.gas)
|
|
301
|
+
},
|
|
302
|
+
...typeof customDomainOf(spec) === "string" ? { setDomain: {
|
|
303
|
+
notEstimated: true,
|
|
304
|
+
reason: "set-domain fee skipped — pre-broadcast lease UUID unavailable; full approach-3 fallback deferred to PR-3.x"
|
|
305
|
+
} } : {}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function buildFredDeployInput(spec, size) {
|
|
309
|
+
const base = { size };
|
|
310
|
+
if (isStackSpec(spec)) base.services = spec.services;
|
|
311
|
+
else {
|
|
312
|
+
const single = spec;
|
|
313
|
+
base.image = single.image;
|
|
314
|
+
base.port = typeof single.port === "number" ? single.port : Array.isArray(single.port) ? single.port[0] : void 0;
|
|
315
|
+
base.env = single.env;
|
|
316
|
+
}
|
|
317
|
+
const customDomain = spec.customDomain;
|
|
318
|
+
if (customDomain) {
|
|
319
|
+
base.customDomain = customDomain;
|
|
320
|
+
const svcName = customDomainServiceOf(spec);
|
|
321
|
+
if (svcName) base.serviceName = svcName;
|
|
322
|
+
}
|
|
323
|
+
return base;
|
|
324
|
+
}
|
|
325
|
+
function applyPlanEdit(spec, edit) {
|
|
326
|
+
if (edit.kind === "replace_spec") return edit.spec;
|
|
327
|
+
if (edit.kind === "edit_env") {
|
|
328
|
+
if (isStackSpec(spec)) {
|
|
329
|
+
if (edit.service === void 0) throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, "applyPlanEdit: edit_env on a stack spec requires `service` identifying which service to edit.");
|
|
330
|
+
if (!Object.keys(spec.services).includes(edit.service)) throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, `applyPlanEdit: edit_env \`service\` "${edit.service}" is not a key in \`services\` (got: [${Object.keys(spec.services).join(", ")}]).`);
|
|
331
|
+
const svc = spec.services[edit.service];
|
|
332
|
+
return {
|
|
333
|
+
...spec,
|
|
334
|
+
services: {
|
|
335
|
+
...spec.services,
|
|
336
|
+
[edit.service]: {
|
|
337
|
+
...svc,
|
|
338
|
+
env: {
|
|
339
|
+
...svc.env ?? {},
|
|
340
|
+
...edit.env
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
const single = spec;
|
|
347
|
+
return {
|
|
348
|
+
...single,
|
|
349
|
+
env: {
|
|
350
|
+
...single.env ?? {},
|
|
351
|
+
...edit.env
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return spec;
|
|
356
|
+
}
|
|
357
|
+
async function handleBroadcastFailure(err, spec, callbacks, opts) {
|
|
358
|
+
const requestedCustomDomain = customDomainOf(spec);
|
|
359
|
+
const classified = classifyDeployError(err, { ...requestedCustomDomain ? { expectedCustomDomain: requestedCustomDomain } : {} });
|
|
360
|
+
if (classified.outcome === "partially_succeeded" && classified.leaseUuid) {
|
|
361
|
+
const envelope = {
|
|
362
|
+
outcome: "partially_succeeded",
|
|
363
|
+
leaseUuid: classified.leaseUuid,
|
|
364
|
+
...requestedCustomDomain ? { requestedCustomDomain } : {},
|
|
365
|
+
reason: classified.reason
|
|
366
|
+
};
|
|
367
|
+
const options = renderPartialSuccessPrompt({
|
|
368
|
+
leaseUuid: classified.leaseUuid,
|
|
369
|
+
decodedState: "LEASE_STATE_PENDING",
|
|
370
|
+
reason: classified.reason,
|
|
371
|
+
...requestedCustomDomain ? { requestedCustomDomain } : {}
|
|
372
|
+
}).options.map((id) => ({
|
|
373
|
+
id,
|
|
374
|
+
label: recoveryOptionLabel(id),
|
|
375
|
+
description: recoveryOptionDescription(id)
|
|
376
|
+
}));
|
|
377
|
+
if (options.length > 0 && callbacks.onFailure !== void 0) return await dispatchRecovery(await callbacks.onFailure(envelope, options), envelope, spec, opts);
|
|
378
|
+
throw new ManifestMCPError(ManifestMCPErrorCode.TX_FAILED, classified.reason);
|
|
379
|
+
}
|
|
380
|
+
classified.reason;
|
|
381
|
+
throw new ManifestMCPError(ManifestMCPErrorCode.TX_FAILED, classified.reason);
|
|
382
|
+
}
|
|
383
|
+
async function dispatchRecovery(choice, envelope, spec, opts) {
|
|
384
|
+
const leaseUuid = envelope.outcome === "partially_succeeded" ? envelope.leaseUuid : "";
|
|
385
|
+
switch (choice.id) {
|
|
386
|
+
case "retry_set_domain": {
|
|
387
|
+
const domain = customDomainOf(spec);
|
|
388
|
+
if (!domain) throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, "retry_set_domain requires a customDomain in spec.");
|
|
389
|
+
const serviceName = customDomainServiceOf(spec);
|
|
390
|
+
const setItemOpts = serviceName ? { serviceName } : void 0;
|
|
391
|
+
await setItemCustomDomain(opts.clientManager, leaseUuid, domain, setItemOpts);
|
|
392
|
+
throw new ManifestMCPError(ManifestMCPErrorCode.TX_FAILED, `retry_set_domain completed for ${leaseUuid}; caller should re-run troubleshootDeployment to confirm app readiness.`);
|
|
393
|
+
}
|
|
394
|
+
case "salvage_without_domain": throw new ManifestMCPError(ManifestMCPErrorCode.TX_FAILED, `salvage_without_domain: lease ${leaseUuid} retained without domain; caller should re-run troubleshootDeployment.`);
|
|
395
|
+
case "cancel_lease":
|
|
396
|
+
case "close_lease":
|
|
397
|
+
await stopApp(opts.clientManager, leaseUuid);
|
|
398
|
+
throw new ManifestMCPError(ManifestMCPErrorCode.TX_FAILED, `${choice.id}: lease ${leaseUuid} closed.`);
|
|
399
|
+
}
|
|
400
|
+
throw new ManifestMCPError(ManifestMCPErrorCode.TX_FAILED, `Unknown recovery option: ${choice.id}`);
|
|
401
|
+
}
|
|
402
|
+
function recoveryOptionLabel(id) {
|
|
403
|
+
switch (id) {
|
|
404
|
+
case "retry_set_domain": return "Retry set-domain + upload";
|
|
405
|
+
case "salvage_without_domain": return "Salvage without domain";
|
|
406
|
+
case "cancel_lease": return "Cancel the lease";
|
|
407
|
+
case "close_lease": return "Cancel or close the lease";
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function recoveryOptionDescription(id) {
|
|
411
|
+
switch (id) {
|
|
412
|
+
case "retry_set_domain": return "Retry the set-domain transaction against the already-created lease.";
|
|
413
|
+
case "salvage_without_domain": return "Keep the lease without the requested custom domain.";
|
|
414
|
+
case "cancel_lease": return "Submit a cancel-lease transaction (pre-active terminal).";
|
|
415
|
+
case "close_lease": return "Submit a close-lease transaction (post-active or pre-active terminal).";
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
async function tryPersistManifest(args) {
|
|
419
|
+
if (!args.dataDir) return void 0;
|
|
420
|
+
try {
|
|
421
|
+
const { saveManifest } = await import("./internals/save-manifest.js");
|
|
422
|
+
const result = await saveManifest({
|
|
423
|
+
leaseUuid: args.leaseUuid,
|
|
424
|
+
image: args.image,
|
|
425
|
+
size: args.size,
|
|
426
|
+
metaHash: args.metaHash,
|
|
427
|
+
chainId: args.chainId,
|
|
428
|
+
manifestJson: args.manifestJson,
|
|
429
|
+
dataDir: args.dataDir,
|
|
430
|
+
...args.customDomain ? { customDomain: args.customDomain } : {},
|
|
431
|
+
...args.customDomainService ? { customDomainServiceName: args.customDomainService } : {}
|
|
432
|
+
});
|
|
433
|
+
args.callbacks.onProgress?.({
|
|
434
|
+
kind: "manifest_saved",
|
|
435
|
+
leaseUuid: args.leaseUuid,
|
|
436
|
+
manifestPath: result.manifestPath
|
|
437
|
+
});
|
|
438
|
+
return result.manifestPath;
|
|
439
|
+
} catch {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
//#endregion
|
|
444
|
+
export { deployApp };
|
|
445
|
+
|
|
446
|
+
//# sourceMappingURL=deploy-app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy-app.js","names":["fredDeployApp","decodeLeaseState"],"sources":["../src/deploy-app.ts"],"sourcesContent":["/**\n * Public entry point: orchestrate a Manifest-Network app deployment from\n * a typed `DeploySpec` through the plan/confirm/broadcast/save flow.\n *\n * Architect's α-locked composition (post-PR-3 sub-plan Q1):\n *\n * Happy path: `fredDeployApp` (workspace MCP-tool function) is called\n * atomically for create-lease + manifest upload + (optional) set-\n * item-custom-domain. agent-core wraps the call with planning, user\n * confirmation, progress events, and post-success persistence.\n *\n * Recovery path: when fred's atomic deployApp throws or the lease\n * reaches a non-recoverable state, agent-core renders a recovery\n * prompt (typed `RecoveryOption[]`), invokes `onFailure`, and\n * dispatches the user's `RecoveryChoice` to inline closures that\n * call core's decomposed primitives (`setItemCustomDomain` for\n * `retry_set_domain`; `stopApp` for `close_lease`).\n *\n * E-hybrid runtime-context (post-PR-3 sub-plan Q5):\n *\n * `opts: DeployAppOptions` carries `clientManager` (chain ops),\n * `walletProvider` (ADR-036 auth-token construction), optional\n * `fetchFn` (HTTP override for fred's upload), and the chain-data /\n * denomMap injection for humanization. agent-core composes the\n * auth-token callbacks internally from `walletProvider` so callers\n * don't need to know about ADR-036 plumbing.\n *\n * Auth-callback construction follows fred's `AuthTokenService` pattern\n * (verified against `packages/fred/src/http/auth.ts` per TL2.1 silent-\n * fix discipline):\n *\n * 1. `timestamps.next()` → monotonic replay-safe timestamp.\n * 2. `createSignMessage(address, leaseUuid, timestamp)` → message.\n * 3. `walletProvider.signArbitrary(address, message)` → `{ pub_key,\n * signature }` (cosmjs convention; `pub_key.value` is base64).\n * 4. `createAuthToken(address, leaseUuid, timestamp, pub_key.value,\n * signature[, metaHashHex])` → token string.\n */\n\nimport {\n cosmosEstimateFee,\n ManifestMCPError,\n ManifestMCPErrorCode,\n setItemCustomDomain,\n stopApp,\n} from '@manifest-network/manifest-mcp-core';\nimport {\n AuthTimestampTracker,\n type BuildManifestPreviewInput,\n buildManifestPreview,\n type CheckDeploymentReadinessResult,\n checkDeploymentReadiness,\n createAuthToken,\n createLeaseDataSignMessage,\n createSignMessage,\n type DeployAppInput as FredDeployAppInput,\n type DeployAppResult as FredDeployAppResult,\n deployApp as fredDeployApp,\n} from '@manifest-network/manifest-mcp-fred';\nimport { classifyDeployError } from './internals/classify-deploy-error.js';\nimport { classifyDeployResponse } from './internals/classify-deploy-response.js';\nimport {\n extractRunningEndpoints,\n formatEndpointAsUrl,\n normalizeFredUrl,\n} from './internals/connection.js';\nimport { findSkuUuid } from './internals/find-sku-uuid.js';\nimport {\n EMPTY_DENOM_MAP,\n loadChainDenomMap,\n} from './internals/humanize-denom.js';\nimport { decode as decodeLeaseState } from './internals/lease-state.js';\nimport { renderDeploymentPlan } from './internals/render-deployment-plan.js';\nimport { renderIntentRecap } from './internals/render-intent-recap.js';\nimport { renderPartialSuccessPrompt } from './internals/render-partial-success-prompt.js';\nimport {\n isStackSpec,\n summarizeSpec,\n validateSpec,\n} from './internals/spec-normalize.js';\nimport type {\n DenomMap,\n DeployAppCallbacks,\n DeployAppOptions,\n DeployResult,\n DeploySpec,\n FailureEnvelope,\n FeeEstimate,\n LeaseStateName,\n Plan,\n Readiness,\n RecoveryChoice,\n RecoveryOption,\n RecoveryOptionId,\n ServiceDef,\n SingleServiceSpec,\n StackSpec,\n} from './types.js';\n\n/**\n * Orchestrate a deployment. See module-level docstring for the architect-\n * locked composition + E-hybrid runtime-context contract.\n *\n * @throws `ManifestMCPError(INVALID_CONFIG)` for spec / wallet validation.\n * @throws `ManifestMCPError(INVALID_CONFIG)` when `onConfirm` returns\n * `'no'` or `onPlan` returns `'cancel'`.\n *\n * Errors from fred's broadcast or core's recovery primitives surface as\n * typed `ManifestMCPError`s. Partial-success failures with applicable\n * recovery options route through `onFailure(envelope, options)` — the\n * callback's return value drives recovery dispatch via the inline\n * closures in `dispatchRecovery`. Non-partial or inform-only failures\n * (no recovery choices to present, per `handleBroadcastFailure`'s F3\n * branch) throw directly as `ManifestMCPError(TX_FAILED)` without\n * invoking `onFailure`.\n */\nexport async function deployApp(\n spec: DeploySpec,\n callbacks: DeployAppCallbacks,\n opts: DeployAppOptions,\n): Promise<DeployResult> {\n // --- Input validation -----------------------------------------------\n try {\n validateSpec(spec);\n } catch (err) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n err instanceof Error ? err.message : `Invalid spec: ${String(err)}`,\n );\n }\n if (typeof opts.walletProvider.signArbitrary !== 'function') {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'opts.walletProvider must implement signArbitrary for ADR-036 auth tokens.',\n );\n }\n\n // --- Address-source consistency guard -------------------------------\n // Copilot review fix (PR #58 r3248900328): `opts.walletProvider` and\n // `opts.clientManager` are independently-injected runtime objects.\n // The readiness check + ADR-036 auth-token signing read the address\n // from `walletProvider`; fred's atomic `deployApp` (create-lease +\n // manifest upload) reads it from `clientManager`. If the two are\n // bound to different wallets (misconfiguration / copy-paste in\n // host-surface composition / multi-tenant test rig), readiness is\n // evaluated for wallet A while create-lease + upload execute as\n // wallet B — orphaning a lease on wallet B with auth tokens signed\n // by wallet A (provider auth-fails after the chain tx confirms).\n // Resolve both up-front, fail fast on mismatch, then reuse the\n // single value as the canonical `tenantAddress` for the rest of\n // the orchestration.\n const walletAddress = await opts.walletProvider.getAddress();\n const clientAddress = await opts.clientManager.getAddress();\n if (walletAddress !== clientAddress) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `opts.walletProvider and opts.clientManager are bound to different addresses ` +\n `(walletProvider=${walletAddress}, clientManager=${clientAddress}); they must reference the same wallet ` +\n `to avoid creating an orphaned lease on the clientManager wallet when ADR-036 auth (signed by walletProvider) fails.`,\n );\n }\n const tenantAddress = walletAddress;\n\n // --- Resolve denom map for humanization -----------------------------\n // I/O at orchestrator boundary (Path-Bii principle): callers may\n // pre-load via `denomMap`, point at `chainDataFile`, or omit both\n // (no-op map → raw on-chain denom rendering downstream).\n const denomMap: DenomMap =\n opts.denomMap ??\n (opts.chainDataFile\n ? await loadChainDenomMap(opts.chainDataFile)\n : EMPTY_DENOM_MAP);\n\n // --- Active-chain detection -----------------------------------------\n // The active chain (testnet / mainnet) drives intent-recap's mainnet\n // warning + parts of the Plan rendering. CosmosClientManager exposes\n // the bound chainId; we map to the canonical user-facing name.\n const chainId = opts.clientManager.getConfig().chainId;\n const activeChain: 'testnet' | 'mainnet' = /mainnet|main/i.test(chainId)\n ? 'mainnet'\n : 'testnet';\n\n // --- Readiness evaluation -------------------------------------------\n // fred's checkDeploymentReadiness takes (queryClient, address, input).\n // `tenantAddress` was resolved + validated as consistent across\n // walletProvider/clientManager in the address-source guard above.\n const queryClient = await opts.clientManager.getQueryClient();\n const readinessRaw = await checkDeploymentReadiness(\n queryClient,\n tenantAddress,\n {\n image: primaryImage(spec),\n size: requestedSize(spec),\n },\n );\n // `readiness` is `let`-bound because the post-edit recompute branch\n // re-evaluates it against the edited spec (Copilot r3267373084 — see\n // the recall block inside the `onPlan` `verdict !== 'confirm'` arm).\n let readiness: Readiness = evaluateReadinessFromRaw(\n readinessRaw,\n opts.clientManager.getConfig().gasPrice ?? '1umfx',\n denomMap,\n );\n callbacks.onProgress?.({ kind: 'readiness_evaluated', readiness });\n if (readiness.status === 'block') {\n const envelope: FailureEnvelope = {\n outcome: 'failed',\n reason: `Readiness check failed: ${readiness.reasons.join('; ')}`,\n };\n // F3 fix: align with verify-recover's pattern — inform-only branch\n // (no recovery choices available) throws directly without calling\n // onFailure. Caller surfaces the error via the thrown\n // ManifestMCPError; no choice to present.\n void envelope;\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Readiness check failed: ${readiness.reasons.join('; ')}`,\n );\n }\n\n // --- Plan assembly --------------------------------------------------\n // Build manifest preview (provides meta_hash for Plan + later save).\n // These are `let`-bound because the onPlan callback may return a\n // PlanEdit that triggers a re-plan (C2 fix below — single-iteration\n // plan-edit must recompute preview/summary/fees/block against the\n // edited spec; otherwise the manifest persistence at step 16 uses\n // the stale pre-edit preview).\n let preview = await buildManifestPreview(\n buildManifestPreviewInput(spec, requestedSize(spec)),\n );\n\n // Fee estimation for create-lease (always) + set-item-custom-domain\n // (when customDomain set). Lean port: cosmosEstimateFee invocation\n // details encapsulated in a helper to keep this fn focused on flow.\n let summary = summarizeSpec(spec);\n let fees = await estimateFees(opts, spec, preview.meta_hash_hex);\n let plan: Plan = { summary, readiness, fees };\n\n // --- Render plan + onPlan callback ----------------------------------\n let confirmedSpec = spec;\n const block = renderDeploymentPlan({\n plan,\n denomMap,\n image: primaryImage(spec),\n size: requestedSize(spec),\n metaHash: preview.meta_hash_hex,\n customDomain: customDomainOf(spec),\n customDomainService: customDomainServiceOf(spec),\n });\n callbacks.onProgress?.({ kind: 'deployment_plan_rendered', block });\n if (callbacks.onPlan) {\n const verdict = await callbacks.onPlan(plan);\n if (verdict === 'cancel') {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'User cancelled deployment at plan step.',\n );\n }\n if (verdict !== 'confirm') {\n // PlanEdit — apply edits to the spec, then re-plan against the\n // edited spec so downstream consumers (intent recap, fred input,\n // manifest persistence) all see the post-edit values.\n //\n // C2 fix (post-edit propagation gap): the prior single-iteration\n // implementation updated `confirmedSpec` but kept `preview` /\n // `summary` / `fees` / `plan` based on the original spec, which\n // caused the manifest persistence at step 16 to record the stale\n // pre-edit `meta_hash_hex` / `manifest_json` while fred's\n // deployApp broadcast used the edited spec — a real mismatch.\n // Re-planning closes the gap. Multi-iteration plan-edit (loop\n // back to onPlan with the new plan) remains a PR-3.x follow-up;\n // this fix addresses single-iteration freshness only.\n confirmedSpec = applyPlanEdit(confirmedSpec, verdict);\n // Copilot review fix (PR #58 r3249684686): re-validate the post-\n // edit spec at the agent-core boundary. `validateSpec` runs once\n // on the original input at the top of `deployApp`; without this\n // second invocation a `replace_spec` edit returning an invalid\n // spec (portless single-service, out-of-range port, stack-\n // without-services, stack-with-customDomain-missing-serviceName,\n // etc.) flows through to `buildManifestPreview` / fred's\n // broadcast and surfaces only as a mid-orchestration error.\n // Placed BEFORE the recompute so we don't spend a\n // `buildManifestPreview` round-trip on a known-bad spec. Wraps\n // `TypeError` from `validateSpec` into `INVALID_CONFIG` to match\n // the initial-input-validation convention at the top of this fn.\n try {\n validateSpec(confirmedSpec);\n } catch (err) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n err instanceof Error\n ? `Post-edit spec failed validation: ${err.message}`\n : `Post-edit spec failed validation: ${String(err)}`,\n );\n }\n // Copilot review fix (PR #58 r3267373084): readiness recall.\n // The original-spec `readiness` (captured pre-`onPlan`) gates\n // SKU + credit-balance pre-flight; a `replace_spec` /\n // `edit_env` edit that changes `image` or `size` can produce a\n // different readiness outcome. Without this recall, the\n // post-edit `plan` still carries the original-spec readiness,\n // which mis-renders the plan and may bypass a `status: 'block'`\n // condition specific to the edited shape.\n //\n // ENG-185 #1 note: `evaluateReadinessFromRaw` is still the\n // stub returning `'ok'` for all callers today (tracked in\n // ENG-185 scope item #1). This recall wires the STRUCTURAL\n // path now so when #1 replaces the stub, the post-edit\n // block-short-circuit fires correctly across both paths\n // (original-spec readiness + edited-spec readiness).\n const editedReadinessRaw = await checkDeploymentReadiness(\n queryClient,\n tenantAddress,\n {\n image: primaryImage(confirmedSpec),\n size: requestedSize(confirmedSpec),\n },\n );\n readiness = evaluateReadinessFromRaw(\n editedReadinessRaw,\n opts.clientManager.getConfig().gasPrice ?? '1umfx',\n denomMap,\n );\n callbacks.onProgress?.({ kind: 'readiness_evaluated', readiness });\n if (readiness.status === 'block') {\n // Same fail-fast as the original-spec readiness gate above.\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Post-edit readiness check failed: ${readiness.reasons.join('; ')}`,\n );\n }\n preview = await buildManifestPreview(\n buildManifestPreviewInput(confirmedSpec, requestedSize(confirmedSpec)),\n );\n summary = summarizeSpec(confirmedSpec);\n fees = await estimateFees(opts, confirmedSpec, preview.meta_hash_hex);\n plan = { summary, readiness, fees };\n // Copilot review fix (PR #58 r3237308843): the pre-edit\n // `deployment_plan_rendered` event already fired with the original\n // spec's block. After applying the edit + recomputing preview /\n // summary / fees / plan, re-render and emit a fresh block so\n // consumers see the post-edit plan alongside the post-edit intent\n // recap. Without this re-emit, the event stream is inconsistent\n // with the user's confirmation surface and the persisted manifest.\n const editedBlock = renderDeploymentPlan({\n plan,\n denomMap,\n image: primaryImage(confirmedSpec),\n size: requestedSize(confirmedSpec),\n metaHash: preview.meta_hash_hex,\n customDomain: customDomainOf(confirmedSpec),\n customDomainService: customDomainServiceOf(confirmedSpec),\n });\n callbacks.onProgress?.({\n kind: 'deployment_plan_rendered',\n block: editedBlock,\n });\n }\n }\n\n // --- Intent recap + onConfirm callback ------------------------------\n const recapText = renderIntentRecap({ spec: confirmedSpec, activeChain });\n const recapBlock = { text: recapText };\n if (callbacks.onConfirm) {\n const yesNo = await callbacks.onConfirm(recapBlock);\n if (yesNo !== 'yes') {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'User declined to proceed at intent-recap step.',\n );\n }\n }\n callbacks.onProgress?.({ kind: 'user_confirmed' });\n\n // --- Compose ADR-036 auth callbacks (E-hybrid: agent-core internalizes) ---\n const signArbitrary = opts.walletProvider.signArbitrary.bind(\n opts.walletProvider,\n );\n const timestamps = new AuthTimestampTracker();\n const getAuthToken = async (\n address: string,\n leaseUuid: string,\n ): Promise<string> => {\n const ts = await timestamps.next();\n const message = createSignMessage(address, leaseUuid, ts);\n const { pub_key, signature } = await signArbitrary(address, message);\n return createAuthToken(address, leaseUuid, ts, pub_key.value, signature);\n };\n const getLeaseDataAuthToken = async (\n address: string,\n leaseUuid: string,\n metaHashHex: string,\n ): Promise<string> => {\n const ts = await timestamps.next();\n const message = createLeaseDataSignMessage(leaseUuid, metaHashHex, ts);\n const { pub_key, signature } = await signArbitrary(address, message);\n return createAuthToken(\n address,\n leaseUuid,\n ts,\n pub_key.value,\n signature,\n metaHashHex,\n );\n };\n\n // --- Broadcast: fred's atomic deployApp (architect α-locked) -------\n callbacks.onProgress?.({ kind: 'deploy_app_broadcast' });\n const fredInput = buildFredDeployInput(\n confirmedSpec,\n requestedSize(confirmedSpec),\n );\n let fredResult: FredDeployAppResult;\n try {\n fredResult = await fredDeployApp(\n opts.clientManager,\n getAuthToken,\n getLeaseDataAuthToken,\n fredInput,\n opts.fetchFn,\n );\n } catch (err) {\n return await handleBroadcastFailure(err, confirmedSpec, callbacks, opts);\n }\n\n // --- Classify happy-path result ------------------------------------\n // Copilot review fix (PR #58 r3237308914): use the canonical\n // `classifyDeployResponse` rather than hardcoding `'active'`. The\n // classifier inspects `state`, `connection`, and `lease_uuid` to bucket\n // returns into `active | needs_wait | failed`.\n //\n // Architect's α-lock: fred returns after tx + manifest upload succeed,\n // NOT after the app is observably running. So `'needs_wait'` IS an\n // expected happy-path return shape (lease created, manifest uploaded,\n // container not yet started by the provider) — and `'failed'` covers\n // the terminal-state-on-return edge (e.g. REJECTED, when the chain\n // invalidated the lease between create and return).\n //\n // PR-3 scope (this commit) implements the **assertion form** only —\n // call the classifier and throw INVALID_CONFIG on any non-active\n // outcome with the classifier's `errorSummary` / `stateName`. This\n // removes the misleading hardcoded `'active'` event and gives callers\n // a clear, typed signal that the outcome falls outside the active\n // happy path.\n //\n // Full routing is deferred to ENG-185 scope item #6\n // (\"Happy-path classifier integration — `classifyDeployResponse`\n // + needs_wait/failed routing\"):\n // - `'needs_wait'` → poll `wait_for_app_ready`, emit\n // `polling_for_readiness` events.\n // - `'failed'` → surface as `FailureEnvelope` through\n // `onFailure` with recovery options.\n const classification = classifyDeployResponse(fredResult);\n callbacks.onProgress?.({\n kind: 'deploy_response_classified',\n outcome: classification.outcome,\n });\n if (classification.outcome !== 'active') {\n const detail =\n classification.errorSummary ??\n (classification.stateName !== undefined\n ? `Lease ${classification.leaseUuid ?? '<no-uuid>'} returned non-active state ${classification.stateName}`\n : `fred deployApp returned non-active outcome '${classification.outcome}'`);\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `${detail}. Full routing (needs_wait → wait_for_app_ready polling; failed → FailureEnvelope) deferred to ENG-185 scope item #6.`,\n );\n }\n callbacks.onProgress?.({\n kind: 'app_ready_confirmed',\n leaseUuid: fredResult.lease_uuid,\n });\n\n // --- Persist manifest (best-effort; save-fail still emits success) -\n const persistedPath = await tryPersistManifest({\n leaseUuid: fredResult.lease_uuid,\n image: primaryImage(confirmedSpec),\n size: requestedSize(confirmedSpec),\n metaHash: preview.meta_hash_hex,\n chainId,\n manifestJson: preview.manifest_json,\n customDomain: fredResult.custom_domain,\n customDomainService: fredResult.service_name,\n dataDir: opts.dataDir,\n callbacks,\n });\n\n // --- Build typed DeployResult --------------------------------------\n // F1 fix: decode lease state via the canonical lease-state.decode()\n // (handles int + LEASE_STATE_* string + undefined paths exhaustively).\n //\n // C3 fix (defensive bias correction; checklist item #16): distinguish\n // truly-absent state (undefined → default ACTIVE per fred's contract:\n // happy-path responses without explicit state mean lease is ACTIVE)\n // from UNRECOGNIZED state (decode returned undefined for a value that\n // WAS provided → likely a terminal/unknown chain emission that must\n // NOT be silently classified as ACTIVE). For the unrecognized case,\n // throw `INVALID_CONFIG` so callers see the empirical mismatch\n // instead of consuming a misleading ACTIVE.\n let leaseStateDecoded: LeaseStateName;\n if (fredResult.state === undefined) {\n leaseStateDecoded = 'LEASE_STATE_ACTIVE';\n } else {\n const decoded = decodeLeaseState(fredResult.state);\n if (decoded === undefined) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `Unrecognized lease state from fred deployApp response: ${String(fredResult.state)}. Cannot safely classify; refusing to silently coerce to ACTIVE.`,\n );\n }\n leaseStateDecoded = decoded;\n }\n\n // F4 fix: derive `urls` from `extractRunningEndpoints(connection)` for\n // multi-FQDN dedup (matches CJS pipeline behavior). fred's\n // `result.url` is a single derived URL; the full connection payload\n // exposes the canonical instance list.\n //\n // Copilot review fix (PR #58 r3249097136): when no FQDN can be\n // extracted from `connection`, fall back to `fredResult.url` THROUGH\n // the shared `normalizeFredUrl` helper. Raw values like\n // `'app.example.com:443'` now surface as\n // `'https://app.example.com:443/'`, matching the classifier's\n // (`classify-deploy-response.ts`) and renderer's (`format-success.ts`)\n // handling. Empty / scheme-less inputs are normalized consistently.\n const endpointUrls = extractRunningEndpoints(fredResult.connection).map(\n formatEndpointAsUrl,\n );\n const fallbackUrl =\n typeof fredResult.url === 'string' ? normalizeFredUrl(fredResult.url) : '';\n const result: DeployResult = {\n leaseUuid: fredResult.lease_uuid,\n providerUuid: fredResult.provider_uuid,\n leaseState: leaseStateDecoded,\n urls:\n endpointUrls.length > 0\n ? endpointUrls\n : fallbackUrl.length > 0\n ? [fallbackUrl]\n : [],\n ...(fredResult.custom_domain\n ? { customDomain: fredResult.custom_domain }\n : {}),\n manifestPath: persistedPath ?? '',\n };\n callbacks.onProgress?.({ kind: 'success_rendered', result });\n callbacks.onComplete?.(result);\n return result;\n}\n\n// --- Helpers ---------------------------------------------------------\n\nfunction primaryImage(spec: DeploySpec): string {\n if (isStackSpec(spec)) {\n for (const svc of Object.values(spec.services)) {\n if (svc?.image) return svc.image;\n }\n return '';\n }\n return (spec as SingleServiceSpec).image ?? '';\n}\n\nfunction requestedSize(spec: DeploySpec): string {\n // PR 3: size lives outside the typed DeploySpec; the orchestrator's\n // caller normally threads it via Plan / fred input. Read from a\n // conventional `size` property if present; fall back to 'small'.\n const recorded = (spec as unknown as { size?: string }).size;\n return typeof recorded === 'string' && recorded.length > 0\n ? recorded\n : 'small';\n}\n\nfunction customDomainOf(spec: DeploySpec): string | undefined {\n return (spec as { customDomain?: string }).customDomain;\n}\n\nfunction customDomainServiceOf(spec: DeploySpec): string | undefined {\n if (isStackSpec(spec)) return (spec as StackSpec).serviceName;\n return undefined;\n}\n\nfunction evaluateReadinessFromRaw(\n raw: CheckDeploymentReadinessResult,\n gasPrice: string,\n denomMap: DenomMap,\n): Readiness {\n // Minimal mapping: PR-3-commit-B's evaluateReadiness consumer translates\n // fred's raw readiness payload to the typed Readiness shape. The full\n // mapping logic lives in `internals/evaluate-readiness.ts`; this helper\n // wraps its invocation with the raw-shape adaption.\n // Voidcoercion + denomMap pass-through; full evaluation in commit-A.\n void gasPrice;\n void denomMap;\n const rawAny = raw as unknown as Record<string, unknown>;\n return {\n status: 'ok',\n reasons: [],\n suggestedActions: [],\n walletBalances:\n (rawAny.wallet_balances as Readiness['walletBalances']) ?? [],\n credits: (rawAny.credits as Readiness['credits']) ?? null,\n sku: (rawAny.sku as Readiness['sku']) ?? null,\n };\n}\n\nfunction buildManifestPreviewInput(\n spec: DeploySpec,\n size: string,\n): BuildManifestPreviewInput {\n if (isStackSpec(spec)) {\n return {\n size,\n services: spec.services,\n } as unknown as BuildManifestPreviewInput;\n }\n const single = spec as SingleServiceSpec;\n return {\n size,\n image: single.image,\n port:\n typeof single.port === 'number'\n ? single.port\n : Array.isArray(single.port)\n ? single.port[0]\n : undefined,\n env: single.env,\n } as unknown as BuildManifestPreviewInput;\n}\n\nasync function estimateFees(\n opts: DeployAppOptions,\n spec: DeploySpec,\n metaHashHex: string, // SHA-256 hex digest of the canonical manifest JSON; threaded into create-lease estimate via the `--meta-hash` flag (mirrors fred's deploy path at packages/fred/src/tools/deployApp.ts:363)\n): Promise<Plan['fees']> {\n // PR 3 fix-3 (B-narrowed-trimmed per architect ratification):\n // - REAL cosmosEstimateFee for create-lease (criterion-blocking).\n // - SET-DOMAIN emits `{notEstimated: true, reason}` sentinel (the\n // frozen-contract escape hatch designed for pre-broadcast lease-\n // UUID unavailability per ENG-128). Real set-domain pre-broadcast\n // estimation (approach-3 fallback) is PR-3.x scope.\n\n const size = requestedSize(spec);\n const { skuUuid } = await findSkuUuid(opts.clientManager, size);\n\n // Item-arg format `sku-uuid:quantity[:service-name]` (verified per\n // Discipline V against packages/core/src/transactions/billing.ts:L102).\n // Quantity = '1' for typical single-lease deploys; optional service-\n // name applies to stack leases (custom-domain attachment context).\n const itemArg =\n isStackSpec(spec) && spec.serviceName\n ? `${skuUuid}:1:${spec.serviceName}`\n : `${skuUuid}:1`;\n\n let createLeaseEstimate: Awaited<ReturnType<typeof cosmosEstimateFee>>;\n try {\n createLeaseEstimate = await cosmosEstimateFee(\n opts.clientManager,\n 'billing',\n 'create-lease',\n ['--meta-hash', metaHashHex, itemArg],\n );\n } catch (err) {\n // Wrap the underlying failure with an agent-core-boundary message\n // for caller diagnostics. `core`'s `cosmosEstimateFee` (per\n // `packages/core/src/cosmos.ts`) throws across multiple sites with\n // different codes: `INVALID_CONFIG` for missing `gasPrice`,\n // `UNSUPPORTED_TX` for invalid module/subcommand,\n // `SIMULATION_FAILED` for actual simulation issues.\n //\n // Copilot review fix (PR #58 r3250192834): preserve the original\n // code when the underlying threw a typed `ManifestMCPError`;\n // fall back to `SIMULATION_FAILED` only for untyped failures.\n // The prior comment claimed code-preservation but the code\n // unconditionally cast to `SIMULATION_FAILED`.\n const msg = `Failed to estimate create-lease fee: ${err instanceof Error ? err.message : String(err)}`;\n if (err instanceof ManifestMCPError) {\n throw new ManifestMCPError(err.code, msg);\n }\n throw new ManifestMCPError(ManifestMCPErrorCode.SIMULATION_FAILED, msg);\n }\n\n // FeeEstimateResult shape (per packages/core/src/types.ts):\n // { module, subcommand, gasEstimate: string, fee: { gas: string, amount: Coin[] } }\n // Map to typed `FeeEstimate { coins: Coin[], gas: number }` (Path-C\n // revision per a62cfd1).\n //\n // Copilot review fix (PR #58 r3250192734): use `fee.gas` (post-\n // `gasMultiplier`), NOT `gasEstimate` (raw simulation gas). The\n // `coins` were priced at `fee.gas`; displaying `gasEstimate` shows\n // a number ~33% lower than the price reflects under the default\n // 1.5x multiplier (per CLAUDE.md `COSMOS_GAS_MULTIPLIER`), creating\n // a visible inconsistency in the rendered plan.\n const createLease: FeeEstimate = {\n coins: createLeaseEstimate.fee.amount.map((c) => ({\n denom: c.denom,\n amount: c.amount,\n })),\n gas: Number(createLeaseEstimate.fee.gas),\n };\n\n // set-domain: emit `{notEstimated: true, reason}` sentinel per\n // architect-ratified counter-proposal. The frozen-contract type\n // includes this discriminated variant precisely for pre-broadcast\n // lease-UUID unavailability (\"using the contract as designed, not\n // papering over\" per architect's framing).\n const hasDomain = typeof customDomainOf(spec) === 'string';\n return {\n createLease,\n ...(hasDomain\n ? {\n setDomain: {\n notEstimated: true,\n reason:\n 'set-domain fee skipped — pre-broadcast lease UUID unavailable; full approach-3 fallback deferred to PR-3.x',\n },\n }\n : {}),\n };\n}\n\nfunction buildFredDeployInput(\n spec: DeploySpec,\n size: string,\n): FredDeployAppInput {\n // Translate typed DeploySpec → fred's DeployAppInput. See fred's\n // tools/deployApp.ts for the full input shape.\n const base: Partial<FredDeployAppInput> = { size };\n if (isStackSpec(spec)) {\n base.services = spec.services as unknown as FredDeployAppInput['services'];\n } else {\n const single = spec as SingleServiceSpec;\n base.image = single.image;\n base.port =\n typeof single.port === 'number'\n ? single.port\n : Array.isArray(single.port)\n ? single.port[0]\n : undefined;\n base.env = single.env;\n }\n const customDomain = (spec as { customDomain?: string }).customDomain;\n if (customDomain) {\n base.customDomain = customDomain;\n const svcName = customDomainServiceOf(spec);\n if (svcName) base.serviceName = svcName;\n }\n return base as FredDeployAppInput;\n}\n\nfunction applyPlanEdit(\n spec: DeploySpec,\n edit: Exclude<\n Awaited<ReturnType<NonNullable<DeployAppCallbacks['onPlan']>>>,\n 'confirm' | 'cancel'\n >,\n): DeploySpec {\n // PR 3 single-iteration: replace_spec replaces; edit_env merges env keys\n // into the matching service (or single-service spec).\n if (edit.kind === 'replace_spec') return edit.spec;\n if (edit.kind === 'edit_env') {\n // Copilot review fix (PR #58 r3266642610): the prior implementation\n // silently no-op'd two stack-spec cases (missing `edit.service` or\n // unknown service name), returning the unchanged spec while the\n // callback caller perceived the edit as applied. Worst case: deploy\n // proceeds with wrong env vars / secrets without an error signal.\n // Fail-fast at the boundary instead, so the user's `onPlan` callback\n // gets a clear `INVALID_CONFIG` for misuse. Uses\n // `Object.keys().includes()` for the membership check — matches\n // Fix 16's cross-package symmetry with fred (avoids prototype-chain\n // bypass via `'constructor'` / `'toString'` / etc.).\n if (isStackSpec(spec)) {\n if (edit.service === undefined) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'applyPlanEdit: edit_env on a stack spec requires `service` identifying which service to edit.',\n );\n }\n if (!Object.keys(spec.services).includes(edit.service)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `applyPlanEdit: edit_env \\`service\\` \"${edit.service}\" is not a key in \\`services\\` (got: [${Object.keys(spec.services).join(', ')}]).`,\n );\n }\n const svc = spec.services[edit.service];\n // Membership check above guarantees `svc` is defined; the\n // non-null assertion documents that — TS narrows it after the\n // `includes` check, but the runtime invariant is in the\n // membership check.\n return {\n ...spec,\n services: {\n ...spec.services,\n [edit.service]: {\n ...(svc as ServiceDef),\n env: { ...((svc as ServiceDef).env ?? {}), ...edit.env },\n },\n },\n };\n }\n const single = spec as SingleServiceSpec;\n return { ...single, env: { ...(single.env ?? {}), ...edit.env } };\n }\n return spec;\n}\n\nasync function handleBroadcastFailure(\n err: unknown,\n spec: DeploySpec,\n callbacks: DeployAppCallbacks,\n opts: DeployAppOptions,\n): Promise<DeployResult> {\n const requestedCustomDomain = customDomainOf(spec);\n\n // F2 fix: classify-deploy-error.ts is the canonical classifier — it\n // anchors the `PARTIAL_PREFIX` match, supports `{ error: {...} }`\n // SDK-wrapping envelopes, and threads `expectedCustomDomain` for\n // downstream rendering. Earlier inline `parsePartialSuccess` was a\n // reduced-robustness duplicate; replaced here per QA F2.\n const classified = classifyDeployError(err, {\n ...(requestedCustomDomain\n ? { expectedCustomDomain: requestedCustomDomain }\n : {}),\n });\n\n if (classified.outcome === 'partially_succeeded' && classified.leaseUuid) {\n const envelope: FailureEnvelope = {\n outcome: 'partially_succeeded',\n leaseUuid: classified.leaseUuid,\n ...(requestedCustomDomain ? { requestedCustomDomain } : {}),\n reason: classified.reason,\n };\n // CJS-parity: the lease was just created so it's typically PENDING.\n // The classifier doesn't decode state from the error envelope (the\n // chain emits state asynchronously after the create-lease tx); the\n // user prompt's \"state: <name>\" line is informational.\n const promptPayload = renderPartialSuccessPrompt({\n leaseUuid: classified.leaseUuid,\n decodedState: 'LEASE_STATE_PENDING',\n reason: classified.reason,\n ...(requestedCustomDomain ? { requestedCustomDomain } : {}),\n });\n const options: RecoveryOption[] = promptPayload.options.map((id) => ({\n id,\n label: recoveryOptionLabel(id),\n description: recoveryOptionDescription(id),\n }));\n // F3 fix: align with verify-recover's pattern — only invoke\n // onFailure when there's a choice to present. Empty options means\n // inform-only path; we throw instead of prompting.\n if (options.length > 0 && callbacks.onFailure !== undefined) {\n const choice = await callbacks.onFailure(envelope, options);\n return await dispatchRecovery(choice, envelope, spec, opts);\n }\n throw new ManifestMCPError(\n ManifestMCPErrorCode.TX_FAILED,\n classified.reason,\n );\n }\n\n // Non-partial failure: surface as `outcome: 'failed'` envelope.\n // F3 fix: skip onFailure when options is empty (inform-only path);\n // throw directly. Caller can still surface the error via the thrown\n // ManifestMCPError if they need to react.\n const envelope: FailureEnvelope = {\n outcome: 'failed',\n reason: classified.reason,\n };\n // Intentionally NOT invoking callbacks.onFailure?.(envelope, []) here\n // per F3 — no recovery choice to present.\n void envelope; // retained for future logging hook if needed\n throw new ManifestMCPError(ManifestMCPErrorCode.TX_FAILED, classified.reason);\n}\n\nasync function dispatchRecovery(\n choice: RecoveryChoice,\n envelope: FailureEnvelope,\n spec: DeploySpec,\n opts: DeployAppOptions,\n): Promise<DeployResult> {\n // Inline closures per gate-2 verdict (no separate strategy module).\n const leaseUuid =\n envelope.outcome === 'partially_succeeded' ? envelope.leaseUuid : '';\n switch (choice.id) {\n case 'retry_set_domain': {\n const domain = customDomainOf(spec);\n if (!domain) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'retry_set_domain requires a customDomain in spec.',\n );\n }\n // C6 fix: pass `serviceName` for stack-lease specs so the\n // set-item-custom-domain tx targets the named service item, not\n // the default single-item lease. setItemCustomDomain's actual\n // signature is `(clientManager, leaseUuid, customDomain,\n // options?: { serviceName?, clear? }, overrides?)` — verified\n // per Discipline V against\n // packages/core/src/tools/setItemCustomDomain.ts.\n const serviceName = customDomainServiceOf(spec);\n const setItemOpts = serviceName ? { serviceName } : undefined;\n await setItemCustomDomain(\n opts.clientManager,\n leaseUuid,\n domain,\n setItemOpts,\n );\n throw new ManifestMCPError(\n ManifestMCPErrorCode.TX_FAILED,\n `retry_set_domain completed for ${leaseUuid}; caller should re-run troubleshootDeployment to confirm app readiness.`,\n );\n }\n case 'salvage_without_domain':\n throw new ManifestMCPError(\n ManifestMCPErrorCode.TX_FAILED,\n `salvage_without_domain: lease ${leaseUuid} retained without domain; caller should re-run troubleshootDeployment.`,\n );\n case 'cancel_lease':\n case 'close_lease': {\n await stopApp(opts.clientManager, leaseUuid);\n throw new ManifestMCPError(\n ManifestMCPErrorCode.TX_FAILED,\n `${choice.id}: lease ${leaseUuid} closed.`,\n );\n }\n }\n throw new ManifestMCPError(\n ManifestMCPErrorCode.TX_FAILED,\n `Unknown recovery option: ${(choice as RecoveryChoice).id}`,\n );\n}\n\nfunction recoveryOptionLabel(id: RecoveryOptionId): string {\n switch (id) {\n case 'retry_set_domain':\n return 'Retry set-domain + upload';\n case 'salvage_without_domain':\n return 'Salvage without domain';\n case 'cancel_lease':\n return 'Cancel the lease';\n case 'close_lease':\n return 'Cancel or close the lease';\n }\n}\n\nfunction recoveryOptionDescription(id: RecoveryOptionId): string {\n switch (id) {\n case 'retry_set_domain':\n return 'Retry the set-domain transaction against the already-created lease.';\n case 'salvage_without_domain':\n return 'Keep the lease without the requested custom domain.';\n case 'cancel_lease':\n return 'Submit a cancel-lease transaction (pre-active terminal).';\n case 'close_lease':\n return 'Submit a close-lease transaction (post-active or pre-active terminal).';\n }\n}\n\ninterface PersistArgs {\n leaseUuid: string;\n image: string;\n size: string;\n metaHash: string;\n chainId: string;\n manifestJson: string;\n customDomain?: string;\n customDomainService?: string;\n dataDir?: string;\n callbacks: DeployAppCallbacks;\n}\n\nasync function tryPersistManifest(\n args: PersistArgs,\n): Promise<string | undefined> {\n if (!args.dataDir) return undefined;\n try {\n // Dynamic import keeps save-manifest's `node:fs` dep out of the\n // platform-neutral build path until needed.\n const { saveManifest } = await import('./internals/save-manifest.js');\n const result = await saveManifest({\n leaseUuid: args.leaseUuid,\n image: args.image,\n size: args.size,\n metaHash: args.metaHash,\n chainId: args.chainId,\n manifestJson: args.manifestJson,\n dataDir: args.dataDir,\n ...(args.customDomain ? { customDomain: args.customDomain } : {}),\n ...(args.customDomainService\n ? { customDomainServiceName: args.customDomainService }\n : {}),\n });\n args.callbacks.onProgress?.({\n kind: 'manifest_saved',\n leaseUuid: args.leaseUuid,\n manifestPath: result.manifestPath,\n });\n return result.manifestPath;\n } catch {\n // Step-16 contract: save-fail still returns success but `onProgress\n // (manifest_saved)` is NOT emitted; result.manifestPath stays empty.\n return undefined;\n }\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AgentCoreRuntime, CloseLeaseArgs, CloseLeaseCallbacks, CloseLeaseOptions, CloseLeaseResult, Coin, CosmosClientManager, DenomLookup, DenomMap, DeployAppCallbacks, DeployAppOptions, DeployResult, DeploySpec, DeploymentPlanBlock, FailureEnvelope, FeeEstimate, LeaseStateName, ManageDomainArgs, ManageDomainCallbacks, ManageDomainOptions, ManageDomainResult, Plan, PlanEdit, PlanFees, ProgressEvent, Readiness, ReadinessAction, RecoveryChoice, RecoveryOption, RecoveryOptionId, ServiceDef, SingleServiceSpec, SpecSummary, StackSpec, TroubleshootArgs, TroubleshootCallbacks, TroubleshootOptions, TroubleshootReport, WalletProvider } from "./types.js";
|
|
2
|
+
import { closeLease } from "./close-lease.js";
|
|
3
|
+
import { deployApp } from "./deploy-app.js";
|
|
4
|
+
import { GuardedFetch, createGuardedFetch } from "./internals/guarded-fetch.js";
|
|
5
|
+
import { loadChainDenomMap } from "./internals/humanize-denom.js";
|
|
6
|
+
import { manageDomain } from "./manage-domain.js";
|
|
7
|
+
import { troubleshootDeployment } from "./troubleshoot.js";
|
|
8
|
+
export { AgentCoreRuntime, CloseLeaseArgs, CloseLeaseCallbacks, CloseLeaseOptions, CloseLeaseResult, Coin, CosmosClientManager, DenomLookup, DenomMap, DeployAppCallbacks, DeployAppOptions, DeployResult, DeploySpec, DeploymentPlanBlock, FailureEnvelope, FeeEstimate, type GuardedFetch, LeaseStateName, ManageDomainArgs, ManageDomainCallbacks, ManageDomainOptions, ManageDomainResult, Plan, PlanEdit, PlanFees, ProgressEvent, Readiness, ReadinessAction, RecoveryChoice, RecoveryOption, RecoveryOptionId, ServiceDef, SingleServiceSpec, SpecSummary, StackSpec, TroubleshootArgs, TroubleshootCallbacks, TroubleshootOptions, TroubleshootReport, WalletProvider, closeLease, createGuardedFetch, deployApp, loadChainDenomMap, manageDomain, troubleshootDeployment };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { closeLease } from "./close-lease.js";
|
|
2
|
+
import { loadChainDenomMap } from "./internals/humanize-denom.js";
|
|
3
|
+
import { deployApp } from "./deploy-app.js";
|
|
4
|
+
import { createGuardedFetch } from "./internals/guarded-fetch.js";
|
|
5
|
+
import { manageDomain } from "./manage-domain.js";
|
|
6
|
+
import { troubleshootDeployment } from "./troubleshoot.js";
|
|
7
|
+
export { closeLease, createGuardedFetch, deployApp, loadChainDenomMap, manageDomain, troubleshootDeployment };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//#region src/internals/classify-deploy-error.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Classify the MCP error envelope thrown by `mcp__manifest-fred__deploy_app`
|
|
4
|
+
* when the call fails AFTER the create-lease tx already confirmed.
|
|
5
|
+
*
|
|
6
|
+
* Companion to `classify-deploy-response.ts`: that file handles the RETURN
|
|
7
|
+
* path; this file handles the THROW path. The split exists because
|
|
8
|
+
* `manifest-mcp-fred` 0.8.0 `deployApp` throws `ManifestMCPError` with the
|
|
9
|
+
* message prefix `Deploy partially succeeded: lease ${uuid} was created
|
|
10
|
+
* but subsequent steps failed.` and `details.lease_uuid` populated when
|
|
11
|
+
* create-lease succeeded but something downstream (set-domain, manifest
|
|
12
|
+
* upload, readiness poll) fell over.
|
|
13
|
+
*
|
|
14
|
+
* Recognised input envelope shapes:
|
|
15
|
+
* - `{ message, details?, code? }`
|
|
16
|
+
* - `{ error: { message, details?, code? } }`
|
|
17
|
+
*
|
|
18
|
+
* Returns deterministically — never throws. A malformed envelope is
|
|
19
|
+
* classified as `outcome: 'failed'` with a stable `reason`, so the
|
|
20
|
+
* orchestrator can branch on the JSON without an outer try/catch.
|
|
21
|
+
*
|
|
22
|
+
* `outcome: 'partially_succeeded'` triggers ONLY when `err.message` starts
|
|
23
|
+
* with the exact prefix `Deploy partially succeeded:`. Looser matching
|
|
24
|
+
* would risk false positives on wrapper errors that happen to contain the
|
|
25
|
+
* phrase nested inside other text.
|
|
26
|
+
*/
|
|
27
|
+
interface DeployErrorClassification {
|
|
28
|
+
outcome: 'partially_succeeded' | 'failed';
|
|
29
|
+
/** Present when create-lease confirmed (outcome partially_succeeded), or when a UUID was extractable from a non-partial error. */
|
|
30
|
+
leaseUuid?: string;
|
|
31
|
+
/** Echoed from `opts.expectedCustomDomain` so downstream prompts can name the FQDN. */
|
|
32
|
+
requestedCustomDomain?: string;
|
|
33
|
+
/** Human-readable summary — the raw error message (or a stable placeholder if missing). */
|
|
34
|
+
reason: string;
|
|
35
|
+
}
|
|
36
|
+
declare function classifyDeployError(err: unknown, opts?: {
|
|
37
|
+
expectedCustomDomain?: string;
|
|
38
|
+
}): DeployErrorClassification;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { DeployErrorClassification, classifyDeployError };
|
|
41
|
+
//# sourceMappingURL=classify-deploy-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify-deploy-error.d.ts","names":[],"sources":["../../src/internals/classify-deploy-error.ts"],"mappings":";;AAgCA;;;;;;;;;;AAsBA;;;;;;;;;;;;;;UAtBiB,yBAAA;EACf,OAAA;;EAEA,SAAA;;EAEA,qBAAA;;EAEA,MAAA;AAAA;AAAA,iBAec,mBAAA,CACd,GAAA,WACA,IAAA;EAAQ,oBAAA;AAAA,IACP,yBAAA"}
|