@manifest-network/manifest-agent-core 0.16.0 → 0.18.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 +8 -1
- package/dist/close-lease.d.ts +10 -6
- package/dist/close-lease.d.ts.map +1 -1
- package/dist/close-lease.js +16 -9
- package/dist/close-lease.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -22,12 +22,15 @@ The core sits above MCP; host surfaces sit above the core. Callbacks are where s
|
|
|
22
22
|
import {
|
|
23
23
|
closeLease,
|
|
24
24
|
deployApp,
|
|
25
|
+
loadChainDenomMap,
|
|
25
26
|
manageDomain,
|
|
26
27
|
troubleshootDeployment,
|
|
27
28
|
} from '@manifest-network/manifest-agent-core';
|
|
28
29
|
```
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
`loadChainDenomMap` is a helper that pre-loads a chain-registry denom map (the `DeployAppOptions.chainDataFile` input) for denom humanization — e.g. `umfx` → `MFX` — in plan/progress output.
|
|
32
|
+
|
|
33
|
+
Each function takes a typed args object plus a callbacks object with `onConfirm` / `onProgress` / `onComplete` / `onFailure` hooks. `deployApp` takes an `AppDeploySpec`; the other three take their own `*Args` types — only `ManageDomainArgs` is action-discriminated (`{ action: 'set' | 'clear' | 'lookup', ... }`), while `TroubleshootArgs` and `CloseLeaseArgs` are plain `{ leaseUuid: string }` interfaces. Only `deployApp` accepts `onPlan` and `onResolveSku` (ambiguous-SKU disambiguation) and uses an enriched `onFailure` — `(failure: FailureEnvelope, options: RecoveryOption[]) => Promise<RecoveryChoice>` — to drive partial-success recovery: retry the set-domain step, salvage the lease without the custom domain, cancel a pending lease, or close an active one. See `RecoveryOptionId` in `src/types.ts` for the exact literal IDs (`retry_set_domain`, `salvage_without_domain`, `cancel_lease`, `close_lease`). The other three use the simpler `(failure: { reason: string }) => Promise<void>`. See `src/types.ts` for the frozen shapes.
|
|
31
34
|
|
|
32
35
|
## Where each function lives
|
|
33
36
|
|
|
@@ -37,3 +40,7 @@ Each function takes a typed args object plus a callbacks object with `onConfirm`
|
|
|
37
40
|
| `manageDomain` | `src/manage-domain.ts` |
|
|
38
41
|
| `troubleshootDeployment` | `src/troubleshoot.ts` |
|
|
39
42
|
| `closeLease` | `src/close-lease.ts` |
|
|
43
|
+
|
|
44
|
+
## SSRF-guarded fetch (Node-only subpath)
|
|
45
|
+
|
|
46
|
+
The SSRF-guarded `fetch` factory is re-exported from a Node-only subpath, `@manifest-network/manifest-agent-core/guarded-fetch` — deliberately kept off the package barrel so browser bundles don't drag in `undici` / `node:async_hooks` (mirrors core's `@manifest-network/manifest-mcp-core/guarded-fetch` split). Import it from that subpath, never the barrel.
|
package/dist/close-lease.d.ts
CHANGED
|
@@ -8,12 +8,16 @@ import { CloseLeaseArgs, CloseLeaseCallbacks, CloseLeaseOptions, CloseLeaseResul
|
|
|
8
8
|
* @throws `ManifestMCPError(OPERATION_CANCELLED)` when `onConfirm` returns
|
|
9
9
|
* `'no'` (deliberate user cancellation — ENG-272).
|
|
10
10
|
* @throws `ManifestMCPError` (typically `TX_FAILED`) propagated as-is
|
|
11
|
-
* from the `stopApp()`
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* from the `stopApp()` teardown step. `stopApp` is now polymorphic and
|
|
12
|
+
* idempotent: an already-terminal lease resolves as a no-op success and
|
|
13
|
+
* a PENDING lease is CANCELLED (→ REJECTED, which the verifier accepts
|
|
14
|
+
* as terminal), so neither raises here anymore. Only a genuine broadcast
|
|
15
|
+
* error on an ACTIVE close (or the rare `PENDING→ACTIVE` mid-call race)
|
|
16
|
+
* still propagates as-is. Broadcast errors do NOT invoke `onFailure` —
|
|
17
|
+
* that callback is reserved for post-broadcast verification failures.
|
|
18
|
+
* `stopApp` already raises a structured `ManifestMCPError` from the core
|
|
19
|
+
* package; wrapping it again at this layer would be redundant. Callers
|
|
20
|
+
* wanting to react to broadcast errors should catch them at the call site.
|
|
17
21
|
* @throws `ManifestMCPError(TX_FAILED)` when post-broadcast verification
|
|
18
22
|
* reaches one of two failure modes (both with `onFailure({ reason })`
|
|
19
23
|
* invoked first):
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"close-lease.d.ts","names":[],"sources":["../src/close-lease.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"close-lease.d.ts","names":[],"sources":["../src/close-lease.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0FsB,UAAA,CACpB,IAAA,EAAM,cAAA,EACN,SAAA,EAAW,mBAAA,EACX,IAAA,EAAM,iBAAA,GACL,OAAA,CAAQ,gBAAA"}
|
package/dist/close-lease.js
CHANGED
|
@@ -4,14 +4,17 @@ import { verifyAndRecover } from "./internals/verify-recover.js";
|
|
|
4
4
|
import { ManifestMCPError, ManifestMCPErrorCode, noopLogger, parseLeaseUuid, stopApp } from "@manifest-network/manifest-mcp-core";
|
|
5
5
|
//#region src/close-lease.ts
|
|
6
6
|
/**
|
|
7
|
-
* Public entry point: orchestrate
|
|
8
|
-
* `close-
|
|
7
|
+
* Public entry point: orchestrate tearing down an existing lease via the
|
|
8
|
+
* polymorphic `stopApp` (close for ACTIVE, cancel for PENDING, no-op if
|
|
9
|
+
* already terminal).
|
|
9
10
|
*
|
|
10
11
|
* Composition (mirrors `deploy-app.ts` / `manage-domain.ts`):
|
|
11
12
|
*
|
|
12
13
|
* 1. Validate args.
|
|
13
14
|
* 2. Render a confirmation block + optionally consult `onConfirm`.
|
|
14
|
-
* 3. Broadcast `stopApp` (
|
|
15
|
+
* 3. Broadcast `stopApp` (submits `MsgCloseLease` for an ACTIVE lease,
|
|
16
|
+
* `MsgCancelLease` for a PENDING one; no-op if the lease is already
|
|
17
|
+
* terminal).
|
|
15
18
|
* 4. Verify the post-broadcast on-chain state via `verifyAndRecover`
|
|
16
19
|
* driving a direct `billing.v1.lease({ leaseUuid })` query +
|
|
17
20
|
* `lease-state.decode` + `isTerminal`. Terminal states (CLOSED /
|
|
@@ -31,12 +34,16 @@ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
|
|
|
31
34
|
* @throws `ManifestMCPError(OPERATION_CANCELLED)` when `onConfirm` returns
|
|
32
35
|
* `'no'` (deliberate user cancellation — ENG-272).
|
|
33
36
|
* @throws `ManifestMCPError` (typically `TX_FAILED`) propagated as-is
|
|
34
|
-
* from the `stopApp()`
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
37
|
+
* from the `stopApp()` teardown step. `stopApp` is now polymorphic and
|
|
38
|
+
* idempotent: an already-terminal lease resolves as a no-op success and
|
|
39
|
+
* a PENDING lease is CANCELLED (→ REJECTED, which the verifier accepts
|
|
40
|
+
* as terminal), so neither raises here anymore. Only a genuine broadcast
|
|
41
|
+
* error on an ACTIVE close (or the rare `PENDING→ACTIVE` mid-call race)
|
|
42
|
+
* still propagates as-is. Broadcast errors do NOT invoke `onFailure` —
|
|
43
|
+
* that callback is reserved for post-broadcast verification failures.
|
|
44
|
+
* `stopApp` already raises a structured `ManifestMCPError` from the core
|
|
45
|
+
* package; wrapping it again at this layer would be redundant. Callers
|
|
46
|
+
* wanting to react to broadcast errors should catch them at the call site.
|
|
40
47
|
* @throws `ManifestMCPError(TX_FAILED)` when post-broadcast verification
|
|
41
48
|
* reaches one of two failure modes (both with `onFailure({ reason })`
|
|
42
49
|
* invoked first):
|
package/dist/close-lease.js.map
CHANGED
|
@@ -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 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":";;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,MAAM,UACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCF,eAAsB,WACpB,MACA,WACA,MAC2B;CAC3B,aAAa,IAAI;CAEjB,MAAM,KAAK,sBAAsB;EAC/B;EACA,YAAY,UAAU;EACtB,SAAS;EACT,YAAY;CACd,CAAC;CACD,GAAG,iBAAiB;CAEpB,MAAM,QAAQ,wBAAwB,IAAI;CAC1C,IAAI,UAAU;MAER,MADgB,GAAG,KAAK,UAAU,UAAU,KAAK,CAAC,MACxC,OACZ,MAAM,IAAI,iBACR,qBAAqB,qBACrB,4CACF;CAAA;CAGJ,UAAU,aAAa,EAAE,MAAM,iBAAiB,CAAC;CAEjD,GAAG,iBAAiB;CAIpB,MAAM,QACJ;EAAE,OAAO,KAAK;EAAe,QAAQ;CAAW,GAChD,EAAE,WAAW,eAAe,KAAK,SAAS,EAAE,CAC9C;CA6FA,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 tearing down an existing lease via the\n * polymorphic `stopApp` (close for ACTIVE, cancel for PENDING, no-op if\n * already terminal).\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` (submits `MsgCloseLease` for an ACTIVE lease,\n * `MsgCancelLease` for a PENDING one; no-op if the lease is already\n * terminal).\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()` teardown step. `stopApp` is now polymorphic and\n * idempotent: an already-terminal lease resolves as a no-op success and\n * a PENDING lease is CANCELLED (→ REJECTED, which the verifier accepts\n * as terminal), so neither raises here anymore. Only a genuine broadcast\n * error on an ACTIVE close (or the rare `PENDING→ACTIVE` mid-call race)\n * still propagates as-is. Broadcast errors do NOT invoke `onFailure` —\n * that callback is reserved for post-broadcast verification failures.\n * `stopApp` already raises a structured `ManifestMCPError` from the core\n * package; wrapping it again at this layer would be redundant. Callers\n * wanting to react to broadcast 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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@manifest-network/manifest-agent-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "TypeScript orchestration surface for Manifest agent flows (deploy / manage-domain / troubleshoot / close-lease). Type contract only — see ENG-127.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -49,12 +49,12 @@
|
|
|
49
49
|
"dist"
|
|
50
50
|
],
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@manifest-network/manifest-mcp-core": "^0.
|
|
53
|
-
"@manifest-network/manifest-mcp-fred": "^0.
|
|
52
|
+
"@manifest-network/manifest-mcp-core": "^0.18.0",
|
|
53
|
+
"@manifest-network/manifest-mcp-fred": "^0.18.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@manifest-network/manifest-mcp-core": "0.
|
|
57
|
-
"@manifest-network/manifest-mcp-fred": "0.
|
|
56
|
+
"@manifest-network/manifest-mcp-core": "0.18.0",
|
|
57
|
+
"@manifest-network/manifest-mcp-fred": "0.18.0",
|
|
58
58
|
"@types/node": "22.19.19",
|
|
59
59
|
"typescript": "5.9.3",
|
|
60
60
|
"vitest": "4.1.8"
|