@manifest-network/manifest-agent-core 0.13.1 → 0.15.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.
Files changed (77) hide show
  1. package/dist/close-lease.d.ts.map +1 -1
  2. package/dist/close-lease.js +15 -3
  3. package/dist/close-lease.js.map +1 -1
  4. package/dist/deploy-app.d.ts +2 -2
  5. package/dist/deploy-app.d.ts.map +1 -1
  6. package/dist/deploy-app.js +155 -43
  7. package/dist/deploy-app.js.map +1 -1
  8. package/dist/deploy-app.test-d.d.ts +1 -0
  9. package/dist/deploy-app.test-d.js +11 -0
  10. package/dist/deploy-app.test-d.js.map +1 -0
  11. package/dist/guarded-fetch.d.ts +2 -0
  12. package/dist/guarded-fetch.js +2 -0
  13. package/dist/index.d.ts +2 -3
  14. package/dist/index.js +1 -2
  15. package/dist/internals/cancellation.d.ts +57 -0
  16. package/dist/internals/cancellation.d.ts.map +1 -0
  17. package/dist/internals/cancellation.js +79 -0
  18. package/dist/internals/cancellation.js.map +1 -0
  19. package/dist/internals/evaluate-readiness-from-fred.d.ts.map +1 -1
  20. package/dist/internals/evaluate-readiness-from-fred.js +12 -3
  21. package/dist/internals/evaluate-readiness-from-fred.js.map +1 -1
  22. package/dist/internals/evaluate-readiness.d.ts +14 -0
  23. package/dist/internals/evaluate-readiness.d.ts.map +1 -1
  24. package/dist/internals/evaluate-readiness.js +21 -8
  25. package/dist/internals/evaluate-readiness.js.map +1 -1
  26. package/dist/internals/inspect-image.js +1 -1
  27. package/dist/internals/inspect-image.js.map +1 -1
  28. package/dist/internals/render-deployment-plan.d.ts +6 -0
  29. package/dist/internals/render-deployment-plan.d.ts.map +1 -1
  30. package/dist/internals/render-deployment-plan.js +8 -5
  31. package/dist/internals/render-deployment-plan.js.map +1 -1
  32. package/dist/internals/render-intent-recap.d.ts +13 -11
  33. package/dist/internals/render-intent-recap.d.ts.map +1 -1
  34. package/dist/internals/render-intent-recap.js +5 -4
  35. package/dist/internals/render-intent-recap.js.map +1 -1
  36. package/dist/internals/spec-normalize.d.ts +34 -27
  37. package/dist/internals/spec-normalize.d.ts.map +1 -1
  38. package/dist/internals/spec-normalize.js +28 -21
  39. package/dist/internals/spec-normalize.js.map +1 -1
  40. package/dist/manage-domain.d.ts.map +1 -1
  41. package/dist/manage-domain.js +34 -8
  42. package/dist/manage-domain.js.map +1 -1
  43. package/dist/node_modules/@vitest/pretty-format/dist/index.js +888 -0
  44. package/dist/node_modules/@vitest/pretty-format/dist/index.js.map +1 -0
  45. package/dist/node_modules/@vitest/runner/dist/chunk-artifact.js +1500 -0
  46. package/dist/node_modules/@vitest/runner/dist/chunk-artifact.js.map +1 -0
  47. package/dist/node_modules/@vitest/runner/dist/index.js +1 -0
  48. package/dist/node_modules/@vitest/runner/dist/utils.js +1 -0
  49. package/dist/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.js +82 -0
  50. package/dist/node_modules/@vitest/utils/dist/chunk-pathe.M-eThtNZ.js.map +1 -0
  51. package/dist/node_modules/@vitest/utils/dist/display.js +558 -0
  52. package/dist/node_modules/@vitest/utils/dist/display.js.map +1 -0
  53. package/dist/node_modules/@vitest/utils/dist/helpers.js +68 -0
  54. package/dist/node_modules/@vitest/utils/dist/helpers.js.map +1 -0
  55. package/dist/node_modules/@vitest/utils/dist/source-map.js +95 -0
  56. package/dist/node_modules/@vitest/utils/dist/source-map.js.map +1 -0
  57. package/dist/node_modules/@vitest/utils/dist/timers.js +20 -0
  58. package/dist/node_modules/@vitest/utils/dist/timers.js.map +1 -0
  59. package/dist/node_modules/tinyrainbow/dist/index.js +86 -0
  60. package/dist/node_modules/tinyrainbow/dist/index.js.map +1 -0
  61. package/dist/node_modules/vite/dist/node/module-runner.js +22 -0
  62. package/dist/node_modules/vite/dist/node/module-runner.js.map +1 -0
  63. package/dist/node_modules/vitest/dist/index.js +6 -0
  64. package/dist/troubleshoot.d.ts.map +1 -1
  65. package/dist/troubleshoot.js +11 -1
  66. package/dist/troubleshoot.js.map +1 -1
  67. package/dist/types.d.ts +42 -22
  68. package/dist/types.d.ts.map +1 -1
  69. package/package.json +12 -6
  70. package/dist/internals/build-fred-input.d.ts +0 -38
  71. package/dist/internals/build-fred-input.d.ts.map +0 -1
  72. package/dist/internals/build-fred-input.js +0 -147
  73. package/dist/internals/build-fred-input.js.map +0 -1
  74. package/dist/internals/find-sku-uuid.d.ts +0 -40
  75. package/dist/internals/find-sku-uuid.d.ts.map +0 -1
  76. package/dist/internals/find-sku-uuid.js +0 -20
  77. package/dist/internals/find-sku-uuid.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"close-lease.d.ts","names":[],"sources":["../src/close-lease.ts"],"mappings":";;;;AAoF2B;;;;;;;;;;;;;;;;;;;;;;;;;;iBAJL,UAAA,CACpB,IAAA,EAAM,cAAA,EACN,SAAA,EAAW,mBAAA,EACX,IAAA,EAAM,iBAAA,GACL,OAAA,CAAQ,gBAAA"}
1
+ {"version":3,"file":"close-lease.d.ts","names":[],"sources":["../src/close-lease.ts"],"mappings":";;;;AAuF2B;;;;;;;;;;;;;;;;;;;;;;;;;;iBAJL,UAAA,CACpB,IAAA,EAAM,cAAA,EACN,SAAA,EAAW,mBAAA,EACX,IAAA,EAAM,iBAAA,GACL,OAAA,CAAQ,gBAAA"}
@@ -1,6 +1,7 @@
1
+ import { makeCancellationScope } from "./internals/cancellation.js";
1
2
  import { decode, isTerminal } from "./internals/lease-state.js";
2
3
  import { verifyAndRecover } from "./internals/verify-recover.js";
3
- import { ManifestMCPError, ManifestMCPErrorCode, stopApp } from "@manifest-network/manifest-mcp-core";
4
+ import { ManifestMCPError, ManifestMCPErrorCode, noopLogger, parseLeaseUuid, stopApp } from "@manifest-network/manifest-mcp-core";
4
5
  //#region src/close-lease.ts
5
6
  /**
6
7
  * Public entry point: orchestrate closing an existing lease via the
@@ -52,12 +53,23 @@ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
52
53
  */
53
54
  async function closeLease(args, callbacks, opts) {
54
55
  validateArgs(args);
56
+ const cx = makeCancellationScope({
57
+ opts,
58
+ onProgress: callbacks.onProgress,
59
+ opLabel: "Lease close",
60
+ broadcasts: true
61
+ });
62
+ cx.throwIfCancelled();
55
63
  const block = renderConfirmationBlock(args);
56
64
  if (callbacks.onConfirm) {
57
- if (await callbacks.onConfirm(block) !== "yes") throw new ManifestMCPError(ManifestMCPErrorCode.OPERATION_CANCELLED, "User declined to proceed with close-lease.");
65
+ if (await cx.race(callbacks.onConfirm(block)) !== "yes") throw new ManifestMCPError(ManifestMCPErrorCode.OPERATION_CANCELLED, "User declined to proceed with close-lease.");
58
66
  }
59
67
  callbacks.onProgress?.({ kind: "user_confirmed" });
60
- await stopApp(opts.clientManager, args.leaseUuid);
68
+ cx.throwIfCancelled();
69
+ await stopApp({
70
+ chain: opts.clientManager,
71
+ logger: noopLogger
72
+ }, { leaseUuid: parseLeaseUuid(args.leaseUuid) });
61
73
  const verifyResult = await verifyAndRecover({
62
74
  verifier: async () => {
63
75
  let result;
@@ -1 +1 @@
1
- {"version":3,"file":"close-lease.js","names":["decodeLeaseState"],"sources":["../src/close-lease.ts"],"sourcesContent":["/**\n * Public entry point: orchestrate closing an existing lease via the\n * `close-lease` billing tx.\n *\n * Composition (mirrors `deploy-app.ts` / `manage-domain.ts`):\n *\n * 1. Validate args.\n * 2. Render a confirmation block + optionally consult `onConfirm`.\n * 3. Broadcast `stopApp` (which submits `MsgCloseLease`).\n * 4. Verify the post-broadcast on-chain state via `verifyAndRecover`\n * driving a direct `billing.v1.lease({ leaseUuid })` query +\n * `lease-state.decode` + `isTerminal`. Terminal states (CLOSED /\n * REJECTED / EXPIRED / INSUFFICIENT_FUNDS) count as success;\n * PENDING / ACTIVE map to the `pending_drift` branch; a chain\n * response with no lease (`{ lease: null }`) maps to the catch-all\n * `unclassified` branch.\n * 5. On verify-failure, invoke the simple-form `onFailure({ reason })`\n * then throw `ManifestMCPError(TX_FAILED)`. On success, emit\n * `onComplete` with the typed `CloseLeaseResult`.\n */\n\nimport {\n ManifestMCPError,\n ManifestMCPErrorCode,\n stopApp,\n} from '@manifest-network/manifest-mcp-core';\nimport {\n decode as decodeLeaseState,\n isTerminal,\n} from './internals/lease-state.js';\nimport {\n type VerificationSpec,\n verifyAndRecover,\n} from './internals/verify-recover.js';\nimport type {\n CloseLeaseArgs,\n CloseLeaseCallbacks,\n CloseLeaseOptions,\n CloseLeaseResult,\n DeploymentPlanBlock,\n LeaseStateName,\n} from './types.js';\n\nconst UUID_RE =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\ntype CloseOutcome = 'terminal' | 'pending' | 'not_found';\n\ninterface CloseDiag {\n stateName?: LeaseStateName;\n reason?: string;\n}\n\n/**\n * Close a lease and verify it reached a terminal on-chain state.\n *\n * @throws `ManifestMCPError(INVALID_CONFIG)` for args validation.\n * @throws `ManifestMCPError(OPERATION_CANCELLED)` when `onConfirm` returns\n * `'no'` (deliberate user cancellation — ENG-272).\n * @throws `ManifestMCPError` (typically `TX_FAILED`) propagated as-is\n * from the `stopApp()` broadcast step. Broadcast errors do NOT invoke\n * `onFailure` — that callback is reserved for post-broadcast\n * verification failures. `stopApp` already raises a structured\n * `ManifestMCPError` from the core package; wrapping it again at this\n * layer would be redundant. Callers wanting to react to broadcast\n * errors should catch them at the call site.\n * @throws `ManifestMCPError(TX_FAILED)` when post-broadcast verification\n * reaches one of two failure modes (both with `onFailure({ reason })`\n * invoked first):\n * - the lease is still non-terminal (`pending_drift` branch — state\n * decoded as PENDING / ACTIVE / similar non-terminal); or\n * - the chain returns `{ lease: null }` post-close, so the lease is\n * not visible on-chain (`unclassified` branch).\n * @throws `ManifestMCPError(QUERY_FAILED)` when the post-broadcast verify\n * chain query (`billing.v1.lease`) raises a non-NotFound error\n * (RPC / transport / decoding failure). Wrapped inside the verifier\n * closure so the failure flows through `onFailure({ reason })` before\n * the throw. Structured `ManifestMCPError`s raised by the chain client\n * are re-thrown as-is (with `onFailure` invoked first).\n */\nexport async function closeLease(\n args: CloseLeaseArgs,\n callbacks: CloseLeaseCallbacks,\n opts: CloseLeaseOptions,\n): Promise<CloseLeaseResult> {\n validateArgs(args);\n\n const block = renderConfirmationBlock(args);\n if (callbacks.onConfirm) {\n const yesNo = await callbacks.onConfirm(block);\n if (yesNo !== 'yes') {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.OPERATION_CANCELLED,\n 'User declined to proceed with close-lease.',\n );\n }\n }\n callbacks.onProgress?.({ kind: 'user_confirmed' });\n\n await stopApp(opts.clientManager, args.leaseUuid);\n\n // Direct single-lease query (Copilot review PR #60, comment 3275999624):\n // the previous `leasesByTenant` + page-1-only pagination would\n // false-`not_found` for tenants with >100 leases. `billing.v1.lease`\n // is the same query shape `troubleshoot.ts` already uses; it's\n // tenant-agnostic and bounded to a single lease.\n const spec: VerificationSpec<unknown, CloseOutcome, CloseDiag> = {\n verifier: async () => {\n // Wrap the chain call in try/catch (Copilot review PR #60,\n // comment 3276419264): if `billing.v1.lease` rejects (RPC down,\n // transport, structured `ManifestMCPError`), the error would\n // otherwise propagate OUT of `verifyAndRecover` and bypass the\n // post-verify `onFailure({ reason })` callback below. Mirror\n // the disambiguation pattern from `lookupDomain` (commit aaa5cc5)\n // and `troubleshootDeployment` (commit f1a4737): invoke\n // `onFailure` first, then re-throw `ManifestMCPError` as-is or\n // wrap plain errors as `QUERY_FAILED`.\n let result: unknown;\n try {\n const queryClient = await opts.clientManager.getQueryClient();\n result = await queryClient.liftedinit.billing.v1.lease({\n leaseUuid: args.leaseUuid,\n });\n } catch (err) {\n const reason = `Failed to query lease ${args.leaseUuid} during close-verify: ${\n err instanceof Error ? err.message : String(err)\n }`;\n if (callbacks.onFailure) {\n await callbacks.onFailure({ reason });\n }\n if (err instanceof ManifestMCPError) {\n throw err;\n }\n throw new ManifestMCPError(ManifestMCPErrorCode.QUERY_FAILED, reason);\n }\n const lease = (result as { lease?: unknown })?.lease;\n if (lease === null || lease === undefined) {\n return {\n outcome: 'not_found' as const,\n diagnostic: {\n reason: `lease ${args.leaseUuid} not visible on chain after close`,\n },\n };\n }\n const rawState = (lease as { state?: unknown }).state;\n const stateName = decodeLeaseState(\n typeof rawState === 'number' || typeof rawState === 'string'\n ? rawState\n : undefined,\n );\n if (stateName === undefined) {\n return {\n outcome: 'pending' as const,\n diagnostic: {\n reason: `lease ${args.leaseUuid} state could not be decoded (raw=${String(rawState)})`,\n },\n };\n }\n return {\n outcome: (isTerminal(stateName) ? 'terminal' : 'pending') as\n | 'terminal'\n | 'pending',\n diagnostic: { stateName },\n };\n },\n successValues: ['terminal'],\n branches: {\n pending: {\n branchId: 'pending_drift',\n journalActionTags: ['close-lease-verify-pending'],\n buildFailureEnvelope: (d) => ({\n outcome: 'failed',\n reason:\n d.reason ??\n `close_lease tx accepted but state is still ${d.stateName ?? 'unknown'}.`,\n }),\n buildRecoveryOptions: () => [],\n },\n not_found: {\n branchId: 'unclassified',\n journalActionTags: ['close-lease-verify-not-found'],\n buildFailureEnvelope: (d) => ({\n outcome: 'failed',\n reason:\n d.reason ??\n `Lease ${args.leaseUuid} not visible on chain after close.`,\n }),\n buildRecoveryOptions: () => [],\n },\n },\n };\n\n const verifyResult = await verifyAndRecover(spec, undefined);\n\n if (verifyResult.result !== 'success') {\n const reason =\n verifyResult.failure?.reason ?? 'close-lease verification failed.';\n if (callbacks.onFailure) {\n await callbacks.onFailure({ reason });\n }\n throw new ManifestMCPError(ManifestMCPErrorCode.TX_FAILED, reason);\n }\n\n // Invariant: when `verifyAndRecover` returns success, the matched\n // outcome was `'terminal'`, and the verifier's `terminal` branch\n // ALWAYS sets `diagnostic.stateName` (see the spec above). A missing\n // `stateName` on the success path means the verifier invariant is\n // broken — likely a future refactor regression. The previous\n // implementation fell back to `'LEASE_STATE_CLOSED'` silently, which\n // would lie to the caller (Copilot review PR #60, comment 3276719603).\n // Fail loudly with a typed error instead. `TX_FAILED` is the closest\n // available code in `ManifestMCPErrorCode` (no `INTERNAL_ERROR`\n // variant); the message names the invariant explicitly.\n if (!verifyResult.diagnostic.stateName) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.TX_FAILED,\n `close-lease verifier invariant violated: success outcome reached without diagnostic.stateName for lease ${args.leaseUuid}`,\n );\n }\n const finalState: LeaseStateName = verifyResult.diagnostic.stateName;\n const result: CloseLeaseResult = {\n leaseUuid: args.leaseUuid,\n finalState,\n };\n callbacks.onComplete?.(result);\n return result;\n}\n\n// --- Helpers --------------------------------------------------------\n\nfunction validateArgs(args: CloseLeaseArgs): void {\n if (typeof args.leaseUuid !== 'string' || !args.leaseUuid.match(UUID_RE)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `closeLease: leaseUuid must be a UUID; got \"${args.leaseUuid}\".`,\n );\n }\n}\n\nfunction renderConfirmationBlock(args: CloseLeaseArgs): DeploymentPlanBlock {\n // Image is not tracked in `CloseLeaseArgs` and `stopApp` doesn't return it;\n // surface the gap explicitly so reviewers/users see the missing context\n // rather than silently omitting an image field they'd expect.\n const text = [\n `Close lease ${args.leaseUuid}.`,\n ' Image: (image not recorded)',\n ' This is permanent — the lease cannot be reopened.',\n '',\n 'Proceed?',\n ].join('\\n');\n return { text };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA2CA,MAAM,UACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCF,eAAsB,WACpB,MACA,WACA,MAC2B;CAC3B,aAAa,IAAI;CAEjB,MAAM,QAAQ,wBAAwB,IAAI;CAC1C,IAAI,UAAU;MAER,MADgB,UAAU,UAAU,KAAK,MAC/B,OACZ,MAAM,IAAI,iBACR,qBAAqB,qBACrB,4CACF;CAAA;CAGJ,UAAU,aAAa,EAAE,MAAM,iBAAiB,CAAC;CAEjD,MAAM,QAAQ,KAAK,eAAe,KAAK,SAAS;CA6FhD,MAAM,eAAe,MAAM,iBAAiB;EArF1C,UAAU,YAAY;GAUpB,IAAI;GACJ,IAAI;IAEF,SAAS,OAAM,MADW,KAAK,cAAc,eAAe,GACjC,WAAW,QAAQ,GAAG,MAAM,EACrD,WAAW,KAAK,UAClB,CAAC;GACH,SAAS,KAAK;IACZ,MAAM,SAAS,yBAAyB,KAAK,UAAU,wBACrD,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;IAEjD,IAAI,UAAU,WACZ,MAAM,UAAU,UAAU,EAAE,OAAO,CAAC;IAEtC,IAAI,eAAe,kBACjB,MAAM;IAER,MAAM,IAAI,iBAAiB,qBAAqB,cAAc,MAAM;GACtE;GACA,MAAM,QAAS,QAAgC;GAC/C,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B,OAAO;IACL,SAAS;IACT,YAAY,EACV,QAAQ,SAAS,KAAK,UAAU,mCAClC;GACF;GAEF,MAAM,WAAY,MAA8B;GAChD,MAAM,YAAYA,OAChB,OAAO,aAAa,YAAY,OAAO,aAAa,WAChD,WACA,KAAA,CACN;GACA,IAAI,cAAc,KAAA,GAChB,OAAO;IACL,SAAS;IACT,YAAY,EACV,QAAQ,SAAS,KAAK,UAAU,mCAAmC,OAAO,QAAQ,EAAE,GACtF;GACF;GAEF,OAAO;IACL,SAAU,WAAW,SAAS,IAAI,aAAa;IAG/C,YAAY,EAAE,UAAU;GAC1B;EACF;EACA,eAAe,CAAC,UAAU;EAC1B,UAAU;GACR,SAAS;IACP,UAAU;IACV,mBAAmB,CAAC,4BAA4B;IAChD,uBAAuB,OAAO;KAC5B,SAAS;KACT,QACE,EAAE,UACF,8CAA8C,EAAE,aAAa,UAAU;IAC3E;IACA,4BAA4B,CAAC;GAC/B;GACA,WAAW;IACT,UAAU;IACV,mBAAmB,CAAC,8BAA8B;IAClD,uBAAuB,OAAO;KAC5B,SAAS;KACT,QACE,EAAE,UACF,SAAS,KAAK,UAAU;IAC5B;IACA,4BAA4B,CAAC;GAC/B;EACF;CAG6C,GAAG,KAAA,CAAS;CAE3D,IAAI,aAAa,WAAW,WAAW;EACrC,MAAM,SACJ,aAAa,SAAS,UAAU;EAClC,IAAI,UAAU,WACZ,MAAM,UAAU,UAAU,EAAE,OAAO,CAAC;EAEtC,MAAM,IAAI,iBAAiB,qBAAqB,WAAW,MAAM;CACnE;CAYA,IAAI,CAAC,aAAa,WAAW,WAC3B,MAAM,IAAI,iBACR,qBAAqB,WACrB,2GAA2G,KAAK,WAClH;CAEF,MAAM,aAA6B,aAAa,WAAW;CAC3D,MAAM,SAA2B;EAC/B,WAAW,KAAK;EAChB;CACF;CACA,UAAU,aAAa,MAAM;CAC7B,OAAO;AACT;AAIA,SAAS,aAAa,MAA4B;CAChD,IAAI,OAAO,KAAK,cAAc,YAAY,CAAC,KAAK,UAAU,MAAM,OAAO,GACrE,MAAM,IAAI,iBACR,qBAAqB,gBACrB,8CAA8C,KAAK,UAAU,GAC/D;AAEJ;AAEA,SAAS,wBAAwB,MAA2C;CAW1E,OAAO,EAAE,MAPI;EACX,eAAe,KAAK,UAAU;EAC9B;EACA;EACA;EACA;CACF,EAAE,KAAK,IACK,EAAE;AAChB"}
1
+ {"version":3,"file":"close-lease.js","names":["decodeLeaseState"],"sources":["../src/close-lease.ts"],"sourcesContent":["/**\n * Public entry point: orchestrate closing an existing lease via the\n * `close-lease` billing tx.\n *\n * Composition (mirrors `deploy-app.ts` / `manage-domain.ts`):\n *\n * 1. Validate args.\n * 2. Render a confirmation block + optionally consult `onConfirm`.\n * 3. Broadcast `stopApp` (which submits `MsgCloseLease`).\n * 4. Verify the post-broadcast on-chain state via `verifyAndRecover`\n * driving a direct `billing.v1.lease({ leaseUuid })` query +\n * `lease-state.decode` + `isTerminal`. Terminal states (CLOSED /\n * REJECTED / EXPIRED / INSUFFICIENT_FUNDS) count as success;\n * PENDING / ACTIVE map to the `pending_drift` branch; a chain\n * response with no lease (`{ lease: null }`) maps to the catch-all\n * `unclassified` branch.\n * 5. On verify-failure, invoke the simple-form `onFailure({ reason })`\n * then throw `ManifestMCPError(TX_FAILED)`. On success, emit\n * `onComplete` with the typed `CloseLeaseResult`.\n */\n\nimport {\n ManifestMCPError,\n ManifestMCPErrorCode,\n noopLogger,\n parseLeaseUuid,\n stopApp,\n} from '@manifest-network/manifest-mcp-core';\nimport { makeCancellationScope } from './internals/cancellation.js';\nimport {\n decode as decodeLeaseState,\n isTerminal,\n} from './internals/lease-state.js';\nimport {\n type VerificationSpec,\n verifyAndRecover,\n} from './internals/verify-recover.js';\nimport type {\n CloseLeaseArgs,\n CloseLeaseCallbacks,\n CloseLeaseOptions,\n CloseLeaseResult,\n DeploymentPlanBlock,\n LeaseStateName,\n} from './types.js';\n\nconst UUID_RE =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\ntype CloseOutcome = 'terminal' | 'pending' | 'not_found';\n\ninterface CloseDiag {\n stateName?: LeaseStateName;\n reason?: string;\n}\n\n/**\n * Close a lease and verify it reached a terminal on-chain state.\n *\n * @throws `ManifestMCPError(INVALID_CONFIG)` for args validation.\n * @throws `ManifestMCPError(OPERATION_CANCELLED)` when `onConfirm` returns\n * `'no'` (deliberate user cancellation — ENG-272).\n * @throws `ManifestMCPError` (typically `TX_FAILED`) propagated as-is\n * from the `stopApp()` broadcast step. Broadcast errors do NOT invoke\n * `onFailure` — that callback is reserved for post-broadcast\n * verification failures. `stopApp` already raises a structured\n * `ManifestMCPError` from the core package; wrapping it again at this\n * layer would be redundant. Callers wanting to react to broadcast\n * errors should catch them at the call site.\n * @throws `ManifestMCPError(TX_FAILED)` when post-broadcast verification\n * reaches one of two failure modes (both with `onFailure({ reason })`\n * invoked first):\n * - the lease is still non-terminal (`pending_drift` branch — state\n * decoded as PENDING / ACTIVE / similar non-terminal); or\n * - the chain returns `{ lease: null }` post-close, so the lease is\n * not visible on-chain (`unclassified` branch).\n * @throws `ManifestMCPError(QUERY_FAILED)` when the post-broadcast verify\n * chain query (`billing.v1.lease`) raises a non-NotFound error\n * (RPC / transport / decoding failure). Wrapped inside the verifier\n * closure so the failure flows through `onFailure({ reason })` before\n * the throw. Structured `ManifestMCPError`s raised by the chain client\n * are re-thrown as-is (with `onFailure` invoked first).\n */\nexport async function closeLease(\n args: CloseLeaseArgs,\n callbacks: CloseLeaseCallbacks,\n opts: CloseLeaseOptions,\n): Promise<CloseLeaseResult> {\n validateArgs(args);\n\n const cx = makeCancellationScope({\n opts,\n onProgress: callbacks.onProgress,\n opLabel: 'Lease close',\n broadcasts: true,\n });\n cx.throwIfCancelled();\n\n const block = renderConfirmationBlock(args);\n if (callbacks.onConfirm) {\n const yesNo = await cx.race(callbacks.onConfirm(block));\n if (yesNo !== 'yes') {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.OPERATION_CANCELLED,\n 'User declined to proceed with close-lease.',\n );\n }\n }\n callbacks.onProgress?.({ kind: 'user_confirmed' });\n\n cx.throwIfCancelled();\n\n // txCtx has no signer (ManageDomain/CloseLease flows carry no walletProvider);\n // the sender resolves from ctx.chain (the CosmosClientManager wallet). See OI-SENDER.\n await stopApp(\n { chain: opts.clientManager, logger: noopLogger },\n { leaseUuid: parseLeaseUuid(args.leaseUuid) },\n );\n\n // Direct single-lease query (Copilot review PR #60, comment 3275999624):\n // the previous `leasesByTenant` + page-1-only pagination would\n // false-`not_found` for tenants with >100 leases. `billing.v1.lease`\n // is the same query shape `troubleshoot.ts` already uses; it's\n // tenant-agnostic and bounded to a single lease.\n const spec: VerificationSpec<unknown, CloseOutcome, CloseDiag> = {\n verifier: async () => {\n // Wrap the chain call in try/catch (Copilot review PR #60,\n // comment 3276419264): if `billing.v1.lease` rejects (RPC down,\n // transport, structured `ManifestMCPError`), the error would\n // otherwise propagate OUT of `verifyAndRecover` and bypass the\n // post-verify `onFailure({ reason })` callback below. Mirror\n // the disambiguation pattern from `lookupDomain` (commit aaa5cc5)\n // and `troubleshootDeployment` (commit f1a4737): invoke\n // `onFailure` first, then re-throw `ManifestMCPError` as-is or\n // wrap plain errors as `QUERY_FAILED`.\n let result: unknown;\n try {\n const queryClient = await opts.clientManager.getQueryClient();\n result = await queryClient.liftedinit.billing.v1.lease({\n leaseUuid: args.leaseUuid,\n });\n } catch (err) {\n const reason = `Failed to query lease ${args.leaseUuid} during close-verify: ${\n err instanceof Error ? err.message : String(err)\n }`;\n if (callbacks.onFailure) {\n await callbacks.onFailure({ reason });\n }\n if (err instanceof ManifestMCPError) {\n throw err;\n }\n throw new ManifestMCPError(ManifestMCPErrorCode.QUERY_FAILED, reason);\n }\n const lease = (result as { lease?: unknown })?.lease;\n if (lease === null || lease === undefined) {\n return {\n outcome: 'not_found' as const,\n diagnostic: {\n reason: `lease ${args.leaseUuid} not visible on chain after close`,\n },\n };\n }\n const rawState = (lease as { state?: unknown }).state;\n const stateName = decodeLeaseState(\n typeof rawState === 'number' || typeof rawState === 'string'\n ? rawState\n : undefined,\n );\n if (stateName === undefined) {\n return {\n outcome: 'pending' as const,\n diagnostic: {\n reason: `lease ${args.leaseUuid} state could not be decoded (raw=${String(rawState)})`,\n },\n };\n }\n return {\n outcome: (isTerminal(stateName) ? 'terminal' : 'pending') as\n | 'terminal'\n | 'pending',\n diagnostic: { stateName },\n };\n },\n successValues: ['terminal'],\n branches: {\n pending: {\n branchId: 'pending_drift',\n journalActionTags: ['close-lease-verify-pending'],\n buildFailureEnvelope: (d) => ({\n outcome: 'failed',\n reason:\n d.reason ??\n `close_lease tx accepted but state is still ${d.stateName ?? 'unknown'}.`,\n }),\n buildRecoveryOptions: () => [],\n },\n not_found: {\n branchId: 'unclassified',\n journalActionTags: ['close-lease-verify-not-found'],\n buildFailureEnvelope: (d) => ({\n outcome: 'failed',\n reason:\n d.reason ??\n `Lease ${args.leaseUuid} not visible on chain after close.`,\n }),\n buildRecoveryOptions: () => [],\n },\n },\n };\n\n const verifyResult = await verifyAndRecover(spec, undefined);\n\n if (verifyResult.result !== 'success') {\n const reason =\n verifyResult.failure?.reason ?? 'close-lease verification failed.';\n if (callbacks.onFailure) {\n await callbacks.onFailure({ reason });\n }\n throw new ManifestMCPError(ManifestMCPErrorCode.TX_FAILED, reason);\n }\n\n // Invariant: when `verifyAndRecover` returns success, the matched\n // outcome was `'terminal'`, and the verifier's `terminal` branch\n // ALWAYS sets `diagnostic.stateName` (see the spec above). A missing\n // `stateName` on the success path means the verifier invariant is\n // broken — likely a future refactor regression. The previous\n // implementation fell back to `'LEASE_STATE_CLOSED'` silently, which\n // would lie to the caller (Copilot review PR #60, comment 3276719603).\n // Fail loudly with a typed error instead. `TX_FAILED` is the closest\n // available code in `ManifestMCPErrorCode` (no `INTERNAL_ERROR`\n // variant); the message names the invariant explicitly.\n if (!verifyResult.diagnostic.stateName) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.TX_FAILED,\n `close-lease verifier invariant violated: success outcome reached without diagnostic.stateName for lease ${args.leaseUuid}`,\n );\n }\n const finalState: LeaseStateName = verifyResult.diagnostic.stateName;\n const result: CloseLeaseResult = {\n leaseUuid: args.leaseUuid,\n finalState,\n };\n callbacks.onComplete?.(result);\n return result;\n}\n\n// --- Helpers --------------------------------------------------------\n\nfunction validateArgs(args: CloseLeaseArgs): void {\n if (typeof args.leaseUuid !== 'string' || !args.leaseUuid.match(UUID_RE)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `closeLease: leaseUuid must be a UUID; got \"${args.leaseUuid}\".`,\n );\n }\n}\n\nfunction renderConfirmationBlock(args: CloseLeaseArgs): DeploymentPlanBlock {\n // Image is not tracked in `CloseLeaseArgs` and `stopApp` doesn't return it;\n // surface the gap explicitly so reviewers/users see the missing context\n // rather than silently omitting an image field they'd expect.\n const text = [\n `Close lease ${args.leaseUuid}.`,\n ' Image: (image not recorded)',\n ' This is permanent — the lease cannot be reopened.',\n '',\n 'Proceed?',\n ].join('\\n');\n return { text };\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { DeployAppCallbacks, DeployAppOptions, DeployResult, DeploySpec } from "./types.js";
1
+ import { AppDeploySpec, DeployAppCallbacks, DeployAppOptions, DeployResult } from "./types.js";
2
2
 
3
3
  //#region src/deploy-app.d.ts
4
4
  /**
@@ -19,7 +19,7 @@ import { DeployAppCallbacks, DeployAppOptions, DeployResult, DeploySpec } from "
19
19
  * branch) throw directly as `ManifestMCPError(TX_FAILED)` without
20
20
  * invoking `onFailure`.
21
21
  */
22
- declare function deployApp(spec: DeploySpec, callbacks: DeployAppCallbacks, opts: DeployAppOptions): Promise<DeployResult>;
22
+ declare function deployApp(spec: AppDeploySpec, callbacks: DeployAppCallbacks, opts: DeployAppOptions): Promise<DeployResult>;
23
23
  //#endregion
24
24
  export { deployApp };
25
25
  //# sourceMappingURL=deploy-app.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deploy-app.d.ts","names":[],"sources":["../src/deploy-app.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;iBAiIsB,SAAA,CACpB,IAAA,EAAM,UAAA,EACN,SAAA,EAAW,kBAAA,EACX,IAAA,EAAM,gBAAA,GACL,OAAA,CAAQ,YAAA"}
1
+ {"version":3,"file":"deploy-app.d.ts","names":[],"sources":["../src/deploy-app.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;iBAqIsB,SAAA,CACpB,IAAA,EAAM,aAAA,EACN,SAAA,EAAW,kBAAA,EACX,IAAA,EAAM,gBAAA,GACL,OAAA,CAAQ,YAAA"}
@@ -1,21 +1,20 @@
1
+ import { makeCancellationScope } from "./internals/cancellation.js";
1
2
  import { decode } from "./internals/lease-state.js";
2
- import { isStackSpec, summarizeSpec, validateSpec } from "./internals/spec-normalize.js";
3
- import { buildFredDeployInput, buildManifestPreviewInput } from "./internals/build-fred-input.js";
4
3
  import { classifyDeployError } from "./internals/classify-deploy-error.js";
5
4
  import { extractRunningEndpoints, formatEndpointAsUrl, normalizeFredUrl } from "./internals/connection.js";
6
5
  import { classifyDeployResponse } from "./internals/classify-deploy-response.js";
7
6
  import { EMPTY_DENOM_MAP, loadChainDenomMap } from "./internals/humanize-denom.js";
8
7
  import { evaluateReadinessFromFredResponse } from "./internals/evaluate-readiness-from-fred.js";
9
- import { findSkuUuid } from "./internals/find-sku-uuid.js";
10
8
  import { renderDeploymentPlan } from "./internals/render-deployment-plan.js";
9
+ import { isStackSpec, summarizeSpec, validateSpec } from "./internals/spec-normalize.js";
11
10
  import { renderIntentRecap } from "./internals/render-intent-recap.js";
12
11
  import { renderPartialSuccessPrompt } from "./internals/render-partial-success-prompt.js";
13
- import { ManifestMCPError, ManifestMCPErrorCode, cosmosEstimateFee, setItemCustomDomain, stopApp } from "@manifest-network/manifest-mcp-core";
12
+ import { ManifestMCPError, ManifestMCPErrorCode, asLeaseUuid, asProviderUuid, cosmosEstimateFee, noopLogger, parseFqdn, parseLeaseUuid, resolveSku, setItemCustomDomain, stopApp } from "@manifest-network/manifest-mcp-core";
14
13
  import { AuthTimestampTracker, buildManifestPreview, checkDeploymentReadiness, createAuthToken, createLeaseDataSignMessage, createSignMessage, deployApp as deployApp$1, fetchActiveLease, pollLeaseUntilReady, resolveProviderUrl, uploadLeaseData, waitForAppReady } from "@manifest-network/manifest-mcp-fred";
15
14
  //#region src/deploy-app.ts
16
15
  /**
17
16
  * Public entry point: orchestrate a Manifest-Network app deployment from
18
- * a typed `DeploySpec` through the plan/confirm/broadcast/save flow.
17
+ * a typed `AppDeploySpec` through the plan/confirm/broadcast/save flow.
19
18
  *
20
19
  * Architect's α-locked composition (post-PR-3 sub-plan Q1):
21
20
  *
@@ -76,6 +75,13 @@ async function deployApp(spec, callbacks, opts) {
76
75
  throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, err instanceof Error ? err.message : `Invalid spec: ${String(err)}`);
77
76
  }
78
77
  if (typeof opts.walletProvider.signArbitrary !== "function") throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, "opts.walletProvider must implement signArbitrary for ADR-036 auth tokens.");
78
+ const { signal, throwIfCancelled, race } = makeCancellationScope({
79
+ opts,
80
+ onProgress: callbacks.onProgress,
81
+ opLabel: "Deployment",
82
+ broadcasts: true
83
+ });
84
+ throwIfCancelled();
79
85
  const walletAddress = await opts.walletProvider.getAddress();
80
86
  const clientAddress = await opts.clientManager.getAddress();
81
87
  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.`);
@@ -84,9 +90,49 @@ async function deployApp(spec, callbacks, opts) {
84
90
  const chainId = opts.clientManager.getConfig().chainId;
85
91
  const activeChain = /mainnet|main/i.test(chainId) ? "mainnet" : "testnet";
86
92
  const queryClient = await opts.clientManager.getQueryClient();
87
- let readiness = evaluateReadinessFromFredResponse(await checkDeploymentReadiness(queryClient, tenantAddress, {
93
+ const readCtx = {
94
+ query: queryClient,
95
+ chain: opts.clientManager,
96
+ logger: noopLogger
97
+ };
98
+ const resolvePin = async (s) => {
99
+ const providerUuid = requestedProviderUuid(s);
100
+ const skuUuid = requestedSkuUuid(s);
101
+ try {
102
+ return {
103
+ pin: await resolveSku(readCtx, {
104
+ size: requestedSize(s),
105
+ ...providerUuid !== void 0 ? { providerUuid } : {},
106
+ ...skuUuid !== void 0 ? { skuUuid } : {}
107
+ }),
108
+ elicited: false
109
+ };
110
+ } catch (err) {
111
+ if (err instanceof ManifestMCPError && err.code === ManifestMCPErrorCode.SKU_AMBIGUOUS && callbacks.onResolveSku) {
112
+ const candidates = err.details?.candidates ?? [];
113
+ callbacks.onProgress?.({
114
+ kind: "sku_ambiguous",
115
+ candidates
116
+ });
117
+ const pick = await race(callbacks.onResolveSku(candidates));
118
+ return {
119
+ pin: await resolveSku(readCtx, {
120
+ size: requestedSize(s),
121
+ skuUuid: pick.skuUuid,
122
+ providerUuid: pick.providerUuid
123
+ }),
124
+ elicited: true
125
+ };
126
+ }
127
+ throw err;
128
+ }
129
+ };
130
+ let { pin: pinned, elicited: pinElicited } = await resolvePin(spec);
131
+ let readiness = evaluateReadinessFromFredResponse(await checkDeploymentReadiness(readCtx, tenantAddress, {
88
132
  image: primaryImage(spec),
89
- size: requestedSize(spec)
133
+ size: pinned.name,
134
+ providerUuid: pinned.providerUuid,
135
+ skuUuid: pinned.skuUuid
90
136
  }), opts.clientManager.getConfig().gasPrice ?? "1umfx", denomMap, tenantAddress);
91
137
  callbacks.onProgress?.({
92
138
  kind: "readiness_evaluated",
@@ -96,30 +142,35 @@ async function deployApp(spec, callbacks, opts) {
96
142
  `${readiness.reasons.join("; ")}`;
97
143
  throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, `Readiness check failed: ${readiness.reasons.join("; ")}`);
98
144
  }
99
- let preview = await buildManifestPreview(buildManifestPreviewInput(spec, requestedSize(spec)));
145
+ let preview = await buildManifestPreview(spec);
100
146
  let summary = summarizeSpec(spec);
101
- let fees = await estimateFees(opts, spec, preview.meta_hash_hex);
147
+ let fees = await estimateFees(opts, spec, preview.meta_hash_hex, pinned.skuUuid);
102
148
  let plan = {
103
149
  summary,
104
150
  readiness,
105
151
  fees
106
152
  };
107
- let confirmedSpec = spec;
153
+ let confirmedSpec = pinElicited ? {
154
+ ...spec,
155
+ skuUuid: pinned.skuUuid,
156
+ providerUuid: pinned.providerUuid
157
+ } : spec;
108
158
  const block = renderDeploymentPlan({
109
159
  plan,
110
160
  denomMap,
111
161
  image: primaryImage(spec),
112
- size: requestedSize(spec),
162
+ size: pinned.name,
113
163
  metaHash: preview.meta_hash_hex,
114
164
  customDomain: customDomainOf(spec),
115
- customDomainService: customDomainServiceOf(spec)
165
+ customDomainService: customDomainServiceOf(spec),
166
+ providerUuid: pinned.providerUuid
116
167
  });
117
168
  callbacks.onProgress?.({
118
169
  kind: "deployment_plan_rendered",
119
170
  block
120
171
  });
121
172
  if (callbacks.onPlan) {
122
- const verdict = await callbacks.onPlan(plan);
173
+ const verdict = await race(callbacks.onPlan(plan));
123
174
  if (verdict === "cancel") throw new ManifestMCPError(ManifestMCPErrorCode.OPERATION_CANCELLED, "User cancelled deployment at plan step.");
124
175
  if (verdict !== "confirm") {
125
176
  confirmedSpec = applyPlanEdit(confirmedSpec, verdict);
@@ -128,18 +179,26 @@ async function deployApp(spec, callbacks, opts) {
128
179
  } catch (err) {
129
180
  throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, err instanceof Error ? `Post-edit spec failed validation: ${err.message}` : `Post-edit spec failed validation: ${String(err)}`);
130
181
  }
131
- readiness = evaluateReadinessFromFredResponse(await checkDeploymentReadiness(queryClient, tenantAddress, {
182
+ ({pin: pinned, elicited: pinElicited} = await resolvePin(confirmedSpec));
183
+ if (pinElicited) confirmedSpec = {
184
+ ...confirmedSpec,
185
+ skuUuid: pinned.skuUuid,
186
+ providerUuid: pinned.providerUuid
187
+ };
188
+ readiness = evaluateReadinessFromFredResponse(await checkDeploymentReadiness(readCtx, tenantAddress, {
132
189
  image: primaryImage(confirmedSpec),
133
- size: requestedSize(confirmedSpec)
190
+ size: pinned.name,
191
+ providerUuid: pinned.providerUuid,
192
+ skuUuid: pinned.skuUuid
134
193
  }), opts.clientManager.getConfig().gasPrice ?? "1umfx", denomMap, tenantAddress);
135
194
  callbacks.onProgress?.({
136
195
  kind: "readiness_evaluated",
137
196
  readiness
138
197
  });
139
198
  if (readiness.status === "block") throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, `Post-edit readiness check failed: ${readiness.reasons.join("; ")}`);
140
- preview = await buildManifestPreview(buildManifestPreviewInput(confirmedSpec, requestedSize(confirmedSpec)));
199
+ preview = await buildManifestPreview(confirmedSpec);
141
200
  summary = summarizeSpec(confirmedSpec);
142
- fees = await estimateFees(opts, confirmedSpec, preview.meta_hash_hex);
201
+ fees = await estimateFees(opts, confirmedSpec, preview.meta_hash_hex, pinned.skuUuid);
143
202
  plan = {
144
203
  summary,
145
204
  readiness,
@@ -149,10 +208,11 @@ async function deployApp(spec, callbacks, opts) {
149
208
  plan,
150
209
  denomMap,
151
210
  image: primaryImage(confirmedSpec),
152
- size: requestedSize(confirmedSpec),
211
+ size: pinned.name,
153
212
  metaHash: preview.meta_hash_hex,
154
213
  customDomain: customDomainOf(confirmedSpec),
155
- customDomainService: customDomainServiceOf(confirmedSpec)
214
+ customDomainService: customDomainServiceOf(confirmedSpec),
215
+ providerUuid: pinned.providerUuid
156
216
  });
157
217
  callbacks.onProgress?.({
158
218
  kind: "deployment_plan_rendered",
@@ -165,7 +225,7 @@ async function deployApp(spec, callbacks, opts) {
165
225
  activeChain
166
226
  }) };
167
227
  if (callbacks.onConfirm) {
168
- if (await callbacks.onConfirm(recapBlock) !== "yes") throw new ManifestMCPError(ManifestMCPErrorCode.OPERATION_CANCELLED, "User declined to proceed at intent-recap step.");
228
+ if (await race(callbacks.onConfirm(recapBlock)) !== "yes") throw new ManifestMCPError(ManifestMCPErrorCode.OPERATION_CANCELLED, "User declined to proceed at intent-recap step.");
169
229
  }
170
230
  callbacks.onProgress?.({ kind: "user_confirmed" });
171
231
  const signArbitrary = opts.walletProvider.signArbitrary.bind(opts.walletProvider);
@@ -180,11 +240,26 @@ async function deployApp(spec, callbacks, opts) {
180
240
  const { pub_key, signature } = await signArbitrary(address, createLeaseDataSignMessage(leaseUuid, metaHashHex, ts));
181
241
  return createAuthToken(address, leaseUuid, ts, pub_key.value, signature, metaHashHex);
182
242
  };
243
+ throwIfCancelled();
183
244
  callbacks.onProgress?.({ kind: "deploy_app_broadcast" });
184
- const fredInput = buildFredDeployInput(confirmedSpec, requestedSize(confirmedSpec));
245
+ const fredInput = {
246
+ ...confirmedSpec,
247
+ size: pinned.name,
248
+ skuUuid: pinned.skuUuid,
249
+ providerUuid: pinned.providerUuid
250
+ };
185
251
  let fredResult;
186
252
  try {
187
- fredResult = await deployApp$1(opts.clientManager, getAuthToken, getLeaseDataAuthToken, fredInput, opts.fetchFn);
253
+ fredResult = await deployApp$1({
254
+ query: queryClient,
255
+ chain: opts.clientManager,
256
+ fetch: opts.fetchFn ?? globalThis.fetch,
257
+ logger: noopLogger,
258
+ providerAuth: {
259
+ providerToken: (i) => getAuthToken(i.address, i.leaseUuid),
260
+ leaseDataToken: (i) => getLeaseDataAuthToken(i.address, i.leaseUuid, i.metaHashHex)
261
+ }
262
+ }, fredInput, signal ? { abortSignal: signal } : {});
188
263
  } catch (err) {
189
264
  const recoveryCtx = {
190
265
  manifestJson: preview.manifest_json,
@@ -193,7 +268,8 @@ async function deployApp(spec, callbacks, opts) {
193
268
  getLeaseDataAuthToken,
194
269
  tenantAddress,
195
270
  chainId,
196
- denomMap
271
+ denomMap,
272
+ skuName: pinned.name
197
273
  };
198
274
  return await handleBroadcastFailure(err, confirmedSpec, callbacks, opts, recoveryCtx);
199
275
  }
@@ -215,7 +291,19 @@ async function deployApp(spec, callbacks, opts) {
215
291
  let attempt = 0;
216
292
  let pollResult;
217
293
  try {
218
- pollResult = await waitForAppReady(queryClient, tenantAddress, leaseUuid, getAuthToken, {
294
+ pollResult = await waitForAppReady({
295
+ query: queryClient,
296
+ chain: opts.clientManager,
297
+ fetch: opts.fetchFn ?? globalThis.fetch,
298
+ logger: noopLogger,
299
+ providerAuth: {
300
+ providerToken: (i) => getAuthToken(i.address, i.leaseUuid),
301
+ leaseDataToken: (i) => getLeaseDataAuthToken(i.address, i.leaseUuid, i.metaHashHex)
302
+ }
303
+ }, {
304
+ address: tenantAddress,
305
+ leaseUuid
306
+ }, {
219
307
  timeoutMs: opts.waitForReadyTimeoutMs ?? 48e4,
220
308
  onProgress: (status) => {
221
309
  attempt += 1;
@@ -228,7 +316,7 @@ async function deployApp(spec, callbacks, opts) {
228
316
  ...stateName !== void 0 ? { state: stateName } : {}
229
317
  });
230
318
  }
231
- }, opts.fetchFn);
319
+ });
232
320
  } catch (err) {
233
321
  const reason = err instanceof Error ? `wait_for_app_ready failed for lease ${leaseUuid}: ${err.message}` : `wait_for_app_ready failed for lease ${leaseUuid}: ${String(err)}`;
234
322
  throw new ManifestMCPError(ManifestMCPErrorCode.TX_FAILED, reason);
@@ -246,8 +334,8 @@ async function deployApp(spec, callbacks, opts) {
246
334
  }
247
335
  fredResult = {
248
336
  ...fredResult,
249
- lease_uuid: pollResult.lease_uuid,
250
- provider_uuid: pollResult.provider_uuid,
337
+ lease_uuid: asLeaseUuid(pollResult.lease_uuid),
338
+ provider_uuid: asProviderUuid(pollResult.provider_uuid),
251
339
  provider_url: pollResult.provider_url
252
340
  };
253
341
  liveState = pollResult.status.state;
@@ -260,7 +348,7 @@ async function deployApp(spec, callbacks, opts) {
260
348
  const persistedPath = await tryPersistManifest({
261
349
  leaseUuid: fredResult.lease_uuid,
262
350
  image: primaryImage(confirmedSpec),
263
- size: requestedSize(confirmedSpec),
351
+ size: pinned.name,
264
352
  metaHash: preview.meta_hash_hex,
265
353
  chainId,
266
354
  manifestJson: preview.manifest_json,
@@ -301,8 +389,21 @@ function primaryImage(spec) {
301
389
  return spec.image ?? "";
302
390
  }
303
391
  function requestedSize(spec) {
304
- const recorded = spec.size;
305
- return typeof recorded === "string" && recorded.length > 0 ? recorded : "small";
392
+ return spec.size;
393
+ }
394
+ /**
395
+ * SKU disambiguator intent helpers. `providerUuid` / `skuUuid` are
396
+ * first-class optional fields on `AppDeploySpec` (ENG-296, mirroring
397
+ * ENG-275's typed `size`). Returns `undefined` for absent / empty values
398
+ * so `resolveSku` only narrows when a real disambiguator is supplied.
399
+ */
400
+ function requestedProviderUuid(spec) {
401
+ const v = spec.providerUuid;
402
+ return typeof v === "string" && v.length > 0 ? v : void 0;
403
+ }
404
+ function requestedSkuUuid(spec) {
405
+ const v = spec.skuUuid;
406
+ return typeof v === "string" && v.length > 0 ? v : void 0;
306
407
  }
307
408
  function customDomainOf(spec) {
308
409
  return spec.customDomain;
@@ -310,9 +411,7 @@ function customDomainOf(spec) {
310
411
  function customDomainServiceOf(spec) {
311
412
  if (isStackSpec(spec)) return spec.serviceName;
312
413
  }
313
- async function estimateFees(opts, spec, metaHashHex) {
314
- const size = requestedSize(spec);
315
- const { skuUuid } = await findSkuUuid(opts.clientManager, size);
414
+ async function estimateFees(opts, spec, metaHashHex, skuUuid) {
316
415
  const itemArgs = isStackSpec(spec) ? Object.keys(spec.services).map((name) => `${skuUuid}:1:${name}`) : [`${skuUuid}:1`];
317
416
  let createLeaseEstimate;
318
417
  try {
@@ -361,11 +460,10 @@ function applyPlanEdit(spec, edit) {
361
460
  }
362
461
  };
363
462
  }
364
- const single = spec;
365
463
  return {
366
- ...single,
464
+ ...spec,
367
465
  env: {
368
- ...single.env ?? {},
466
+ ...spec.env ?? {},
369
467
  ...edit.env
370
468
  }
371
469
  };
@@ -413,7 +511,10 @@ async function dispatchRecovery(choice, envelope, spec, opts, callbacks, ctx) {
413
511
  case "salvage_without_domain": throw new ManifestMCPError(ManifestMCPErrorCode.TX_FAILED, `salvage_without_domain: lease ${leaseUuid} retained without domain; caller should re-run troubleshootDeployment.`);
414
512
  case "cancel_lease":
415
513
  case "close_lease":
416
- await stopApp(opts.clientManager, leaseUuid);
514
+ await stopApp({
515
+ chain: opts.clientManager,
516
+ logger: noopLogger
517
+ }, { leaseUuid: parseLeaseUuid(leaseUuid) });
417
518
  throw new ManifestMCPError(ManifestMCPErrorCode.TX_FAILED, `${choice.id}: lease ${leaseUuid} closed.`);
418
519
  }
419
520
  throw new ManifestMCPError(ManifestMCPErrorCode.TX_FAILED, `Unknown recovery option: ${choice.id}`);
@@ -472,19 +573,30 @@ async function retrySetDomainAndComplete(leaseUuid, spec, opts, callbacks, ctx)
472
573
  const domain = customDomainOf(spec);
473
574
  if (!domain) throw new ManifestMCPError(ManifestMCPErrorCode.INVALID_CONFIG, "retry_set_domain requires a customDomain in spec.");
474
575
  const serviceName = customDomainServiceOf(spec);
475
- const setItemOpts = serviceName ? { serviceName } : void 0;
476
576
  try {
477
- await setItemCustomDomain(opts.clientManager, leaseUuid, domain, setItemOpts);
577
+ await setItemCustomDomain({
578
+ chain: opts.clientManager,
579
+ logger: noopLogger
580
+ }, {
581
+ leaseUuid: parseLeaseUuid(leaseUuid),
582
+ customDomain: parseFqdn(domain),
583
+ serviceName
584
+ });
478
585
  } catch (err) {
479
586
  const reason = err instanceof Error ? `retry_set_domain set-item-custom-domain failed for lease ${leaseUuid}: ${err.message}` : `retry_set_domain set-item-custom-domain failed for lease ${leaseUuid}: ${String(err)}`;
480
587
  throw new ManifestMCPError(err instanceof ManifestMCPError ? err.code : ManifestMCPErrorCode.TX_FAILED, reason);
481
588
  }
482
- const queryClient = await opts.clientManager.getQueryClient();
589
+ const readCtx = {
590
+ query: await opts.clientManager.getQueryClient(),
591
+ chain: opts.clientManager,
592
+ fetch: opts.fetchFn ?? globalThis.fetch,
593
+ logger: noopLogger
594
+ };
483
595
  let lease;
484
596
  let providerApiUrl;
485
597
  try {
486
- lease = await fetchActiveLease(queryClient, leaseUuid, "cannot complete retry_set_domain");
487
- providerApiUrl = await resolveProviderUrl(queryClient, lease.providerUuid);
598
+ lease = await fetchActiveLease(readCtx, leaseUuid, "cannot complete retry_set_domain");
599
+ providerApiUrl = await resolveProviderUrl(readCtx, lease.providerUuid);
488
600
  } catch (err) {
489
601
  const reason = err instanceof Error ? `retry_set_domain failed to resolve provider for lease ${leaseUuid}: ${err.message}` : `retry_set_domain failed to resolve provider for lease ${leaseUuid}: ${String(err)}`;
490
602
  throw new ManifestMCPError(err instanceof ManifestMCPError ? err.code : ManifestMCPErrorCode.TX_FAILED, reason);
@@ -537,7 +649,7 @@ async function retrySetDomainAndComplete(leaseUuid, spec, opts, callbacks, ctx)
537
649
  const persistedPath = await tryPersistManifest({
538
650
  leaseUuid,
539
651
  image: primaryImage(spec),
540
- size: requestedSize(spec),
652
+ size: ctx.skuName,
541
653
  metaHash: ctx.metaHash,
542
654
  chainId: ctx.chainId,
543
655
  manifestJson: ctx.manifestJson,