@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,46 @@
|
|
|
1
|
+
//#region src/internals/lease-items.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shared decoding for `leases_by_tenant` responses. Walks `leases[]`,
|
|
4
|
+
* matches by UUID, normalizes each item's serviceName/customDomain across
|
|
5
|
+
* snake_case/camelCase variants. `verify-domain-state.ts` is the in-package
|
|
6
|
+
* consumer for PR 1; PR 4's `manageDomain` / `troubleshoot` will also consume.
|
|
7
|
+
*
|
|
8
|
+
* Exports:
|
|
9
|
+
* - `pickLeasesArray(payload)` — tolerate `{ leases: [...] }` (current
|
|
10
|
+
* chain shape) and a bare array. Throws on anything else.
|
|
11
|
+
* - `normalizeItem(rawItem)` — return `{ serviceName, customDomain }`
|
|
12
|
+
* with empty-string defaults; accepts both camelCase and snake_case.
|
|
13
|
+
* - `findLease(payload, leaseUuid)` — `pickLeasesArray` + UUID lookup.
|
|
14
|
+
* Case-insensitive; tolerates `uuid` / `lease_uuid` / `leaseUuid` keys.
|
|
15
|
+
* Returns the matched lease object (raw shape) or `null`. Throws
|
|
16
|
+
* `TypeError` when `leaseUuid` is not a string.
|
|
17
|
+
*/
|
|
18
|
+
interface NormalizedLeaseItem {
|
|
19
|
+
serviceName: string;
|
|
20
|
+
customDomain: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Tolerate either `{ leases: [...] }` (current chain shape) or a bare
|
|
24
|
+
* array. Throws on anything else.
|
|
25
|
+
*/
|
|
26
|
+
declare function pickLeasesArray(payload: unknown): unknown[];
|
|
27
|
+
/**
|
|
28
|
+
* Normalize a raw lease-item record (chain snake_case OR proto-decoded
|
|
29
|
+
* camelCase) into `{ serviceName, customDomain }` with empty-string
|
|
30
|
+
* defaults on missing fields.
|
|
31
|
+
*/
|
|
32
|
+
declare function normalizeItem(raw: unknown): NormalizedLeaseItem;
|
|
33
|
+
/**
|
|
34
|
+
* Find a lease by UUID inside a `leases_by_tenant` response. Lookup is
|
|
35
|
+
* case-insensitive and tolerates `uuid`, `lease_uuid`, or `leaseUuid`
|
|
36
|
+
* fields on the lease object. Returns the raw lease record or `null`.
|
|
37
|
+
*
|
|
38
|
+
* Throws `TypeError` if `leaseUuid` is not a string. Both production
|
|
39
|
+
* callers (verify-domain-state, future manageDomain) pre-validate against
|
|
40
|
+
* a UUID regex, but the helper guards anyway — a clear error beats a
|
|
41
|
+
* "Cannot read properties of …" stack trace.
|
|
42
|
+
*/
|
|
43
|
+
declare function findLease(payload: unknown, leaseUuid: string): unknown | null;
|
|
44
|
+
//#endregion
|
|
45
|
+
export { NormalizedLeaseItem, findLease, normalizeItem, pickLeasesArray };
|
|
46
|
+
//# sourceMappingURL=lease-items.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lease-items.d.ts","names":[],"sources":["../../src/internals/lease-items.ts"],"mappings":";;AAiBA;;;;;AASA;;;;;AAmBA;;;;;UA5BiB,mBAAA;EACf,WAAA;EACA,YAAA;AAAA;;;;;iBAOc,eAAA,CAAgB,OAAA;;;;;;iBAmBhB,aAAA,CAAc,GAAA,YAAe,mBAAA;;;;;;;;;;;iBA2B7B,SAAA,CAAU,OAAA,WAAkB,SAAA"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//#region src/internals/lease-items.ts
|
|
2
|
+
/**
|
|
3
|
+
* Tolerate either `{ leases: [...] }` (current chain shape) or a bare
|
|
4
|
+
* array. Throws on anything else.
|
|
5
|
+
*/
|
|
6
|
+
function pickLeasesArray(payload) {
|
|
7
|
+
if (Array.isArray(payload)) return payload;
|
|
8
|
+
if (payload !== null && typeof payload === "object" && Array.isArray(payload.leases)) return payload.leases;
|
|
9
|
+
throw new Error("leases_by_tenant response: expected `leases[]` array or bare array");
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Normalize a raw lease-item record (chain snake_case OR proto-decoded
|
|
13
|
+
* camelCase) into `{ serviceName, customDomain }` with empty-string
|
|
14
|
+
* defaults on missing fields.
|
|
15
|
+
*/
|
|
16
|
+
function normalizeItem(raw) {
|
|
17
|
+
if (raw === null || typeof raw !== "object") return {
|
|
18
|
+
serviceName: "",
|
|
19
|
+
customDomain: ""
|
|
20
|
+
};
|
|
21
|
+
const r = raw;
|
|
22
|
+
return {
|
|
23
|
+
serviceName: readStringOrEmpty(r.serviceName) || readStringOrEmpty(r.service_name),
|
|
24
|
+
customDomain: readStringOrEmpty(r.customDomain) || readStringOrEmpty(r.custom_domain)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Find a lease by UUID inside a `leases_by_tenant` response. Lookup is
|
|
29
|
+
* case-insensitive and tolerates `uuid`, `lease_uuid`, or `leaseUuid`
|
|
30
|
+
* fields on the lease object. Returns the raw lease record or `null`.
|
|
31
|
+
*
|
|
32
|
+
* Throws `TypeError` if `leaseUuid` is not a string. Both production
|
|
33
|
+
* callers (verify-domain-state, future manageDomain) pre-validate against
|
|
34
|
+
* a UUID regex, but the helper guards anyway — a clear error beats a
|
|
35
|
+
* "Cannot read properties of …" stack trace.
|
|
36
|
+
*/
|
|
37
|
+
function findLease(payload, leaseUuid) {
|
|
38
|
+
if (typeof leaseUuid !== "string") {
|
|
39
|
+
const got = leaseUuid === null ? "null" : typeof leaseUuid;
|
|
40
|
+
throw new TypeError(`findLease: leaseUuid must be a string, got ${got}`);
|
|
41
|
+
}
|
|
42
|
+
const leases = pickLeasesArray(payload);
|
|
43
|
+
const target = leaseUuid.toLowerCase();
|
|
44
|
+
for (const lease of leases) {
|
|
45
|
+
if (lease === null || typeof lease !== "object") continue;
|
|
46
|
+
const r = lease;
|
|
47
|
+
const u = r.uuid ?? r.lease_uuid ?? r.leaseUuid;
|
|
48
|
+
if (typeof u === "string" && u.toLowerCase() === target) return lease;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
function readStringOrEmpty(value) {
|
|
53
|
+
return typeof value === "string" ? value : "";
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { findLease, normalizeItem, pickLeasesArray };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=lease-items.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lease-items.js","names":[],"sources":["../../src/internals/lease-items.ts"],"sourcesContent":["/**\n * Shared decoding for `leases_by_tenant` responses. Walks `leases[]`,\n * matches by UUID, normalizes each item's serviceName/customDomain across\n * snake_case/camelCase variants. `verify-domain-state.ts` is the in-package\n * consumer for PR 1; PR 4's `manageDomain` / `troubleshoot` will also consume.\n *\n * Exports:\n * - `pickLeasesArray(payload)` — tolerate `{ leases: [...] }` (current\n * chain shape) and a bare array. Throws on anything else.\n * - `normalizeItem(rawItem)` — return `{ serviceName, customDomain }`\n * with empty-string defaults; accepts both camelCase and snake_case.\n * - `findLease(payload, leaseUuid)` — `pickLeasesArray` + UUID lookup.\n * Case-insensitive; tolerates `uuid` / `lease_uuid` / `leaseUuid` keys.\n * Returns the matched lease object (raw shape) or `null`. Throws\n * `TypeError` when `leaseUuid` is not a string.\n */\n\nexport interface NormalizedLeaseItem {\n serviceName: string;\n customDomain: string;\n}\n\n/**\n * Tolerate either `{ leases: [...] }` (current chain shape) or a bare\n * array. Throws on anything else.\n */\nexport function pickLeasesArray(payload: unknown): unknown[] {\n if (Array.isArray(payload)) return payload;\n if (\n payload !== null &&\n typeof payload === 'object' &&\n Array.isArray((payload as { leases?: unknown }).leases)\n ) {\n return (payload as { leases: unknown[] }).leases;\n }\n throw new Error(\n 'leases_by_tenant response: expected `leases[]` array or bare array',\n );\n}\n\n/**\n * Normalize a raw lease-item record (chain snake_case OR proto-decoded\n * camelCase) into `{ serviceName, customDomain }` with empty-string\n * defaults on missing fields.\n */\nexport function normalizeItem(raw: unknown): NormalizedLeaseItem {\n if (raw === null || typeof raw !== 'object') {\n return { serviceName: '', customDomain: '' };\n }\n const r = raw as {\n serviceName?: unknown;\n service_name?: unknown;\n customDomain?: unknown;\n custom_domain?: unknown;\n };\n const serviceName =\n readStringOrEmpty(r.serviceName) || readStringOrEmpty(r.service_name);\n const customDomain =\n readStringOrEmpty(r.customDomain) || readStringOrEmpty(r.custom_domain);\n return { serviceName, customDomain };\n}\n\n/**\n * Find a lease by UUID inside a `leases_by_tenant` response. Lookup is\n * case-insensitive and tolerates `uuid`, `lease_uuid`, or `leaseUuid`\n * fields on the lease object. Returns the raw lease record or `null`.\n *\n * Throws `TypeError` if `leaseUuid` is not a string. Both production\n * callers (verify-domain-state, future manageDomain) pre-validate against\n * a UUID regex, but the helper guards anyway — a clear error beats a\n * \"Cannot read properties of …\" stack trace.\n */\nexport function findLease(payload: unknown, leaseUuid: string): unknown | null {\n if (typeof leaseUuid !== 'string') {\n const got = leaseUuid === null ? 'null' : typeof leaseUuid;\n throw new TypeError(`findLease: leaseUuid must be a string, got ${got}`);\n }\n const leases = pickLeasesArray(payload);\n const target = leaseUuid.toLowerCase();\n for (const lease of leases) {\n if (lease === null || typeof lease !== 'object') continue;\n const r = lease as {\n uuid?: unknown;\n lease_uuid?: unknown;\n leaseUuid?: unknown;\n };\n const u = r.uuid ?? r.lease_uuid ?? r.leaseUuid;\n if (typeof u === 'string' && u.toLowerCase() === target) {\n return lease;\n }\n }\n return null;\n}\n\nfunction readStringOrEmpty(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n"],"mappings":";;;;;AA0BA,SAAgB,gBAAgB,SAA6B;AAC3D,KAAI,MAAM,QAAQ,QAAQ,CAAE,QAAO;AACnC,KACE,YAAY,QACZ,OAAO,YAAY,YACnB,MAAM,QAAS,QAAiC,OAAO,CAEvD,QAAQ,QAAkC;AAE5C,OAAM,IAAI,MACR,qEACD;;;;;;;AAQH,SAAgB,cAAc,KAAmC;AAC/D,KAAI,QAAQ,QAAQ,OAAO,QAAQ,SACjC,QAAO;EAAE,aAAa;EAAI,cAAc;EAAI;CAE9C,MAAM,IAAI;AAUV,QAAO;EAAE,aAHP,kBAAkB,EAAE,YAAY,IAAI,kBAAkB,EAAE,aAAa;EAGjD,cADpB,kBAAkB,EAAE,aAAa,IAAI,kBAAkB,EAAE,cAAc;EACrC;;;;;;;;;;;;AAatC,SAAgB,UAAU,SAAkB,WAAmC;AAC7E,KAAI,OAAO,cAAc,UAAU;EACjC,MAAM,MAAM,cAAc,OAAO,SAAS,OAAO;AACjD,QAAM,IAAI,UAAU,8CAA8C,MAAM;;CAE1E,MAAM,SAAS,gBAAgB,QAAQ;CACvC,MAAM,SAAS,UAAU,aAAa;AACtC,MAAK,MAAM,SAAS,QAAQ;AAC1B,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU;EACjD,MAAM,IAAI;EAKV,MAAM,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE;AACtC,MAAI,OAAO,MAAM,YAAY,EAAE,aAAa,KAAK,OAC/C,QAAO;;AAGX,QAAO;;AAGT,SAAS,kBAAkB,OAAwB;AACjD,QAAO,OAAO,UAAU,WAAW,QAAQ"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { LeaseStateName } from "../types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/internals/lease-state.d.ts
|
|
4
|
+
declare const TERMINAL_STATES: ReadonlySet<LeaseStateName>;
|
|
5
|
+
/**
|
|
6
|
+
* Decode an integer-or-string lease state into the canonical
|
|
7
|
+
* `LEASE_STATE_*` name. Returns `undefined` for unrecognized input so
|
|
8
|
+
* callers can distinguish "no info" from a known state. Callers that
|
|
9
|
+
* need a display sentinel may widen the return type via `|| 'UNKNOWN'`
|
|
10
|
+
* (or similar) for logging/UI purposes only — `'UNKNOWN'` is NOT a
|
|
11
|
+
* `LeaseStateName` variant, so the widening explicitly opts out of the
|
|
12
|
+
* type narrowing the union provides.
|
|
13
|
+
*
|
|
14
|
+
* Accepts either:
|
|
15
|
+
* - a numeric (or numeric-coercible string) integer matching a STATES key,
|
|
16
|
+
* - a string that already starts with `LEASE_STATE_` (passthrough), in
|
|
17
|
+
* which case the value is returned verbatim — the chain's JSON form
|
|
18
|
+
* emits the name directly via `leaseStateToJSON`, so this branch
|
|
19
|
+
* handles both the codec.toJSON() shape and raw integer emit paths.
|
|
20
|
+
*
|
|
21
|
+
* Unrecognized strings (no `LEASE_STATE_` prefix) and out-of-range integers
|
|
22
|
+
* return `undefined`. The passthrough does not validate the suffix against
|
|
23
|
+
* `LeaseStateName` because the chain proto is the source of truth — if a
|
|
24
|
+
* future enum variant appears, it'll flow through; if a malformed string
|
|
25
|
+
* appears, the caller's typed handling catches it.
|
|
26
|
+
*/
|
|
27
|
+
declare function decode(state: number | string | undefined): LeaseStateName | undefined;
|
|
28
|
+
/** True iff `name` is in the `TERMINAL_STATES` set. Accepts any string for caller convenience. */
|
|
29
|
+
declare function isTerminal(name: string | undefined): boolean;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { TERMINAL_STATES, decode, isTerminal };
|
|
32
|
+
//# sourceMappingURL=lease-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lease-state.d.ts","names":[],"sources":["../../src/internals/lease-state.ts"],"mappings":";;;cAyCa,eAAA,EAAiB,WAAA,CAAY,cAAA;;AAA1C;;;;;AAmCA;;;;;AAcA;;;;;;;;;;;iBAdgB,MAAA,CACd,KAAA,gCACC,cAAA;;iBAYa,UAAA,CAAW,IAAA"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
//#region src/internals/lease-state.ts
|
|
2
|
+
/**
|
|
3
|
+
* Canonical chain lease-state enum table + decode helpers, used by
|
|
4
|
+
* `classify-deploy-response.ts` and (later) `deploy-app.ts` /
|
|
5
|
+
* `close-lease.ts` / `troubleshoot.ts` to translate integer state codes
|
|
6
|
+
* the chain emits into the typed `LeaseStateName` literal union.
|
|
7
|
+
*
|
|
8
|
+
* Aligned with the current `@manifest-network/manifestjs@2.4.1` `LeaseState`
|
|
9
|
+
* proto (see
|
|
10
|
+
* `node_modules/@manifest-network/manifestjs/dist/codegen/liftedinit/billing/v1/types.d.ts`):
|
|
11
|
+
*
|
|
12
|
+
* ```
|
|
13
|
+
* 0 → LEASE_STATE_UNSPECIFIED
|
|
14
|
+
* 1 → LEASE_STATE_PENDING — lease awaiting provider acknowledgement; credit locked, billing not started
|
|
15
|
+
* 2 → LEASE_STATE_ACTIVE — provider acknowledged, resources provisioned, billing accruing
|
|
16
|
+
* 3 → LEASE_STATE_CLOSED — lease closed normally; final settlement occurred
|
|
17
|
+
* 4 → LEASE_STATE_REJECTED — provider rejected the lease; credit returned to tenant
|
|
18
|
+
* 5 → LEASE_STATE_EXPIRED — lease expired while in PENDING (provider did not acknowledge within the timeout); credit returned. Pre-active terminal state, NOT a post-active expiry
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* The proto's `UNRECOGNIZED = -1` enum convenience is NOT included in the
|
|
22
|
+
* STATES map — that value is a TS-enum sentinel for "unknown decode," never
|
|
23
|
+
* a chain emit.
|
|
24
|
+
*
|
|
25
|
+
* `LEASE_STATE_INSUFFICIENT_FUNDS` is retained as an unreachable variant in
|
|
26
|
+
* the frozen `LeaseStateName` union and in `TERMINAL_STATES` for forward-
|
|
27
|
+
* compat — the chain doesn't emit it under v2.1.0, but the public type
|
|
28
|
+
* permits it and the no-op set entry guards against a future chain
|
|
29
|
+
* regression that re-emits it (defense-in-depth).
|
|
30
|
+
*/
|
|
31
|
+
const STATES = {
|
|
32
|
+
0: "LEASE_STATE_UNSPECIFIED",
|
|
33
|
+
1: "LEASE_STATE_PENDING",
|
|
34
|
+
2: "LEASE_STATE_ACTIVE",
|
|
35
|
+
3: "LEASE_STATE_CLOSED",
|
|
36
|
+
4: "LEASE_STATE_REJECTED",
|
|
37
|
+
5: "LEASE_STATE_EXPIRED"
|
|
38
|
+
};
|
|
39
|
+
const TERMINAL_STATES = new Set([
|
|
40
|
+
"LEASE_STATE_CLOSED",
|
|
41
|
+
"LEASE_STATE_REJECTED",
|
|
42
|
+
"LEASE_STATE_EXPIRED",
|
|
43
|
+
"LEASE_STATE_INSUFFICIENT_FUNDS"
|
|
44
|
+
]);
|
|
45
|
+
/**
|
|
46
|
+
* Decode an integer-or-string lease state into the canonical
|
|
47
|
+
* `LEASE_STATE_*` name. Returns `undefined` for unrecognized input so
|
|
48
|
+
* callers can distinguish "no info" from a known state. Callers that
|
|
49
|
+
* need a display sentinel may widen the return type via `|| 'UNKNOWN'`
|
|
50
|
+
* (or similar) for logging/UI purposes only — `'UNKNOWN'` is NOT a
|
|
51
|
+
* `LeaseStateName` variant, so the widening explicitly opts out of the
|
|
52
|
+
* type narrowing the union provides.
|
|
53
|
+
*
|
|
54
|
+
* Accepts either:
|
|
55
|
+
* - a numeric (or numeric-coercible string) integer matching a STATES key,
|
|
56
|
+
* - a string that already starts with `LEASE_STATE_` (passthrough), in
|
|
57
|
+
* which case the value is returned verbatim — the chain's JSON form
|
|
58
|
+
* emits the name directly via `leaseStateToJSON`, so this branch
|
|
59
|
+
* handles both the codec.toJSON() shape and raw integer emit paths.
|
|
60
|
+
*
|
|
61
|
+
* Unrecognized strings (no `LEASE_STATE_` prefix) and out-of-range integers
|
|
62
|
+
* return `undefined`. The passthrough does not validate the suffix against
|
|
63
|
+
* `LeaseStateName` because the chain proto is the source of truth — if a
|
|
64
|
+
* future enum variant appears, it'll flow through; if a malformed string
|
|
65
|
+
* appears, the caller's typed handling catches it.
|
|
66
|
+
*/
|
|
67
|
+
function decode(state) {
|
|
68
|
+
if (typeof state === "string" && state.startsWith("LEASE_STATE_")) return state;
|
|
69
|
+
const n = Number(state);
|
|
70
|
+
if (Number.isInteger(n) && n in STATES) return STATES[n];
|
|
71
|
+
}
|
|
72
|
+
/** True iff `name` is in the `TERMINAL_STATES` set. Accepts any string for caller convenience. */
|
|
73
|
+
function isTerminal(name) {
|
|
74
|
+
if (typeof name !== "string") return false;
|
|
75
|
+
return TERMINAL_STATES.has(name);
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { TERMINAL_STATES, decode, isTerminal };
|
|
79
|
+
|
|
80
|
+
//# sourceMappingURL=lease-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lease-state.js","names":[],"sources":["../../src/internals/lease-state.ts"],"sourcesContent":["import type { LeaseStateName } from '../types.js';\n\n/**\n * Canonical chain lease-state enum table + decode helpers, used by\n * `classify-deploy-response.ts` and (later) `deploy-app.ts` /\n * `close-lease.ts` / `troubleshoot.ts` to translate integer state codes\n * the chain emits into the typed `LeaseStateName` literal union.\n *\n * Aligned with the current `@manifest-network/manifestjs@2.4.1` `LeaseState`\n * proto (see\n * `node_modules/@manifest-network/manifestjs/dist/codegen/liftedinit/billing/v1/types.d.ts`):\n *\n * ```\n * 0 → LEASE_STATE_UNSPECIFIED\n * 1 → LEASE_STATE_PENDING — lease awaiting provider acknowledgement; credit locked, billing not started\n * 2 → LEASE_STATE_ACTIVE — provider acknowledged, resources provisioned, billing accruing\n * 3 → LEASE_STATE_CLOSED — lease closed normally; final settlement occurred\n * 4 → LEASE_STATE_REJECTED — provider rejected the lease; credit returned to tenant\n * 5 → LEASE_STATE_EXPIRED — lease expired while in PENDING (provider did not acknowledge within the timeout); credit returned. Pre-active terminal state, NOT a post-active expiry\n * ```\n *\n * The proto's `UNRECOGNIZED = -1` enum convenience is NOT included in the\n * STATES map — that value is a TS-enum sentinel for \"unknown decode,\" never\n * a chain emit.\n *\n * `LEASE_STATE_INSUFFICIENT_FUNDS` is retained as an unreachable variant in\n * the frozen `LeaseStateName` union and in `TERMINAL_STATES` for forward-\n * compat — the chain doesn't emit it under v2.1.0, but the public type\n * permits it and the no-op set entry guards against a future chain\n * regression that re-emits it (defense-in-depth).\n */\n\nconst STATES = {\n 0: 'LEASE_STATE_UNSPECIFIED',\n 1: 'LEASE_STATE_PENDING',\n 2: 'LEASE_STATE_ACTIVE',\n 3: 'LEASE_STATE_CLOSED',\n 4: 'LEASE_STATE_REJECTED',\n 5: 'LEASE_STATE_EXPIRED',\n} as const satisfies Record<number, LeaseStateName>;\n\nexport const TERMINAL_STATES: ReadonlySet<LeaseStateName> =\n new Set<LeaseStateName>([\n 'LEASE_STATE_CLOSED',\n 'LEASE_STATE_REJECTED',\n 'LEASE_STATE_EXPIRED',\n // Retained as defense-in-depth: unreachable from decode() on the\n // current chain (v2.1.0 proto drops INSUFFICIENT_FUNDS), but still a\n // legal LeaseStateName variant. If a future chain regression re-emits\n // it, terminal-state checks downstream still classify correctly without\n // a coordinated update across deploy-app / close-lease / troubleshoot.\n 'LEASE_STATE_INSUFFICIENT_FUNDS',\n ]);\n\n/**\n * Decode an integer-or-string lease state into the canonical\n * `LEASE_STATE_*` name. Returns `undefined` for unrecognized input so\n * callers can distinguish \"no info\" from a known state. Callers that\n * need a display sentinel may widen the return type via `|| 'UNKNOWN'`\n * (or similar) for logging/UI purposes only — `'UNKNOWN'` is NOT a\n * `LeaseStateName` variant, so the widening explicitly opts out of the\n * type narrowing the union provides.\n *\n * Accepts either:\n * - a numeric (or numeric-coercible string) integer matching a STATES key,\n * - a string that already starts with `LEASE_STATE_` (passthrough), in\n * which case the value is returned verbatim — the chain's JSON form\n * emits the name directly via `leaseStateToJSON`, so this branch\n * handles both the codec.toJSON() shape and raw integer emit paths.\n *\n * Unrecognized strings (no `LEASE_STATE_` prefix) and out-of-range integers\n * return `undefined`. The passthrough does not validate the suffix against\n * `LeaseStateName` because the chain proto is the source of truth — if a\n * future enum variant appears, it'll flow through; if a malformed string\n * appears, the caller's typed handling catches it.\n */\nexport function decode(\n state: number | string | undefined,\n): LeaseStateName | undefined {\n if (typeof state === 'string' && state.startsWith('LEASE_STATE_')) {\n return state as LeaseStateName;\n }\n const n = Number(state);\n if (Number.isInteger(n) && n in STATES) {\n return STATES[n as keyof typeof STATES];\n }\n return undefined;\n}\n\n/** True iff `name` is in the `TERMINAL_STATES` set. Accepts any string for caller convenience. */\nexport function isTerminal(name: string | undefined): boolean {\n if (typeof name !== 'string') return false;\n return TERMINAL_STATES.has(name as LeaseStateName);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAM,SAAS;CACb,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACJ;AAED,MAAa,kBACX,IAAI,IAAoB;CACtB;CACA;CACA;CAMA;CACD,CAAC;;;;;;;;;;;;;;;;;;;;;;;AAwBJ,SAAgB,OACd,OAC4B;AAC5B,KAAI,OAAO,UAAU,YAAY,MAAM,WAAW,eAAe,CAC/D,QAAO;CAET,MAAM,IAAI,OAAO,MAAM;AACvB,KAAI,OAAO,UAAU,EAAE,IAAI,KAAK,OAC9B,QAAO,OAAO;;;AAMlB,SAAgB,WAAW,MAAmC;AAC5D,KAAI,OAAO,SAAS,SAAU,QAAO;AACrC,QAAO,gBAAgB,IAAI,KAAuB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { DenomMap, DeploymentPlanBlock, Plan } from "../types.js";
|
|
2
|
+
//#region src/internals/render-deployment-plan.d.ts
|
|
3
|
+
interface RenderDeploymentPlanInput {
|
|
4
|
+
/** Frozen Plan (summary + readiness + fees). */
|
|
5
|
+
plan: Plan;
|
|
6
|
+
/** Pre-loaded denom map. Default: `EMPTY_DENOM_MAP` (raw on-chain rendering). */
|
|
7
|
+
denomMap?: DenomMap;
|
|
8
|
+
/** Primary image reference — first service's image for stacks. */
|
|
9
|
+
image: string;
|
|
10
|
+
/** SKU tier name (e.g. `docker-micro`, `small`). */
|
|
11
|
+
size: string;
|
|
12
|
+
/** Manifest meta-hash hex from `build_manifest_preview`. */
|
|
13
|
+
metaHash: string;
|
|
14
|
+
/** Optional custom-domain FQDN; presence drives the two-tx fee layout. */
|
|
15
|
+
customDomain?: string;
|
|
16
|
+
/** Optional stack-service holding the custom domain. */
|
|
17
|
+
customDomainService?: string;
|
|
18
|
+
}
|
|
19
|
+
declare function renderDeploymentPlan(input: RenderDeploymentPlanInput): DeploymentPlanBlock;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { RenderDeploymentPlanInput, renderDeploymentPlan };
|
|
22
|
+
//# sourceMappingURL=render-deployment-plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-deployment-plan.d.ts","names":[],"sources":["../../src/internals/render-deployment-plan.ts"],"mappings":";;UAgHiB,yBAAA;;EAEf,IAAA,EAAM,IAAA;EAFkC;EAIxC,QAAA,GAAW,QAAA;EAAQ;EAEnB,KAAA;EAJM;EAMN,IAAA;EAJW;EAMX,QAAA;EAFA;EAIA,YAAA;EAAA;EAEA,mBAAA;AAAA;AAAA,iBAGc,oBAAA,CACd,KAAA,EAAO,yBAAA,GACN,mBAAA"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { EMPTY_DENOM_MAP, humanizeBalances, humanizeCoin } from "./humanize-denom.js";
|
|
2
|
+
//#region src/internals/render-deployment-plan.ts
|
|
3
|
+
/**
|
|
4
|
+
* Render the canonical `DeploymentPlan` block for `deployApp`'s
|
|
5
|
+
* confirmation step. Consumes the typed `Plan` + `FeeEstimate {coins, gas}`
|
|
6
|
+
* shape.
|
|
7
|
+
*
|
|
8
|
+
* **Why this is a renderer, not a builder:** the function consumes a
|
|
9
|
+
* fully-resolved `Plan` (summary + readiness + fees) plus orchestrator-
|
|
10
|
+
* supplied trim data (image / size / metaHash / customDomain). It
|
|
11
|
+
* doesn't compose those inputs — `deployApp.ts` (commit B) constructs
|
|
12
|
+
* the `Plan` from chain queries + estimates and threads it here.
|
|
13
|
+
*
|
|
14
|
+
* **Sync, pure-decision function** (per Q4 Bii pattern): no I/O, no
|
|
15
|
+
* mutation, no implicit lookups. Caller pre-loads the `DenomMap` via
|
|
16
|
+
* `await loadChainDenomMap(chainDataFile)` and passes it in. Default
|
|
17
|
+
* fallback is the no-op `EMPTY_DENOM_MAP` — raw on-chain denoms render
|
|
18
|
+
* verbatim. The `(empty)` literal continues to mark missing balances.
|
|
19
|
+
*
|
|
20
|
+
* **Fee humanization:** the new `FeeEstimate {coins: Coin[], gas}` shape
|
|
21
|
+
* preserves multi-coin precision. The CJS read pre-humanized strings
|
|
22
|
+
* (`--tx-fee "0.0023 MFX"`); the TS port humanizes `fees.coins[0]` at
|
|
23
|
+
* render time using `humanizeCoin`, then concatenates with `(gas <n>)`.
|
|
24
|
+
* Multi-coin fees: humanizes all coins with `humanizeBalances` (comma-
|
|
25
|
+
* separated) and renders the result verbatim — gas suffix is appended
|
|
26
|
+
* once.
|
|
27
|
+
*
|
|
28
|
+
* **`setDomain` fee sentinel:** when `plan.fees.setDomain` is the
|
|
29
|
+
* `{notEstimated: true, reason}` sentinel (approach-3 no-representative-
|
|
30
|
+
* lease fallback), the line emits the explicit "(not estimated — no
|
|
31
|
+
* representative lease...)" message preserving the CJS's user-facing
|
|
32
|
+
* "skipped" semantics.
|
|
33
|
+
*
|
|
34
|
+
* Provider line is intentionally absent (chain selects internally; format-
|
|
35
|
+
* success.ts emits it post-deploy).
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Same-denom single-coin: sum as `BigInt` (the underlying on-chain
|
|
39
|
+
* unit), then humanize the total. Different denom OR multi-coin:
|
|
40
|
+
* `"<a> + <b>"` concat (mirrors the CJS's `sumHumanFees` fallback).
|
|
41
|
+
*
|
|
42
|
+
* Copilot review fix (PR #58 r3250445951): the prior `sumHumanFees`
|
|
43
|
+
* parsed humanized strings to float64, summed, and re-formatted —
|
|
44
|
+
* breaking the BigInt invariant the rest of the denom-humanization
|
|
45
|
+
* pipeline maintains (`humanize-denom.ts:_fmtScaledAmount` is
|
|
46
|
+
* BigInt-based). Realistic create-lease + set-domain fees were tiny
|
|
47
|
+
* so the hit rate was low; the inconsistency was real, and amounts
|
|
48
|
+
* above `Number.MAX_SAFE_INTEGER` (2^53-1) would silently round.
|
|
49
|
+
*
|
|
50
|
+
* Operates on the underlying `FeeEstimate.coins` arrays directly so
|
|
51
|
+
* BigInt precision is preserved through the sum. Humanization
|
|
52
|
+
* happens once, at the end.
|
|
53
|
+
*/
|
|
54
|
+
function sumFees(a, b, denomMap) {
|
|
55
|
+
if (a.coins.length === 1 && b.coins.length === 1) {
|
|
56
|
+
const ca = a.coins[0];
|
|
57
|
+
const cb = b.coins[0];
|
|
58
|
+
if (ca && cb && ca.denom === cb.denom) return humanizeCoin((BigInt(ca.amount) + BigInt(cb.amount)).toString(), ca.denom, denomMap);
|
|
59
|
+
}
|
|
60
|
+
return `${humanizeFeeAmount(a, denomMap)} + ${humanizeFeeAmount(b, denomMap)}`;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Render a `FeeEstimate {coins, gas}` as the user-facing fee string.
|
|
64
|
+
* Empty coins → `(empty)` literal (CJS parity). Single coin → humanized
|
|
65
|
+
* `"<amount> <symbol>"`. Multi-coin → comma-joined.
|
|
66
|
+
*/
|
|
67
|
+
function humanizeFeeAmount(fee, denomMap) {
|
|
68
|
+
if (fee.coins.length === 0) return "(empty)";
|
|
69
|
+
if (fee.coins.length === 1) {
|
|
70
|
+
const c = fee.coins[0];
|
|
71
|
+
if (c === void 0) return "(empty)";
|
|
72
|
+
return humanizeCoin(c.amount, c.denom, denomMap);
|
|
73
|
+
}
|
|
74
|
+
return humanizeBalances(fee.coins, denomMap);
|
|
75
|
+
}
|
|
76
|
+
function formatFeeLine(humanFee, gas) {
|
|
77
|
+
return `${humanFee} (gas ${gas})`;
|
|
78
|
+
}
|
|
79
|
+
function formatSkuPrice(plan, denomMap) {
|
|
80
|
+
const sku = plan.readiness.sku;
|
|
81
|
+
if (sku === null) return "(unknown — SKU has no listed price)";
|
|
82
|
+
return `${humanizeCoin(sku.price.amount, sku.price.denom, denomMap)} / hour`;
|
|
83
|
+
}
|
|
84
|
+
function formatWallet(plan, denomMap) {
|
|
85
|
+
return humanizeBalances(plan.readiness.walletBalances, denomMap);
|
|
86
|
+
}
|
|
87
|
+
function formatCredits(plan, denomMap) {
|
|
88
|
+
const credits = plan.readiness.credits;
|
|
89
|
+
if (credits === null) return "none";
|
|
90
|
+
const balances = credits.availableBalances;
|
|
91
|
+
if (!Array.isArray(balances) || balances.length === 0) return "(empty)";
|
|
92
|
+
return humanizeBalances(balances, denomMap);
|
|
93
|
+
}
|
|
94
|
+
function renderDeploymentPlan(input) {
|
|
95
|
+
const denomMap = input.denomMap ?? EMPTY_DENOM_MAP;
|
|
96
|
+
const { summary } = input.plan;
|
|
97
|
+
const manifestLine = `${summary.format ?? "single"}, services=${summary.serviceCount}, ports=${summary.portCount}, env=${summary.envCount}`;
|
|
98
|
+
const hasDomain = typeof input.customDomain === "string" && input.customDomain.length > 0;
|
|
99
|
+
const createFee = input.plan.fees.createLease;
|
|
100
|
+
const createFeeLine = formatFeeLine(humanizeFeeAmount(createFee, denomMap), createFee.gas);
|
|
101
|
+
const lines = [
|
|
102
|
+
"DeploymentPlan",
|
|
103
|
+
` Image: ${input.image}`,
|
|
104
|
+
` Size: ${input.size}`,
|
|
105
|
+
` Manifest: ${manifestLine}`,
|
|
106
|
+
` meta_hash: ${input.metaHash}`
|
|
107
|
+
];
|
|
108
|
+
if (hasDomain) {
|
|
109
|
+
const target = typeof input.customDomainService === "string" && input.customDomainService.length > 0 ? `-> service ${input.customDomainService}` : "-> single-service lease";
|
|
110
|
+
lines.push(` Custom domain: ${input.customDomain} ${target}`);
|
|
111
|
+
}
|
|
112
|
+
lines.push(` SKU price: ${formatSkuPrice(input.plan, denomMap)}`);
|
|
113
|
+
if (hasDomain) {
|
|
114
|
+
const setDomain = input.plan.fees.setDomain;
|
|
115
|
+
let setDomainLine;
|
|
116
|
+
let setDomainReal = null;
|
|
117
|
+
if (setDomain === void 0) setDomainLine = "(not estimated — agent skipped pre-broadcast simulation, policy violation)";
|
|
118
|
+
else if ("notEstimated" in setDomain) setDomainLine = `(not estimated — ${setDomain.reason})`;
|
|
119
|
+
else {
|
|
120
|
+
setDomainReal = setDomain;
|
|
121
|
+
setDomainLine = formatFeeLine(humanizeFeeAmount(setDomain, denomMap), setDomain.gas);
|
|
122
|
+
}
|
|
123
|
+
lines.push(` Tx fee (create-lease): ${createFeeLine}`);
|
|
124
|
+
lines.push(` Tx fee (set-domain): ${setDomainLine}`);
|
|
125
|
+
const totalLine = setDomainReal !== null ? sumFees(createFee, setDomainReal, denomMap) : "(partial — see fee lines above)";
|
|
126
|
+
lines.push(` Total fee: ${totalLine}`);
|
|
127
|
+
} else lines.push(` Tx fee: ${createFeeLine}`);
|
|
128
|
+
lines.push(` Wallet: ${formatWallet(input.plan, denomMap)}`);
|
|
129
|
+
lines.push(` Credits: ${formatCredits(input.plan, denomMap)}`);
|
|
130
|
+
return { text: lines.join("\n") };
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
export { renderDeploymentPlan };
|
|
134
|
+
|
|
135
|
+
//# sourceMappingURL=render-deployment-plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-deployment-plan.js","names":[],"sources":["../../src/internals/render-deployment-plan.ts"],"sourcesContent":["import type { DeploymentPlanBlock, FeeEstimate, Plan } from '../types.js';\nimport {\n type DenomMap,\n EMPTY_DENOM_MAP,\n humanizeBalances,\n humanizeCoin,\n} from './humanize-denom.js';\n\n/**\n * Render the canonical `DeploymentPlan` block for `deployApp`'s\n * confirmation step. Consumes the typed `Plan` + `FeeEstimate {coins, gas}`\n * shape.\n *\n * **Why this is a renderer, not a builder:** the function consumes a\n * fully-resolved `Plan` (summary + readiness + fees) plus orchestrator-\n * supplied trim data (image / size / metaHash / customDomain). It\n * doesn't compose those inputs — `deployApp.ts` (commit B) constructs\n * the `Plan` from chain queries + estimates and threads it here.\n *\n * **Sync, pure-decision function** (per Q4 Bii pattern): no I/O, no\n * mutation, no implicit lookups. Caller pre-loads the `DenomMap` via\n * `await loadChainDenomMap(chainDataFile)` and passes it in. Default\n * fallback is the no-op `EMPTY_DENOM_MAP` — raw on-chain denoms render\n * verbatim. The `(empty)` literal continues to mark missing balances.\n *\n * **Fee humanization:** the new `FeeEstimate {coins: Coin[], gas}` shape\n * preserves multi-coin precision. The CJS read pre-humanized strings\n * (`--tx-fee \"0.0023 MFX\"`); the TS port humanizes `fees.coins[0]` at\n * render time using `humanizeCoin`, then concatenates with `(gas <n>)`.\n * Multi-coin fees: humanizes all coins with `humanizeBalances` (comma-\n * separated) and renders the result verbatim — gas suffix is appended\n * once.\n *\n * **`setDomain` fee sentinel:** when `plan.fees.setDomain` is the\n * `{notEstimated: true, reason}` sentinel (approach-3 no-representative-\n * lease fallback), the line emits the explicit \"(not estimated — no\n * representative lease...)\" message preserving the CJS's user-facing\n * \"skipped\" semantics.\n *\n * Provider line is intentionally absent (chain selects internally; format-\n * success.ts emits it post-deploy).\n */\n\n/**\n * Same-denom single-coin: sum as `BigInt` (the underlying on-chain\n * unit), then humanize the total. Different denom OR multi-coin:\n * `\"<a> + <b>\"` concat (mirrors the CJS's `sumHumanFees` fallback).\n *\n * Copilot review fix (PR #58 r3250445951): the prior `sumHumanFees`\n * parsed humanized strings to float64, summed, and re-formatted —\n * breaking the BigInt invariant the rest of the denom-humanization\n * pipeline maintains (`humanize-denom.ts:_fmtScaledAmount` is\n * BigInt-based). Realistic create-lease + set-domain fees were tiny\n * so the hit rate was low; the inconsistency was real, and amounts\n * above `Number.MAX_SAFE_INTEGER` (2^53-1) would silently round.\n *\n * Operates on the underlying `FeeEstimate.coins` arrays directly so\n * BigInt precision is preserved through the sum. Humanization\n * happens once, at the end.\n */\nfunction sumFees(a: FeeEstimate, b: FeeEstimate, denomMap: DenomMap): string {\n // Same-denom single-coin: BigInt sum, then humanize.\n if (a.coins.length === 1 && b.coins.length === 1) {\n const ca = a.coins[0];\n const cb = b.coins[0];\n if (ca && cb && ca.denom === cb.denom) {\n const sum = (BigInt(ca.amount) + BigInt(cb.amount)).toString();\n return humanizeCoin(sum, ca.denom, denomMap);\n }\n }\n // Different denom or multi-coin: fall back to concat, mirroring the\n // CJS's behavior. Humanize each side independently.\n return `${humanizeFeeAmount(a, denomMap)} + ${humanizeFeeAmount(b, denomMap)}`;\n}\n\n/**\n * Render a `FeeEstimate {coins, gas}` as the user-facing fee string.\n * Empty coins → `(empty)` literal (CJS parity). Single coin → humanized\n * `\"<amount> <symbol>\"`. Multi-coin → comma-joined.\n */\nfunction humanizeFeeAmount(fee: FeeEstimate, denomMap: DenomMap): string {\n if (fee.coins.length === 0) return '(empty)';\n if (fee.coins.length === 1) {\n const c = fee.coins[0];\n if (c === undefined) return '(empty)';\n return humanizeCoin(c.amount, c.denom, denomMap);\n }\n return humanizeBalances(fee.coins, denomMap);\n}\n\nfunction formatFeeLine(humanFee: string, gas: number): string {\n return `${humanFee} (gas ${gas})`;\n}\n\nfunction formatSkuPrice(plan: Plan, denomMap: DenomMap): string {\n const sku = plan.readiness.sku;\n if (sku === null) return '(unknown — SKU has no listed price)';\n return `${humanizeCoin(sku.price.amount, sku.price.denom, denomMap)} / hour`;\n}\n\nfunction formatWallet(plan: Plan, denomMap: DenomMap): string {\n return humanizeBalances(plan.readiness.walletBalances, denomMap);\n}\n\nfunction formatCredits(plan: Plan, denomMap: DenomMap): string {\n const credits = plan.readiness.credits;\n if (credits === null) return 'none';\n const balances = credits.availableBalances;\n if (!Array.isArray(balances) || balances.length === 0) return '(empty)';\n return humanizeBalances(balances, denomMap);\n}\n\nexport interface RenderDeploymentPlanInput {\n /** Frozen Plan (summary + readiness + fees). */\n plan: Plan;\n /** Pre-loaded denom map. Default: `EMPTY_DENOM_MAP` (raw on-chain rendering). */\n denomMap?: DenomMap;\n /** Primary image reference — first service's image for stacks. */\n image: string;\n /** SKU tier name (e.g. `docker-micro`, `small`). */\n size: string;\n /** Manifest meta-hash hex from `build_manifest_preview`. */\n metaHash: string;\n /** Optional custom-domain FQDN; presence drives the two-tx fee layout. */\n customDomain?: string;\n /** Optional stack-service holding the custom domain. */\n customDomainService?: string;\n}\n\nexport function renderDeploymentPlan(\n input: RenderDeploymentPlanInput,\n): DeploymentPlanBlock {\n const denomMap = input.denomMap ?? EMPTY_DENOM_MAP;\n const { summary } = input.plan;\n\n const manifestLine =\n `${summary.format ?? 'single'}, services=${summary.serviceCount}, ` +\n `ports=${summary.portCount}, env=${summary.envCount}`;\n\n const hasDomain =\n typeof input.customDomain === 'string' && input.customDomain.length > 0;\n\n // Create-lease fee — always present in PlanFees.\n const createFee = input.plan.fees.createLease;\n const createHuman = humanizeFeeAmount(createFee, denomMap);\n const createFeeLine = formatFeeLine(createHuman, createFee.gas);\n\n const lines: string[] = [\n 'DeploymentPlan',\n ` Image: ${input.image}`,\n ` Size: ${input.size}`,\n ` Manifest: ${manifestLine}`,\n ` meta_hash: ${input.metaHash}`,\n ];\n\n if (hasDomain) {\n const target =\n typeof input.customDomainService === 'string' &&\n input.customDomainService.length > 0\n ? `-> service ${input.customDomainService}`\n : '-> single-service lease';\n lines.push(` Custom domain: ${input.customDomain} ${target}`);\n }\n\n lines.push(\n ` SKU price: ${formatSkuPrice(input.plan, denomMap)}`,\n );\n\n if (hasDomain) {\n // Two-tx layout: labeled lines + Total fee. Honors approach-3\n // `notEstimated` sentinel for set-domain pre-broadcast estimation\n // fallback (no representative lease).\n const setDomain = input.plan.fees.setDomain;\n let setDomainLine: string;\n // Capture the typed `FeeEstimate` reference (when the set-domain\n // fee is a real estimate, not the sentinel) so the total-line\n // BigInt sum can operate on `coins` directly via `sumFees`. The\n // prior code parsed humanized strings to float64 — see\n // `sumFees`'s docstring for the precision-loss rationale.\n let setDomainReal: FeeEstimate | null = null;\n if (setDomain === undefined) {\n setDomainLine =\n '(not estimated — agent skipped pre-broadcast simulation, policy violation)';\n } else if ('notEstimated' in setDomain) {\n setDomainLine = `(not estimated — ${setDomain.reason})`;\n } else {\n setDomainReal = setDomain;\n setDomainLine = formatFeeLine(\n humanizeFeeAmount(setDomain, denomMap),\n setDomain.gas,\n );\n }\n\n lines.push(` Tx fee (create-lease): ${createFeeLine}`);\n lines.push(` Tx fee (set-domain): ${setDomainLine}`);\n\n // Total only when both fees are real numbers. Sentinel set-domain\n // fees fall through to the placeholder.\n const totalLine =\n setDomainReal !== null\n ? sumFees(createFee, setDomainReal, denomMap)\n : '(partial — see fee lines above)';\n lines.push(` Total fee: ${totalLine}`);\n } else {\n lines.push(` Tx fee: ${createFeeLine}`);\n }\n\n lines.push(\n ` Wallet: ${formatWallet(input.plan, denomMap)}`,\n );\n lines.push(\n ` Credits: ${formatCredits(input.plan, denomMap)}`,\n );\n\n return { text: lines.join('\\n') };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,SAAS,QAAQ,GAAgB,GAAgB,UAA4B;AAE3E,KAAI,EAAE,MAAM,WAAW,KAAK,EAAE,MAAM,WAAW,GAAG;EAChD,MAAM,KAAK,EAAE,MAAM;EACnB,MAAM,KAAK,EAAE,MAAM;AACnB,MAAI,MAAM,MAAM,GAAG,UAAU,GAAG,MAE9B,QAAO,cADM,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,EAAE,UAAU,EACrC,GAAG,OAAO,SAAS;;AAKhD,QAAO,GAAG,kBAAkB,GAAG,SAAS,CAAC,KAAK,kBAAkB,GAAG,SAAS;;;;;;;AAQ9E,SAAS,kBAAkB,KAAkB,UAA4B;AACvE,KAAI,IAAI,MAAM,WAAW,EAAG,QAAO;AACnC,KAAI,IAAI,MAAM,WAAW,GAAG;EAC1B,MAAM,IAAI,IAAI,MAAM;AACpB,MAAI,MAAM,KAAA,EAAW,QAAO;AAC5B,SAAO,aAAa,EAAE,QAAQ,EAAE,OAAO,SAAS;;AAElD,QAAO,iBAAiB,IAAI,OAAO,SAAS;;AAG9C,SAAS,cAAc,UAAkB,KAAqB;AAC5D,QAAO,GAAG,SAAS,QAAQ,IAAI;;AAGjC,SAAS,eAAe,MAAY,UAA4B;CAC9D,MAAM,MAAM,KAAK,UAAU;AAC3B,KAAI,QAAQ,KAAM,QAAO;AACzB,QAAO,GAAG,aAAa,IAAI,MAAM,QAAQ,IAAI,MAAM,OAAO,SAAS,CAAC;;AAGtE,SAAS,aAAa,MAAY,UAA4B;AAC5D,QAAO,iBAAiB,KAAK,UAAU,gBAAgB,SAAS;;AAGlE,SAAS,cAAc,MAAY,UAA4B;CAC7D,MAAM,UAAU,KAAK,UAAU;AAC/B,KAAI,YAAY,KAAM,QAAO;CAC7B,MAAM,WAAW,QAAQ;AACzB,KAAI,CAAC,MAAM,QAAQ,SAAS,IAAI,SAAS,WAAW,EAAG,QAAO;AAC9D,QAAO,iBAAiB,UAAU,SAAS;;AAoB7C,SAAgB,qBACd,OACqB;CACrB,MAAM,WAAW,MAAM,YAAY;CACnC,MAAM,EAAE,YAAY,MAAM;CAE1B,MAAM,eACJ,GAAG,QAAQ,UAAU,SAAS,aAAa,QAAQ,aAAa,UACvD,QAAQ,UAAU,QAAQ,QAAQ;CAE7C,MAAM,YACJ,OAAO,MAAM,iBAAiB,YAAY,MAAM,aAAa,SAAS;CAGxE,MAAM,YAAY,MAAM,KAAK,KAAK;CAElC,MAAM,gBAAgB,cADF,kBAAkB,WAAW,SAAS,EACT,UAAU,IAAI;CAE/D,MAAM,QAAkB;EACtB;EACA,gCAAgC,MAAM;EACtC,gCAAgC,MAAM;EACtC,gCAAgC;EAChC,gCAAgC,MAAM;EACvC;AAED,KAAI,WAAW;EACb,MAAM,SACJ,OAAO,MAAM,wBAAwB,YACrC,MAAM,oBAAoB,SAAS,IAC/B,cAAc,MAAM,wBACpB;AACN,QAAM,KAAK,gCAAgC,MAAM,aAAa,GAAG,SAAS;;AAG5E,OAAM,KACJ,gCAAgC,eAAe,MAAM,MAAM,SAAS,GACrE;AAED,KAAI,WAAW;EAIb,MAAM,YAAY,MAAM,KAAK,KAAK;EAClC,IAAI;EAMJ,IAAI,gBAAoC;AACxC,MAAI,cAAc,KAAA,EAChB,iBACE;WACO,kBAAkB,UAC3B,iBAAgB,oBAAoB,UAAU,OAAO;OAChD;AACL,mBAAgB;AAChB,mBAAgB,cACd,kBAAkB,WAAW,SAAS,EACtC,UAAU,IACX;;AAGH,QAAM,KAAK,gCAAgC,gBAAgB;AAC3D,QAAM,KAAK,gCAAgC,gBAAgB;EAI3D,MAAM,YACJ,kBAAkB,OACd,QAAQ,WAAW,eAAe,SAAS,GAC3C;AACN,QAAM,KAAK,gCAAgC,YAAY;OAEvD,OAAM,KAAK,gCAAgC,gBAAgB;AAG7D,OAAM,KACJ,gCAAgC,aAAa,MAAM,MAAM,SAAS,GACnE;AACD,OAAM,KACJ,gCAAgC,cAAc,MAAM,MAAM,SAAS,GACpE;AAED,QAAO,EAAE,MAAM,MAAM,KAAK,KAAK,EAAE"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { DeploySpec } from "../types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/internals/render-intent-recap.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Render the structural portion of the intent-recap block shown to the user
|
|
6
|
+
* before any chain round-trips in the deploy-app orchestrator.
|
|
7
|
+
*
|
|
8
|
+
* The 4 deterministic items the recap covers:
|
|
9
|
+
*
|
|
10
|
+
* 1. Deployment surface (service count + per-service `name — image`)
|
|
11
|
+
* 2. Connectivity (per-port ingress posture)
|
|
12
|
+
* 3. Redacted sensitive-key inventory (env / label keys only; never values)
|
|
13
|
+
* 4. Custom-domain + dual-tx clarifier + mainnet warning (when applicable)
|
|
14
|
+
*
|
|
15
|
+
* The 2 LLM-judgment items ("what you provided vs auto-detected", "heads-up:
|
|
16
|
+
* obvious gaps") stay in prose — the orchestrator appends them between the
|
|
17
|
+
* deterministic block and the `AskUserQuestion` prompt.
|
|
18
|
+
*
|
|
19
|
+
* **Sensitive-value posture:** env values and label values are NEVER
|
|
20
|
+
* surfaced; only keys appear. Mirrors `summarizeSpec`'s contract. FQDNs are
|
|
21
|
+
* not secrets so `customDomain` is surfaced verbatim.
|
|
22
|
+
*
|
|
23
|
+
* **Port-shape handling:** the CJS supports two runtime shapes for ports:
|
|
24
|
+
* - Legacy single-service: `port: number` → renders one ingress=true entry.
|
|
25
|
+
* - Services-map: `ports: Record<portKey, { ingress?: boolean }>` → one
|
|
26
|
+
* entry per port-key with the declared ingress flag (default false).
|
|
27
|
+
*
|
|
28
|
+
* The frozen TS contract narrows `ServiceDef.ports` to `number[]` for the
|
|
29
|
+
* common case; this port also handles the historical Record shape at runtime
|
|
30
|
+
* (matching `summarizeSpec`'s defensive widening) so callers passing
|
|
31
|
+
* unknown-typed input from JSON.parse don't silently drop ports.
|
|
32
|
+
*/
|
|
33
|
+
/** Render output is a multi-paragraph plain-text block, ready to print verbatim. */
|
|
34
|
+
interface RenderIntentRecapInput {
|
|
35
|
+
/** The structured deploy spec (frozen `DeploySpec` shape). */
|
|
36
|
+
spec: DeploySpec;
|
|
37
|
+
/** Active chain — drives the mainnet permanence warning. */
|
|
38
|
+
activeChain: 'testnet' | 'mainnet';
|
|
39
|
+
}
|
|
40
|
+
declare function renderIntentRecap(input: RenderIntentRecapInput): string;
|
|
41
|
+
//#endregion
|
|
42
|
+
export { RenderIntentRecapInput, renderIntentRecap };
|
|
43
|
+
//# sourceMappingURL=render-intent-recap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-intent-recap.d.ts","names":[],"sources":["../../src/internals/render-intent-recap.ts"],"mappings":";;;;;AAuCA;;;;;;;;;AAoBA;;;;;;;;;;;;;;;;;;;UApBiB,sBAAA;;EAEf,IAAA,EAAM,UAAA;;EAEN,WAAA;AAAA;AAAA,iBAgBc,iBAAA,CAAkB,KAAA,EAAO,sBAAA"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { isStackSpec, normalizeServices } from "./spec-normalize.js";
|
|
2
|
+
//#region src/internals/render-intent-recap.ts
|
|
3
|
+
function renderIntentRecap(input) {
|
|
4
|
+
if (input.activeChain !== "testnet" && input.activeChain !== "mainnet") throw new TypeError(`renderIntentRecap: activeChain must be "testnet" or "mainnet"; got "${String(input.activeChain)}"`);
|
|
5
|
+
const services = projectServices(input.spec);
|
|
6
|
+
const blocks = [
|
|
7
|
+
renderServiceList(services, input.activeChain),
|
|
8
|
+
renderConnectivity(services),
|
|
9
|
+
renderRedactedInventory(services)
|
|
10
|
+
];
|
|
11
|
+
const domainBlock = renderCustomDomain(input.spec, input.activeChain);
|
|
12
|
+
if (domainBlock !== null) blocks.push(domainBlock);
|
|
13
|
+
return blocks.join("\n\n");
|
|
14
|
+
}
|
|
15
|
+
function projectServices(spec) {
|
|
16
|
+
return normalizeServices(spec).map(({ name, raw }) => {
|
|
17
|
+
const rawRecord = raw;
|
|
18
|
+
return {
|
|
19
|
+
name,
|
|
20
|
+
image: typeof rawRecord.image === "string" && rawRecord.image.length > 0 ? rawRecord.image : "(unknown image)",
|
|
21
|
+
ports: name === null ? extractPortsLegacy(raw.port) : extractPorts(raw.ports),
|
|
22
|
+
envKeys: extractKeys(rawRecord.env),
|
|
23
|
+
labelKeys: extractKeys(rawRecord.labels)
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Services-map shape: `{ "80": { ingress?: boolean }, "9090": { ... } }`.
|
|
29
|
+
* Ingress flag may be absent — default `false` matches Fred's cluster-private
|
|
30
|
+
* default. Also handles the typed `number[]` shape (frozen `ServiceDef.ports`)
|
|
31
|
+
* by treating each entry as ingress=false (services-map default).
|
|
32
|
+
*/
|
|
33
|
+
function extractPorts(ports) {
|
|
34
|
+
if (Array.isArray(ports)) return ports.filter((p) => typeof p === "number").map((p) => ({
|
|
35
|
+
port: String(p),
|
|
36
|
+
ingress: false
|
|
37
|
+
}));
|
|
38
|
+
if (ports !== null && typeof ports === "object") return Object.entries(ports).map(([port, cfg]) => ({
|
|
39
|
+
port,
|
|
40
|
+
ingress: !!(cfg !== null && typeof cfg === "object" && cfg.ingress)
|
|
41
|
+
}));
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Legacy single-service shape: bare `port: number`. Fred treats this as
|
|
46
|
+
* ingress=true by default — that's the whole point of the simplified
|
|
47
|
+
* shape.
|
|
48
|
+
*
|
|
49
|
+
* Also handles the `number[]` form (the frozen-contract array form):
|
|
50
|
+
* returns one `{ port, ingress: true }` entry per array element, each
|
|
51
|
+
* with `ingress: true` matching the single-service convention. Returns
|
|
52
|
+
* `[]` for any other value (undefined, non-number scalar, non-array
|
|
53
|
+
* object).
|
|
54
|
+
*
|
|
55
|
+
* M2 fix: prior JSDoc incorrectly stated "Returns `[]` for any other
|
|
56
|
+
* value (including `number[]`...)" — empirically wrong per the
|
|
57
|
+
* `Array.isArray(port)` branch below.
|
|
58
|
+
*/
|
|
59
|
+
function extractPortsLegacy(port) {
|
|
60
|
+
if (typeof port === "number") return [{
|
|
61
|
+
port: String(port),
|
|
62
|
+
ingress: true
|
|
63
|
+
}];
|
|
64
|
+
if (Array.isArray(port)) return port.filter((p) => typeof p === "number").map((p) => ({
|
|
65
|
+
port: String(p),
|
|
66
|
+
ingress: true
|
|
67
|
+
}));
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
function extractKeys(obj) {
|
|
71
|
+
if (obj === null || typeof obj !== "object" || Array.isArray(obj)) return [];
|
|
72
|
+
return Object.keys(obj).sort();
|
|
73
|
+
}
|
|
74
|
+
function renderServiceList(services, activeChain) {
|
|
75
|
+
const count = services.length;
|
|
76
|
+
const lines = [`Deploying ${count} ${count === 1 ? "service" : "services"} on ${activeChain}:`];
|
|
77
|
+
for (const svc of services) {
|
|
78
|
+
const prefix = svc.name === null ? "" : `${svc.name} — `;
|
|
79
|
+
lines.push(` - ${prefix}${svc.image}`);
|
|
80
|
+
}
|
|
81
|
+
return lines.join("\n");
|
|
82
|
+
}
|
|
83
|
+
function renderConnectivity(services) {
|
|
84
|
+
const lines = ["Connectivity:"];
|
|
85
|
+
let total = 0;
|
|
86
|
+
for (const svc of services) {
|
|
87
|
+
if (svc.ports.length === 0) continue;
|
|
88
|
+
for (const p of svc.ports) {
|
|
89
|
+
total += 1;
|
|
90
|
+
const prefix = svc.name === null ? `port ${p.port}` : `${svc.name} port ${p.port}`;
|
|
91
|
+
const reach = p.ingress ? "publicly reachable via the provider's HTTPS subdomain" : "internal only (cluster-private)";
|
|
92
|
+
lines.push(` - ${prefix}: ${reach}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (total === 0) lines.push(" (no ports declared — the deployment will not expose any network surface)");
|
|
96
|
+
return lines.join("\n");
|
|
97
|
+
}
|
|
98
|
+
function renderRedactedInventory(services) {
|
|
99
|
+
const lines = ["Sensitive values are redacted in this recap (keys only, never values):"];
|
|
100
|
+
let anything = false;
|
|
101
|
+
for (const svc of services) {
|
|
102
|
+
const prefix = svc.name === null ? "this service" : svc.name;
|
|
103
|
+
const parts = [];
|
|
104
|
+
if (svc.envKeys.length > 0) {
|
|
105
|
+
anything = true;
|
|
106
|
+
parts.push(`env keys [${svc.envKeys.join(", ")}]`);
|
|
107
|
+
}
|
|
108
|
+
if (svc.labelKeys.length > 0) {
|
|
109
|
+
anything = true;
|
|
110
|
+
parts.push(`label keys [${svc.labelKeys.join(", ")}]`);
|
|
111
|
+
}
|
|
112
|
+
if (parts.length === 0) lines.push(` - ${prefix}: no env or labels supplied`);
|
|
113
|
+
else lines.push(` - ${prefix}: ${parts.join("; ")}`);
|
|
114
|
+
}
|
|
115
|
+
if (!anything) lines.push(" - (no env or labels supplied across any service — nothing to redact)");
|
|
116
|
+
return lines.join("\n");
|
|
117
|
+
}
|
|
118
|
+
function renderCustomDomain(spec, activeChain) {
|
|
119
|
+
const customDomain = spec.customDomain;
|
|
120
|
+
if (typeof customDomain !== "string" || customDomain.length === 0) return null;
|
|
121
|
+
const serviceName = isStackSpec(spec) ? spec.serviceName : void 0;
|
|
122
|
+
const lines = [`Custom domain: ${customDomain} → ${typeof serviceName === "string" && serviceName.length > 0 ? `service ${serviceName}` : "single-service lease"}`];
|
|
123
|
+
lines.push("");
|
|
124
|
+
lines.push("Note: when a custom domain is set, deploy_app broadcasts TWO billing\ntransactions atomically: create-lease AND set-item-custom-domain. The\nsingle permission prompt that fires later covers BOTH; this textual\nrecap is your per-tx review.");
|
|
125
|
+
if (activeChain === "mainnet") {
|
|
126
|
+
lines.push("");
|
|
127
|
+
lines.push(`Mainnet warning: this transaction permanently associates ${customDomain}\nwith this lease on-chain until you --clear it via
|
|
128
|
+
/manifest-agent:manage-domain or close the lease. FQDN squatting is
|
|
129
|
+
irreversible.`);
|
|
130
|
+
}
|
|
131
|
+
return lines.join("\n");
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
export { renderIntentRecap };
|
|
135
|
+
|
|
136
|
+
//# sourceMappingURL=render-intent-recap.js.map
|