@manifest-network/manifest-agent-core 0.11.0 → 0.13.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/dist/close-lease.d.ts.map +1 -1
- package/dist/close-lease.js.map +1 -1
- package/dist/deploy-app.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/internals/build-fred-input.d.ts.map +1 -1
- package/dist/internals/build-fred-input.js.map +1 -1
- package/dist/internals/classify-deploy-error.d.ts +13 -9
- package/dist/internals/classify-deploy-error.d.ts.map +1 -1
- package/dist/internals/classify-deploy-error.js +15 -11
- package/dist/internals/classify-deploy-error.js.map +1 -1
- package/dist/internals/classify-deploy-response.d.ts.map +1 -1
- package/dist/internals/classify-deploy-response.js.map +1 -1
- package/dist/internals/connection.d.ts.map +1 -1
- package/dist/internals/connection.js.map +1 -1
- package/dist/internals/evaluate-readiness-from-fred.js.map +1 -1
- package/dist/internals/evaluate-readiness.d.ts.map +1 -1
- package/dist/internals/evaluate-readiness.js.map +1 -1
- package/dist/internals/find-sku-uuid.d.ts.map +1 -1
- package/dist/internals/find-sku-uuid.js.map +1 -1
- package/dist/internals/format-success.d.ts.map +1 -1
- package/dist/internals/format-success.js.map +1 -1
- package/dist/internals/guarded-fetch.d.ts +1 -1
- package/dist/internals/guarded-fetch.js +1 -1
- package/dist/internals/humanize-denom.d.ts.map +1 -1
- package/dist/internals/humanize-denom.js.map +1 -1
- package/dist/internals/inspect-image.d.ts.map +1 -1
- package/dist/internals/inspect-image.js.map +1 -1
- package/dist/internals/lease-items.d.ts.map +1 -1
- package/dist/internals/lease-items.js.map +1 -1
- package/dist/internals/lease-state.d.ts.map +1 -1
- package/dist/internals/lease-state.js.map +1 -1
- package/dist/internals/render-deployment-plan.d.ts.map +1 -1
- package/dist/internals/render-deployment-plan.js.map +1 -1
- package/dist/internals/render-intent-recap.d.ts.map +1 -1
- package/dist/internals/render-intent-recap.js.map +1 -1
- package/dist/internals/render-partial-success-prompt.d.ts.map +1 -1
- package/dist/internals/render-partial-success-prompt.js.map +1 -1
- package/dist/internals/save-manifest.d.ts.map +1 -1
- package/dist/internals/save-manifest.js.map +1 -1
- package/dist/internals/secret-denylist.d.ts.map +1 -1
- package/dist/internals/secret-denylist.js.map +1 -1
- package/dist/internals/spec-normalize.d.ts.map +1 -1
- package/dist/internals/spec-normalize.js.map +1 -1
- package/dist/internals/verify-domain-state.d.ts.map +1 -1
- package/dist/internals/verify-domain-state.js.map +1 -1
- package/dist/internals/verify-recover.d.ts.map +1 -1
- package/dist/internals/verify-recover.js.map +1 -1
- package/dist/manage-domain.js.map +1 -1
- package/dist/troubleshoot.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spec-normalize.js","names":[],"sources":["../../src/internals/spec-normalize.ts"],"sourcesContent":["import type {\n DeploySpec,\n ServiceDef,\n SingleServiceSpec,\n SpecSummary,\n StackSpec,\n} from '../types.js';\n\n/**\n * Spec normalization + summarization helpers. Exports `isStack`,\n * `firstImage`, `normalizeServices`, `summarizeSpec`, and `validateSpec`\n * (the latter surfaces pre-broadcast shape violations).\n *\n * Two spec shapes are supported (frozen in ENG-128's `types.ts`):\n * - **services-map (StackSpec)** — `{ services: { <name>: ServiceDef }, customDomain?, serviceName? }`\n * - **legacy single-service (SingleServiceSpec)** — `{ image, port?, env?, customDomain? }`\n *\n * `normalizeServices` collapses the two shapes into a single iterable form\n * so callers (Plan summary, manifest builder, etc.) walk one structure\n * regardless of which form the user passed.\n *\n * Validation: `validateSpec` throws a plain `TypeError` on shape violations\n * — agent-core has no workspace dep on `@manifest-network/manifest-mcp-core`\n * in PR 1/2 (per parent's REV 1), so `ManifestMCPError` isn't available\n * here. PR 3's high-level `deployApp` re-wraps `TypeError` into\n * `ManifestMCPError(INVALID_CONFIG)` at the public-API boundary.\n */\n\n/**\n * True when `spec` uses the services-map shape (StackSpec). Mirrors\n * `_spec.cjs#isStack`: `services` is a non-null, non-array object.\n */\nexport function isStackSpec(\n spec: DeploySpec | null | undefined,\n): spec is StackSpec {\n if (spec === null || spec === undefined || typeof spec !== 'object')\n return false;\n const services = (spec as { services?: unknown }).services;\n return (\n services !== null &&\n typeof services === 'object' &&\n !Array.isArray(services)\n );\n}\n\n/**\n * Return the canonical first image string for a spec. For legacy single-\n * service: `spec.image`. For stack: the first non-empty `image` in\n * `Object.values(spec.services)`. Returns `null` when neither shape\n * carries an image (or `spec` is malformed).\n */\nexport function firstImage(spec: DeploySpec | null | undefined): string | null {\n if (spec === null || spec === undefined || typeof spec !== 'object')\n return null;\n const single = spec as Partial<SingleServiceSpec>;\n if (typeof single.image === 'string' && single.image.length > 0) {\n return single.image;\n }\n if (isStackSpec(spec)) {\n for (const svc of Object.values(spec.services)) {\n if (svc !== null && typeof svc === 'object') {\n const image = (svc as Partial<ServiceDef>).image;\n if (typeof image === 'string' && image.length > 0) return image;\n }\n }\n }\n return null;\n}\n\n/**\n * Walk a spec as `[{name, raw}]` where:\n * - `name === null` for legacy single-service (only one entry, raw is the spec itself).\n * - `name === <key>` for each services-map entry; `raw` is the per-service ServiceDef.\n *\n * Stable iteration order matches `Object.entries` (insertion order in v8/modern engines).\n */\nexport interface NormalizedService {\n /** `null` for legacy single-service; the services-map key for stack leases. */\n name: string | null;\n /** The per-service object exactly as the spec stores it. No field projection. */\n raw: ServiceDef | SingleServiceSpec;\n}\n\nexport function normalizeServices(\n spec: DeploySpec | null | undefined,\n): NormalizedService[] {\n if (isStackSpec(spec)) {\n return Object.entries(spec.services).map(([name, raw]) => ({\n name,\n raw: (raw ?? {}) as ServiceDef,\n }));\n }\n return [\n {\n name: null,\n raw: (spec ?? {}) as SingleServiceSpec,\n },\n ];\n}\n\n/**\n * Produce the frozen `SpecSummary` shape for inclusion in the `Plan`\n * (camelCase fields: `serviceCount`, etc.).\n *\n * Port count rules:\n * - SingleServiceSpec `port: number` → +1 port.\n * - SingleServiceSpec `port: number[]` → +length ports.\n * - ServiceDef `ports: number[]` (per type) → +length ports.\n * - ServiceDef `ports` shaped as a Record (older codepath) → +key count.\n *\n * Env key uniqueness is computed across services (one `env_keys` set\n * spans the whole spec); `envCount` is the size of that set; `envKeys`\n * is sorted ascending.\n */\nexport function summarizeSpec(spec: DeploySpec): SpecSummary {\n const format: 'single' | 'stack' = isStackSpec(spec) ? 'stack' : 'single';\n const services = normalizeServices(spec);\n\n let portCount = 0;\n const envKeys = new Set<string>();\n const images: string[] = [];\n\n for (const { raw: svc } of services) {\n if (svc !== null && typeof svc === 'object') {\n const svcRecord = svc as unknown as Record<string, unknown>;\n const image = svcRecord.image;\n if (typeof image === 'string' && image.length > 0) images.push(image);\n\n const port = svcRecord.port;\n if (typeof port === 'number') portCount += 1;\n else if (Array.isArray(port)) portCount += port.length;\n\n const ports = svcRecord.ports;\n if (Array.isArray(ports)) {\n portCount += ports.length;\n } else if (ports !== null && typeof ports === 'object') {\n portCount += Object.keys(ports).length;\n }\n\n const env = svcRecord.env;\n if (env !== null && typeof env === 'object' && !Array.isArray(env)) {\n for (const k of Object.keys(env)) envKeys.add(k);\n }\n }\n }\n\n return {\n format,\n serviceCount: services.length,\n portCount,\n envCount: envKeys.size,\n envKeys: Array.from(envKeys).sort(),\n images,\n };\n}\n\n/**\n * Validate a `DeploySpec` shape pre-broadcast. Throws `TypeError` on the\n * first violation. The frozen type union (`SingleServiceSpec | StackSpec`)\n * already enforces most structural rules at compile time; this runtime\n * check defends against `unknown`-cast callers and `JSON.parse`-decoded\n * inputs.\n *\n * Rules (mirror fred's `deployApp.ts` input validation):\n * - `spec` must be a non-null object.\n * - Stack: `services` must have ≥1 entry; each entry's `image` must be a\n * non-empty string.\n * - Single: `image` must be a non-empty string.\n * - Mutually exclusive `image` AND `services` not allowed.\n *\n * The high-level `deployApp` in PR 3 layers domain checks on top\n * (`customDomain` shape, `serviceName` membership, etc.).\n */\nexport function validateSpec(spec: DeploySpec | null | undefined): void {\n if (spec === null || spec === undefined || typeof spec !== 'object') {\n throw new TypeError('validateSpec: spec must be a non-null object');\n }\n const record = spec as unknown as Record<string, unknown>;\n\n // Mutual-exclusion gate uses KEY presence (not value validity). This\n // closes the bypass where a caller supplies a malformed `image` value\n // (empty string, number, null) alongside a valid `services` map: the\n // value-based check would silently treat `image` as \"absent\" and accept\n // the spec, but the caller's intent was ambiguous (which shape did they\n // mean?). Rejecting on key-presence forces the caller to delete one key\n // before submission and removes the ambiguity.\n const hasImageKey = 'image' in record;\n const hasServicesKey = 'services' in record;\n if (hasImageKey && hasServicesKey) {\n throw new TypeError(\n 'validateSpec: spec has both `image` and `services` keys; these are mutually exclusive (regardless of value validity)',\n );\n }\n\n // Downstream value-validity check (after the mutual-exclusion gate has\n // ruled out the ambiguous case). An `image` key with a non-string or\n // empty-string value still fails here when `services` is absent.\n const hasImage = typeof record.image === 'string' && record.image.length > 0;\n const hasServices = isStackSpec(spec);\n if (!hasImage && !hasServices) {\n throw new TypeError(\n 'validateSpec: spec must declare either `image` (SingleServiceSpec) or `services` (StackSpec)',\n );\n }\n\n // Copilot review fix (PR #58 r3266786899): `customDomain` shape at\n // the boundary. The orchestrator's `buildFredDeployInput`\n // (`deploy-app.ts:701`) uses a `if (customDomain)` truthiness check,\n // which silently drops `''`, `null`, `false`, `0`, `NaN` from the\n // emitted `fredInput`. A user spec like `{ ..., customDomain: '' }`\n // passes validation today, fred receives `fredInput` WITHOUT the\n // domain, deploy proceeds — the user's requested domain silently\n // not claimed, no error signal.\n //\n // Boundary check: when `customDomain` is present, it must be a\n // non-empty string. `undefined` (key absent) is fine; that's the\n // \"no domain requested\" case. Fires before the stack-customDomain\n // serviceName check (r3249684707) so the user gets a clear\n // customDomain-shape error rather than a misleading\n // requires-serviceName one.\n // Copilot review fix (PR #58 r3267373001): reject whitespace-only\n // strings AND strings with surrounding whitespace (option (i) from\n // the team-lead's brief — strict; let the caller send a clean,\n // already-trimmed value rather than silently trim for them). The\n // prior `cd.length === 0` predicate accepted `' '`, `'\\t\\n'`,\n // and `' app.example.com '`; fred would either accept the\n // surrounding whitespace as part of the domain (correctness bug)\n // or trim-and-reject (worse UX than agent-core's clear error).\n if ('customDomain' in record) {\n const cd = record.customDomain;\n if (cd !== undefined) {\n const isCleanNonEmptyString =\n typeof cd === 'string' && cd.length > 0 && cd.trim() === cd;\n if (!isCleanNonEmptyString) {\n const got =\n typeof cd === 'string'\n ? cd.trim().length === 0\n ? `\"${cd}\"`\n : `\"${cd}\" (has surrounding whitespace)`\n : cd === null\n ? 'null'\n : typeof cd;\n throw new TypeError(\n `validateSpec: \\`customDomain\\` must be a non-empty trimmed string or absent (got ${got}).`,\n );\n }\n }\n }\n\n if (hasServices) {\n const entries = Object.entries(spec.services);\n if (entries.length === 0) {\n throw new TypeError(\n 'validateSpec: stack spec `services` must have at least one entry',\n );\n }\n for (const [name, svc] of entries) {\n if (svc === null || typeof svc !== 'object') {\n throw new TypeError(\n `validateSpec: stack service \"${name}\" must be a non-null object`,\n );\n }\n const image = (svc as Partial<ServiceDef>).image;\n if (typeof image !== 'string' || image.length === 0) {\n throw new TypeError(\n `validateSpec: stack service \"${name}\" must declare a non-empty \\`image\\` string`,\n );\n }\n }\n\n // Copilot review fix (PR #58 r3249684707): a stack spec with a\n // `customDomain` MUST declare which service receives the domain\n // via `serviceName`, and that value must be a key in `services`.\n // Without this guard, `customDomainServiceOf` in `deploy-app.ts`\n // returns `undefined`, planning proceeds with no target, renderers\n // misrepresent the claim, and fred rejects the set-domain tx\n // ONLY after `create-lease` commits — leaving the user with an\n // orphan lease + a failed domain claim. Catching this at\n // validate-time is fail-fast at the boundary.\n //\n // Single-service specs are unaffected: their `customDomain` is\n // claimed against the implicit single lease item — no\n // serviceName disambiguation needed.\n const stackDomain = (spec as Partial<StackSpec>).customDomain;\n if (typeof stackDomain === 'string' && stackDomain.length > 0) {\n const stackServiceName = (spec as Partial<StackSpec>).serviceName;\n if (\n typeof stackServiceName !== 'string' ||\n stackServiceName.length === 0\n ) {\n throw new TypeError(\n 'validateSpec: stack spec with `customDomain` requires `serviceName` identifying which service receives the domain.',\n );\n }\n // Copilot review fix (PR #58 r3250331968): use an own-key check.\n // The `in` operator walks the prototype chain, so `serviceName:\n // 'constructor'` (or `'toString'`, `'hasOwnProperty'`, etc.)\n // would falsely pass against a `services` map that doesn't\n // declare those names. Mirrors fred's own choice at\n // `packages/fred/src/tools/deployApp.ts:254` for cross-package\n // symmetry. `Object.keys().includes()` (not `Object.hasOwn`,\n // which is ES2022 and our `tsdown.config.ts` targets ES2020).\n if (!Object.keys(spec.services).includes(stackServiceName)) {\n throw new TypeError(\n `validateSpec: stack spec \\`serviceName\\` \"${stackServiceName}\" must be a key in \\`services\\` (got services: [${Object.keys(spec.services).join(', ')}]).`,\n );\n }\n }\n } else {\n // Single-service spec port requirement.\n //\n // Copilot review fix (PR #58 r3249097051): fred's image-mode rejects\n // portless inputs with `port is required when using image`\n // (`packages/fred/src/tools/deployApp.ts:202` +\n // `packages/fred/src/tools/buildManifestPreview.ts:181`). Without\n // an agent-core boundary check the orchestrator silently passed\n // `port: undefined` through `buildManifestPreviewInput` /\n // `buildFredDeployInput`, surfacing fred's error mid-orchestration\n // (after readiness check + plan render). Failing fast at validate\n // time produces a clearer message and avoids partial work.\n //\n // The escape hatch for genuinely internal-only services is the\n // stack spec — service-level `ports` is optional, so a stack with\n // `{ services: { mysvc: { image, env } } }` deploys without ports.\n //\n // Copilot review fix (PR #58 r3249294877): tighten the predicate to\n // a finite positive integer in the TCP port range. The prior\n // `typeof p === 'number'` check accepted `0`, `NaN`, `Infinity`,\n // negative numbers, non-integers, and out-of-range ports —\n // partially defeating the fail-fast intent. Fred catches `port: 0`\n // via `!input.port`, but the other shapes either flow through to a\n // less helpful error or get coerced silently. The shared predicate\n // `isValidPortNumber` (below) is the single source of truth.\n const port = (spec as Partial<SingleServiceSpec>).port;\n const hasValidPort =\n isValidPortNumber(port) ||\n (Array.isArray(port) && port.length > 0 && port.every(isValidPortNumber));\n if (!hasValidPort) {\n throw new TypeError(\n 'validateSpec: single-service specs require at least one port (port must be a finite positive integer in the TCP range (1-65535), or a non-empty array of such); got ' +\n `port=${JSON.stringify(port)}. For internal-only services, use a stack spec instead.`,\n );\n }\n }\n}\n\n/**\n * Predicate: `p` is a finite positive integer in the TCP port range\n * (1-65535). Used by `validateSpec` to gate single-service `port`\n * shapes against the broad `typeof === 'number'` bypass.\n *\n * Co-located in this module because it's exclusive to the port-\n * validation boundary; if a future caller needs the same check,\n * promote it to a shared utility then.\n */\nfunction isValidPortNumber(p: unknown): p is number {\n return typeof p === 'number' && Number.isInteger(p) && p > 0 && p <= 65535;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"spec-normalize.js","names":[],"sources":["../../src/internals/spec-normalize.ts"],"sourcesContent":["import type {\n DeploySpec,\n ServiceDef,\n SingleServiceSpec,\n SpecSummary,\n StackSpec,\n} from '../types.js';\n\n/**\n * Spec normalization + summarization helpers. Exports `isStack`,\n * `firstImage`, `normalizeServices`, `summarizeSpec`, and `validateSpec`\n * (the latter surfaces pre-broadcast shape violations).\n *\n * Two spec shapes are supported (frozen in ENG-128's `types.ts`):\n * - **services-map (StackSpec)** — `{ services: { <name>: ServiceDef }, customDomain?, serviceName? }`\n * - **legacy single-service (SingleServiceSpec)** — `{ image, port?, env?, customDomain? }`\n *\n * `normalizeServices` collapses the two shapes into a single iterable form\n * so callers (Plan summary, manifest builder, etc.) walk one structure\n * regardless of which form the user passed.\n *\n * Validation: `validateSpec` throws a plain `TypeError` on shape violations\n * — agent-core has no workspace dep on `@manifest-network/manifest-mcp-core`\n * in PR 1/2 (per parent's REV 1), so `ManifestMCPError` isn't available\n * here. PR 3's high-level `deployApp` re-wraps `TypeError` into\n * `ManifestMCPError(INVALID_CONFIG)` at the public-API boundary.\n */\n\n/**\n * True when `spec` uses the services-map shape (StackSpec). Mirrors\n * `_spec.cjs#isStack`: `services` is a non-null, non-array object.\n */\nexport function isStackSpec(\n spec: DeploySpec | null | undefined,\n): spec is StackSpec {\n if (spec === null || spec === undefined || typeof spec !== 'object')\n return false;\n const services = (spec as { services?: unknown }).services;\n return (\n services !== null &&\n typeof services === 'object' &&\n !Array.isArray(services)\n );\n}\n\n/**\n * Return the canonical first image string for a spec. For legacy single-\n * service: `spec.image`. For stack: the first non-empty `image` in\n * `Object.values(spec.services)`. Returns `null` when neither shape\n * carries an image (or `spec` is malformed).\n */\nexport function firstImage(spec: DeploySpec | null | undefined): string | null {\n if (spec === null || spec === undefined || typeof spec !== 'object')\n return null;\n const single = spec as Partial<SingleServiceSpec>;\n if (typeof single.image === 'string' && single.image.length > 0) {\n return single.image;\n }\n if (isStackSpec(spec)) {\n for (const svc of Object.values(spec.services)) {\n if (svc !== null && typeof svc === 'object') {\n const image = (svc as Partial<ServiceDef>).image;\n if (typeof image === 'string' && image.length > 0) return image;\n }\n }\n }\n return null;\n}\n\n/**\n * Walk a spec as `[{name, raw}]` where:\n * - `name === null` for legacy single-service (only one entry, raw is the spec itself).\n * - `name === <key>` for each services-map entry; `raw` is the per-service ServiceDef.\n *\n * Stable iteration order matches `Object.entries` (insertion order in v8/modern engines).\n */\nexport interface NormalizedService {\n /** `null` for legacy single-service; the services-map key for stack leases. */\n name: string | null;\n /** The per-service object exactly as the spec stores it. No field projection. */\n raw: ServiceDef | SingleServiceSpec;\n}\n\nexport function normalizeServices(\n spec: DeploySpec | null | undefined,\n): NormalizedService[] {\n if (isStackSpec(spec)) {\n return Object.entries(spec.services).map(([name, raw]) => ({\n name,\n raw: (raw ?? {}) as ServiceDef,\n }));\n }\n return [\n {\n name: null,\n raw: (spec ?? {}) as SingleServiceSpec,\n },\n ];\n}\n\n/**\n * Produce the frozen `SpecSummary` shape for inclusion in the `Plan`\n * (camelCase fields: `serviceCount`, etc.).\n *\n * Port count rules:\n * - SingleServiceSpec `port: number` → +1 port.\n * - SingleServiceSpec `port: number[]` → +length ports.\n * - ServiceDef `ports: number[]` (per type) → +length ports.\n * - ServiceDef `ports` shaped as a Record (older codepath) → +key count.\n *\n * Env key uniqueness is computed across services (one `env_keys` set\n * spans the whole spec); `envCount` is the size of that set; `envKeys`\n * is sorted ascending.\n */\nexport function summarizeSpec(spec: DeploySpec): SpecSummary {\n const format: 'single' | 'stack' = isStackSpec(spec) ? 'stack' : 'single';\n const services = normalizeServices(spec);\n\n let portCount = 0;\n const envKeys = new Set<string>();\n const images: string[] = [];\n\n for (const { raw: svc } of services) {\n if (svc !== null && typeof svc === 'object') {\n const svcRecord = svc as unknown as Record<string, unknown>;\n const image = svcRecord.image;\n if (typeof image === 'string' && image.length > 0) images.push(image);\n\n const port = svcRecord.port;\n if (typeof port === 'number') portCount += 1;\n else if (Array.isArray(port)) portCount += port.length;\n\n const ports = svcRecord.ports;\n if (Array.isArray(ports)) {\n portCount += ports.length;\n } else if (ports !== null && typeof ports === 'object') {\n portCount += Object.keys(ports).length;\n }\n\n const env = svcRecord.env;\n if (env !== null && typeof env === 'object' && !Array.isArray(env)) {\n for (const k of Object.keys(env)) envKeys.add(k);\n }\n }\n }\n\n return {\n format,\n serviceCount: services.length,\n portCount,\n envCount: envKeys.size,\n envKeys: Array.from(envKeys).sort(),\n images,\n };\n}\n\n/**\n * Validate a `DeploySpec` shape pre-broadcast. Throws `TypeError` on the\n * first violation. The frozen type union (`SingleServiceSpec | StackSpec`)\n * already enforces most structural rules at compile time; this runtime\n * check defends against `unknown`-cast callers and `JSON.parse`-decoded\n * inputs.\n *\n * Rules (mirror fred's `deployApp.ts` input validation):\n * - `spec` must be a non-null object.\n * - Stack: `services` must have ≥1 entry; each entry's `image` must be a\n * non-empty string.\n * - Single: `image` must be a non-empty string.\n * - Mutually exclusive `image` AND `services` not allowed.\n *\n * The high-level `deployApp` in PR 3 layers domain checks on top\n * (`customDomain` shape, `serviceName` membership, etc.).\n */\nexport function validateSpec(spec: DeploySpec | null | undefined): void {\n if (spec === null || spec === undefined || typeof spec !== 'object') {\n throw new TypeError('validateSpec: spec must be a non-null object');\n }\n const record = spec as unknown as Record<string, unknown>;\n\n // Mutual-exclusion gate uses KEY presence (not value validity). This\n // closes the bypass where a caller supplies a malformed `image` value\n // (empty string, number, null) alongside a valid `services` map: the\n // value-based check would silently treat `image` as \"absent\" and accept\n // the spec, but the caller's intent was ambiguous (which shape did they\n // mean?). Rejecting on key-presence forces the caller to delete one key\n // before submission and removes the ambiguity.\n const hasImageKey = 'image' in record;\n const hasServicesKey = 'services' in record;\n if (hasImageKey && hasServicesKey) {\n throw new TypeError(\n 'validateSpec: spec has both `image` and `services` keys; these are mutually exclusive (regardless of value validity)',\n );\n }\n\n // Downstream value-validity check (after the mutual-exclusion gate has\n // ruled out the ambiguous case). An `image` key with a non-string or\n // empty-string value still fails here when `services` is absent.\n const hasImage = typeof record.image === 'string' && record.image.length > 0;\n const hasServices = isStackSpec(spec);\n if (!hasImage && !hasServices) {\n throw new TypeError(\n 'validateSpec: spec must declare either `image` (SingleServiceSpec) or `services` (StackSpec)',\n );\n }\n\n // Copilot review fix (PR #58 r3266786899): `customDomain` shape at\n // the boundary. The orchestrator's `buildFredDeployInput`\n // (`deploy-app.ts:701`) uses a `if (customDomain)` truthiness check,\n // which silently drops `''`, `null`, `false`, `0`, `NaN` from the\n // emitted `fredInput`. A user spec like `{ ..., customDomain: '' }`\n // passes validation today, fred receives `fredInput` WITHOUT the\n // domain, deploy proceeds — the user's requested domain silently\n // not claimed, no error signal.\n //\n // Boundary check: when `customDomain` is present, it must be a\n // non-empty string. `undefined` (key absent) is fine; that's the\n // \"no domain requested\" case. Fires before the stack-customDomain\n // serviceName check (r3249684707) so the user gets a clear\n // customDomain-shape error rather than a misleading\n // requires-serviceName one.\n // Copilot review fix (PR #58 r3267373001): reject whitespace-only\n // strings AND strings with surrounding whitespace (option (i) from\n // the team-lead's brief — strict; let the caller send a clean,\n // already-trimmed value rather than silently trim for them). The\n // prior `cd.length === 0` predicate accepted `' '`, `'\\t\\n'`,\n // and `' app.example.com '`; fred would either accept the\n // surrounding whitespace as part of the domain (correctness bug)\n // or trim-and-reject (worse UX than agent-core's clear error).\n if ('customDomain' in record) {\n const cd = record.customDomain;\n if (cd !== undefined) {\n const isCleanNonEmptyString =\n typeof cd === 'string' && cd.length > 0 && cd.trim() === cd;\n if (!isCleanNonEmptyString) {\n const got =\n typeof cd === 'string'\n ? cd.trim().length === 0\n ? `\"${cd}\"`\n : `\"${cd}\" (has surrounding whitespace)`\n : cd === null\n ? 'null'\n : typeof cd;\n throw new TypeError(\n `validateSpec: \\`customDomain\\` must be a non-empty trimmed string or absent (got ${got}).`,\n );\n }\n }\n }\n\n if (hasServices) {\n const entries = Object.entries(spec.services);\n if (entries.length === 0) {\n throw new TypeError(\n 'validateSpec: stack spec `services` must have at least one entry',\n );\n }\n for (const [name, svc] of entries) {\n if (svc === null || typeof svc !== 'object') {\n throw new TypeError(\n `validateSpec: stack service \"${name}\" must be a non-null object`,\n );\n }\n const image = (svc as Partial<ServiceDef>).image;\n if (typeof image !== 'string' || image.length === 0) {\n throw new TypeError(\n `validateSpec: stack service \"${name}\" must declare a non-empty \\`image\\` string`,\n );\n }\n }\n\n // Copilot review fix (PR #58 r3249684707): a stack spec with a\n // `customDomain` MUST declare which service receives the domain\n // via `serviceName`, and that value must be a key in `services`.\n // Without this guard, `customDomainServiceOf` in `deploy-app.ts`\n // returns `undefined`, planning proceeds with no target, renderers\n // misrepresent the claim, and fred rejects the set-domain tx\n // ONLY after `create-lease` commits — leaving the user with an\n // orphan lease + a failed domain claim. Catching this at\n // validate-time is fail-fast at the boundary.\n //\n // Single-service specs are unaffected: their `customDomain` is\n // claimed against the implicit single lease item — no\n // serviceName disambiguation needed.\n const stackDomain = (spec as Partial<StackSpec>).customDomain;\n if (typeof stackDomain === 'string' && stackDomain.length > 0) {\n const stackServiceName = (spec as Partial<StackSpec>).serviceName;\n if (\n typeof stackServiceName !== 'string' ||\n stackServiceName.length === 0\n ) {\n throw new TypeError(\n 'validateSpec: stack spec with `customDomain` requires `serviceName` identifying which service receives the domain.',\n );\n }\n // Copilot review fix (PR #58 r3250331968): use an own-key check.\n // The `in` operator walks the prototype chain, so `serviceName:\n // 'constructor'` (or `'toString'`, `'hasOwnProperty'`, etc.)\n // would falsely pass against a `services` map that doesn't\n // declare those names. Mirrors fred's own choice at\n // `packages/fred/src/tools/deployApp.ts:254` for cross-package\n // symmetry. `Object.keys().includes()` (not `Object.hasOwn`,\n // which is ES2022 and our `tsdown.config.ts` targets ES2020).\n if (!Object.keys(spec.services).includes(stackServiceName)) {\n throw new TypeError(\n `validateSpec: stack spec \\`serviceName\\` \"${stackServiceName}\" must be a key in \\`services\\` (got services: [${Object.keys(spec.services).join(', ')}]).`,\n );\n }\n }\n } else {\n // Single-service spec port requirement.\n //\n // Copilot review fix (PR #58 r3249097051): fred's image-mode rejects\n // portless inputs with `port is required when using image`\n // (`packages/fred/src/tools/deployApp.ts:202` +\n // `packages/fred/src/tools/buildManifestPreview.ts:181`). Without\n // an agent-core boundary check the orchestrator silently passed\n // `port: undefined` through `buildManifestPreviewInput` /\n // `buildFredDeployInput`, surfacing fred's error mid-orchestration\n // (after readiness check + plan render). Failing fast at validate\n // time produces a clearer message and avoids partial work.\n //\n // The escape hatch for genuinely internal-only services is the\n // stack spec — service-level `ports` is optional, so a stack with\n // `{ services: { mysvc: { image, env } } }` deploys without ports.\n //\n // Copilot review fix (PR #58 r3249294877): tighten the predicate to\n // a finite positive integer in the TCP port range. The prior\n // `typeof p === 'number'` check accepted `0`, `NaN`, `Infinity`,\n // negative numbers, non-integers, and out-of-range ports —\n // partially defeating the fail-fast intent. Fred catches `port: 0`\n // via `!input.port`, but the other shapes either flow through to a\n // less helpful error or get coerced silently. The shared predicate\n // `isValidPortNumber` (below) is the single source of truth.\n const port = (spec as Partial<SingleServiceSpec>).port;\n const hasValidPort =\n isValidPortNumber(port) ||\n (Array.isArray(port) && port.length > 0 && port.every(isValidPortNumber));\n if (!hasValidPort) {\n throw new TypeError(\n 'validateSpec: single-service specs require at least one port (port must be a finite positive integer in the TCP range (1-65535), or a non-empty array of such); got ' +\n `port=${JSON.stringify(port)}. For internal-only services, use a stack spec instead.`,\n );\n }\n }\n}\n\n/**\n * Predicate: `p` is a finite positive integer in the TCP port range\n * (1-65535). Used by `validateSpec` to gate single-service `port`\n * shapes against the broad `typeof === 'number'` bypass.\n *\n * Co-located in this module because it's exclusive to the port-\n * validation boundary; if a future caller needs the same check,\n * promote it to a shared utility then.\n */\nfunction isValidPortNumber(p: unknown): p is number {\n return typeof p === 'number' && Number.isInteger(p) && p > 0 && p <= 65535;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify-domain-state.d.ts","names":[],"sources":["../../src/internals/verify-domain-state.ts"],"mappings":";KA8BY,mBAAA;AAAA,UAEK,kBAAA;EACf,OAAA,EAAS,
|
|
1
|
+
{"version":3,"file":"verify-domain-state.d.ts","names":[],"sources":["../../src/internals/verify-domain-state.ts"],"mappings":";KA8BY,mBAAA;AAAA,UAEK,kBAAA;EACf,OAAA,EAAS,mBAAmB;;EAE5B,MAAA;EAL6B;EAO7B,MAAA;AAAA;AAAA,UAGe,gBAAA;EACf,SAAA;EARA;EAUA,WAAA;EARA;EAUA,QAAA;AAAA;AAAA,iBAGc,iBAAA,CACd,aAAA,WACA,IAAA,EAAM,gBAAA,GACL,kBAAkB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify-domain-state.js","names":[],"sources":["../../src/internals/verify-domain-state.ts"],"sourcesContent":["import { findLease, normalizeItem } from './lease-items.js';\n\n/**\n * Verify a lease item's `customDomain` against an expected value after a\n * `set_item_custom_domain` broadcast.\n *\n * Decodes the same lease shape as `lease-items.ts`, then compares the\n * matched item's `customDomain` to the expected FQDN (or empty string for\n * clear-mode). Used by the in-process `verifyAndRecover` driver in PR 1\n * and by the high-level `manageDomain` set/clear flows in PR 4.\n *\n * Outcome semantics:\n * - `'match'` — actual `customDomain` equals expected\n * - `'mismatch'` — actual differs from expected (item carries `actual` for surfacing)\n * - `'not_found'` — lease UUID not present in the verification payload, OR multi-item lease but no `serviceName` supplied, OR `serviceName` not present in the lease's items\n *\n * Single-item leases (legacy 1-item lease with `serviceName === ''`) ignore\n * the `serviceName` argument and always use the only item. Multi-item\n * stack leases require `serviceName` to address the target item.\n *\n * Throws `TypeError` for malformed args (non-string leaseUuid, leaseUuid\n * that doesn't match UUID grammar). The CJS exits 1 via stderr; the TS\n * port surfaces a typed error instead of a synthetic `not_found` result\n * so caller-side argument bugs don't masquerade as a chain-state outcome.\n */\n\n/** Anchored UUID-shape regex (8-4-4-4-12, version-byte lenient — matches `_uuid.cjs#UUID_RE`). */\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\nexport type VerifyDomainOutcome = 'match' | 'mismatch' | 'not_found';\n\nexport interface VerifyDomainResult {\n outcome: VerifyDomainOutcome;\n /** Present when outcome is 'match' or 'mismatch'. The lease item's actual customDomain. */\n actual?: string;\n /** Present when outcome is 'not_found'. Human-readable detail. */\n reason?: string;\n}\n\nexport interface VerifyDomainArgs {\n leaseUuid: string;\n /** DNS label addressing an item inside a stack lease. Omit / leave empty for legacy 1-item leases. */\n serviceName?: string;\n /** FQDN to compare against the chain's stored value. Use '' for clear-mode (post-clear verification). */\n expected: string;\n}\n\nexport function verifyDomainState(\n leasesPayload: unknown,\n args: VerifyDomainArgs,\n): VerifyDomainResult {\n if (typeof args.leaseUuid !== 'string') {\n throw new TypeError(\n `verifyDomainState: leaseUuid must be a string, got ${typeof args.leaseUuid}`,\n );\n }\n if (!UUID_RE.test(args.leaseUuid)) {\n throw new TypeError(\n `verifyDomainState: leaseUuid must be a UUID; got \"${args.leaseUuid}\"`,\n );\n }\n if (typeof args.expected !== 'string') {\n throw new TypeError(\n `verifyDomainState: expected must be a string (use '' for clear-mode), got ${typeof args.expected}`,\n );\n }\n\n const lease = findLease(leasesPayload, args.leaseUuid);\n if (lease === null) {\n return {\n outcome: 'not_found',\n reason: 'lease UUID not found in verification payload',\n };\n }\n\n // The lease shape is opaque to TS — pickLeasesArray + findLease validate\n // structural keys but the items array can be missing or non-array.\n const rawItems = (lease as { items?: unknown }).items;\n const itemsArray = Array.isArray(rawItems) ? rawItems : [];\n const items = itemsArray.map(normalizeItem);\n\n const singleItem = items.length === 1 && items[0]?.serviceName === '';\n const requestedService = (args.serviceName ?? '').trim();\n\n let item: ReturnType<typeof normalizeItem> | undefined;\n if (singleItem) {\n item = items[0];\n } else if (requestedService === '') {\n return {\n outcome: 'not_found',\n reason: 'lease has multiple items but --service-name was not supplied',\n };\n } else {\n item = items.find((i) => i.serviceName === requestedService);\n if (!item) {\n return {\n outcome: 'not_found',\n reason: `service-name \"${requestedService}\" not found in lease items`,\n };\n }\n }\n\n const actual = item?.customDomain ?? '';\n const outcome: VerifyDomainOutcome =\n actual === args.expected ? 'match' : 'mismatch';\n return { outcome, actual };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,MAAM,UACJ;AAoBF,SAAgB,kBACd,eACA,MACoB;
|
|
1
|
+
{"version":3,"file":"verify-domain-state.js","names":[],"sources":["../../src/internals/verify-domain-state.ts"],"sourcesContent":["import { findLease, normalizeItem } from './lease-items.js';\n\n/**\n * Verify a lease item's `customDomain` against an expected value after a\n * `set_item_custom_domain` broadcast.\n *\n * Decodes the same lease shape as `lease-items.ts`, then compares the\n * matched item's `customDomain` to the expected FQDN (or empty string for\n * clear-mode). Used by the in-process `verifyAndRecover` driver in PR 1\n * and by the high-level `manageDomain` set/clear flows in PR 4.\n *\n * Outcome semantics:\n * - `'match'` — actual `customDomain` equals expected\n * - `'mismatch'` — actual differs from expected (item carries `actual` for surfacing)\n * - `'not_found'` — lease UUID not present in the verification payload, OR multi-item lease but no `serviceName` supplied, OR `serviceName` not present in the lease's items\n *\n * Single-item leases (legacy 1-item lease with `serviceName === ''`) ignore\n * the `serviceName` argument and always use the only item. Multi-item\n * stack leases require `serviceName` to address the target item.\n *\n * Throws `TypeError` for malformed args (non-string leaseUuid, leaseUuid\n * that doesn't match UUID grammar). The CJS exits 1 via stderr; the TS\n * port surfaces a typed error instead of a synthetic `not_found` result\n * so caller-side argument bugs don't masquerade as a chain-state outcome.\n */\n\n/** Anchored UUID-shape regex (8-4-4-4-12, version-byte lenient — matches `_uuid.cjs#UUID_RE`). */\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\nexport type VerifyDomainOutcome = 'match' | 'mismatch' | 'not_found';\n\nexport interface VerifyDomainResult {\n outcome: VerifyDomainOutcome;\n /** Present when outcome is 'match' or 'mismatch'. The lease item's actual customDomain. */\n actual?: string;\n /** Present when outcome is 'not_found'. Human-readable detail. */\n reason?: string;\n}\n\nexport interface VerifyDomainArgs {\n leaseUuid: string;\n /** DNS label addressing an item inside a stack lease. Omit / leave empty for legacy 1-item leases. */\n serviceName?: string;\n /** FQDN to compare against the chain's stored value. Use '' for clear-mode (post-clear verification). */\n expected: string;\n}\n\nexport function verifyDomainState(\n leasesPayload: unknown,\n args: VerifyDomainArgs,\n): VerifyDomainResult {\n if (typeof args.leaseUuid !== 'string') {\n throw new TypeError(\n `verifyDomainState: leaseUuid must be a string, got ${typeof args.leaseUuid}`,\n );\n }\n if (!UUID_RE.test(args.leaseUuid)) {\n throw new TypeError(\n `verifyDomainState: leaseUuid must be a UUID; got \"${args.leaseUuid}\"`,\n );\n }\n if (typeof args.expected !== 'string') {\n throw new TypeError(\n `verifyDomainState: expected must be a string (use '' for clear-mode), got ${typeof args.expected}`,\n );\n }\n\n const lease = findLease(leasesPayload, args.leaseUuid);\n if (lease === null) {\n return {\n outcome: 'not_found',\n reason: 'lease UUID not found in verification payload',\n };\n }\n\n // The lease shape is opaque to TS — pickLeasesArray + findLease validate\n // structural keys but the items array can be missing or non-array.\n const rawItems = (lease as { items?: unknown }).items;\n const itemsArray = Array.isArray(rawItems) ? rawItems : [];\n const items = itemsArray.map(normalizeItem);\n\n const singleItem = items.length === 1 && items[0]?.serviceName === '';\n const requestedService = (args.serviceName ?? '').trim();\n\n let item: ReturnType<typeof normalizeItem> | undefined;\n if (singleItem) {\n item = items[0];\n } else if (requestedService === '') {\n return {\n outcome: 'not_found',\n reason: 'lease has multiple items but --service-name was not supplied',\n };\n } else {\n item = items.find((i) => i.serviceName === requestedService);\n if (!item) {\n return {\n outcome: 'not_found',\n reason: `service-name \"${requestedService}\" not found in lease items`,\n };\n }\n }\n\n const actual = item?.customDomain ?? '';\n const outcome: VerifyDomainOutcome =\n actual === args.expected ? 'match' : 'mismatch';\n return { outcome, actual };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,MAAM,UACJ;AAoBF,SAAgB,kBACd,eACA,MACoB;CACpB,IAAI,OAAO,KAAK,cAAc,UAC5B,MAAM,IAAI,UACR,sDAAsD,OAAO,KAAK,WACpE;CAEF,IAAI,CAAC,QAAQ,KAAK,KAAK,SAAS,GAC9B,MAAM,IAAI,UACR,qDAAqD,KAAK,UAAU,EACtE;CAEF,IAAI,OAAO,KAAK,aAAa,UAC3B,MAAM,IAAI,UACR,6EAA6E,OAAO,KAAK,UAC3F;CAGF,MAAM,QAAQ,UAAU,eAAe,KAAK,SAAS;CACrD,IAAI,UAAU,MACZ,OAAO;EACL,SAAS;EACT,QAAQ;CACV;CAKF,MAAM,WAAY,MAA8B;CAEhD,MAAM,SADa,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,GAChC,IAAI,aAAa;CAE1C,MAAM,aAAa,MAAM,WAAW,KAAK,MAAM,IAAI,gBAAgB;CACnE,MAAM,oBAAoB,KAAK,eAAe,IAAI,KAAK;CAEvD,IAAI;CACJ,IAAI,YACF,OAAO,MAAM;MACR,IAAI,qBAAqB,IAC9B,OAAO;EACL,SAAS;EACT,QAAQ;CACV;MACK;EACL,OAAO,MAAM,MAAM,MAAM,EAAE,gBAAgB,gBAAgB;EAC3D,IAAI,CAAC,MACH,OAAO;GACL,SAAS;GACT,QAAQ,iBAAiB,iBAAiB;EAC5C;CAEJ;CAEA,MAAM,SAAS,MAAM,gBAAgB;CAGrC,OAAO;EAAE,SADP,WAAW,KAAK,WAAW,UAAU;EACrB;CAAO;AAC3B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify-recover.d.ts","names":[],"sources":["../../src/internals/verify-recover.ts"],"mappings":";;;;;AAwCA
|
|
1
|
+
{"version":3,"file":"verify-recover.d.ts","names":[],"sources":["../../src/internals/verify-recover.ts"],"mappings":";;;;;AAwCA;;;;AAAoB;AAapB;;;;;;;;;;;;;;;;;;;;;;;;;KAbY,QAAA;AA+BZ;;;;;AAAA,UAlBiB,kBAAA,SAA2B,MAAA;EAsBgB;EAAA,SApBjD,QAAA,EAAU,QAAA;EAoBM;EAAA,SAlBhB,iBAAA;EAkBuB;EAhBhC,oBAAA,GAAuB,UAAA,EAAY,KAAA,KAAU,eAAA;EAc7C;;;;;;;EANA,oBAAA,GAAuB,UAAA,EAAY,KAAA,KAAU,cAAA;AAAA;;KAInC,QAAA,4CAGF,MAAA,sBACL,OAAA,EAAS,QAAA,KAAa,OAAA,CAAQ,cAAA,CAAe,QAAA,EAAU,KAAA;AAG5D;AAAA,UAAiB,cAAA;EACf,OAAA,EAAS,QAAA;EACT,UAAA,EAAY,KAAK;AAAA;;;;;;;AAAA;AAWnB;UAAiB,gBAAA,4CAGP,MAAA;EAAA,SAEC,QAAA,EAAU,QAAA,CAAS,QAAA,EAAU,QAAA,EAAU,KAAA;EAFxC;EAAA,SAIC,aAAA,WAAwB,QAAA;EAFK;EAAA,SAI7B,QAAA,EAAU,OAAA,CACjB,MAAA,CAAO,QAAA,gBAAwB,kBAAA,CAAmB,KAAA;AAAA;AAAA,UAIrC,sBAAA,kCAEP,MAAA;EAER,MAAA;EACA,eAAA,EAAiB,QAAA;EATgB;EAWjC,QAAA,EAAU,QAAA;EACV,iBAAA;EAb0B;EAe1B,UAAA,EAAY,KAAA;EAvBZ;EAyBA,OAAA,GAAU,eAAA;EAvBV;EAyBA,cAAA,GAAiB,cAAA;AAAA;AAAA,UAGF,yBAAA;EA1Ba;;;;;;;;EAmC5B,SAAA,IACE,OAAA,EAAS,eAAA,EACT,OAAA,EAAS,cAAA,OACN,OAAA,CAAQ,cAAA;AAAA;;;AAjC4C;AAI3D;;;;;;;;;;iBA6CsB,gBAAA,4CAGZ,MAAA,mBAER,IAAA,EAAM,gBAAA,CAAiB,QAAA,EAAU,QAAA,EAAU,KAAA,GAC3C,OAAA,EAAS,QAAA,EACT,SAAA,GAAW,yBAAA,GACV,OAAA,CAAQ,sBAAA,CAAuB,QAAA,EAAU,KAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify-recover.js","names":[],"sources":["../../src/internals/verify-recover.ts"],"sourcesContent":["import type {\n FailureEnvelope,\n RecoveryChoice,\n RecoveryOption,\n} from '../types.js';\nimport { stripDenylist } from './secret-denylist.js';\n\n/**\n * In-process verify-and-recover driver. Uses an inline async verifier\n * function (L7: agent-core MUST NOT spawn subprocesses).\n *\n * Out of scope (subprocess-only concerns):\n * - verifier-script path sanitization (no path; verifier is a function)\n * - stdin-source indirection (verifier receives typed context)\n * - argv interpolation (verifier closes over context)\n * - `{{var}}` template interpolation on user_message (recovery options\n * carry their own typed-diagnostic closures via `buildRecoveryOptions`)\n * - `timeout` / `maxBuffer` operational caps (no subprocess; an optional\n * AbortController-based timeout can be added per-verifier if needed)\n * - `NODE_ENV` test-override env vars (none of the above need them)\n *\n * Keeps (in-process security still relevant):\n * - `SECRET_KEY_DENYLIST` strip on the diagnostic before it reaches\n * `buildFailureEnvelope` / `buildRecoveryOptions` / the host callback / the result.\n * - Prototype-pollution guard on `__proto__` / `constructor` / `prototype`\n * in the diagnostic walk (defense for verifier-output objects that\n * could have come via `JSON.parse`).\n * - Branch dispatch: `branches[outcome]` → `branches.__other__` →\n * synthesized `unclassified` fallback (CJS calls it `'other'`; the TS\n * port uses `'__other__'` to avoid collisions with a literal outcome\n * string `'other'`).\n *\n * Branch IDs are an internal, closed-set string-literal union — they\n * identify branches for journal/logging purposes but are NOT part of the\n * public type contract (Option A from ENG-128). The public surface for\n * recovery is the frozen `RecoveryOption[]` array, materialized by each\n * branch's inline `buildRecoveryOptions(diag)` closure.\n */\n\n/** Closed-set internal branch identifier. Surfaces via journal/log only. */\nexport type BranchId =\n | 'partial_success_domain'\n | 'lease_terminal'\n | 'domain_verification_mismatch'\n | 'domain_not_found'\n | 'pending_drift'\n | 'unclassified';\n\n/**\n * Per-branch behavior contract. Authored inline at each high-level\n * function's call site (deployApp, manageDomain, etc.) so the closures\n * can bind diagnostic data into the surfaced label/description text.\n */\nexport interface VerificationBranch<TDiag = Record<string, unknown>> {\n /** Internal id for journal write + log; not surfaced to host callbacks directly. */\n readonly branchId: BranchId;\n /** Pass-through tags for the ENG-124 journal `recovery_actions[]`. Empty when not journaling. */\n readonly journalActionTags: readonly string[];\n /** Synthesize the public `FailureEnvelope` (frozen contract) from the post-strip diagnostic. */\n buildFailureEnvelope: (diagnostic: TDiag) => FailureEnvelope;\n /**\n * Materialize the `RecoveryOption[]` for the host's `onFailure` callback.\n * Returning an empty array marks the branch as inform-only:\n * `verifyAndRecover` will return the failure envelope without invoking\n * `onFailure` so callers don't waste a user prompt asking what to do\n * when there's nothing to choose between.\n */\n buildRecoveryOptions: (diagnostic: TDiag) => RecoveryOption[];\n}\n\n/** Verifier function — async; receives typed context; returns typed outcome + free-form diagnostic. */\nexport type Verifier<\n TContext,\n TOutcome extends string,\n TDiag = Record<string, unknown>,\n> = (context: TContext) => Promise<VerifierResult<TOutcome, TDiag>>;\n\n/** Shape returned by every verifier. `outcome` drives branch selection; `diagnostic` flows into the branch's closures. */\nexport interface VerifierResult<TOutcome extends string, TDiag> {\n outcome: TOutcome;\n diagnostic: TDiag;\n}\n\n/**\n * Verification spec — declarative description of how to verify post-state\n * and dispatch to a recovery branch. Mirrors the CJS spec shape with\n * the subprocess-specific fields dropped.\n *\n * `__other__` is the catch-all branch key, equivalent to the CJS's `'other'`.\n * Renamed to avoid collisions with an outcome literally equal to `'other'`.\n */\nexport interface VerificationSpec<\n TContext,\n TOutcome extends string,\n TDiag = Record<string, unknown>,\n> {\n readonly verifier: Verifier<TContext, TOutcome, TDiag>;\n /** Outcome values that count as success — no branch dispatch, host's `onFailure` is NOT called. */\n readonly successValues: readonly TOutcome[];\n /** Branch dictionary keyed by outcome string. `__other__` is the catch-all fallback. */\n readonly branches: Partial<\n Record<TOutcome | '__other__', VerificationBranch<TDiag>>\n >;\n}\n\nexport interface VerifyAndRecoverResult<\n TOutcome extends string,\n TDiag = Record<string, unknown>,\n> {\n result: 'success' | 'failure';\n verifierOutcome: TOutcome;\n /** `null` on success; the matched branch's id (or `'unclassified'`) on failure. */\n branchId: BranchId | null;\n journalActionTags: readonly string[];\n /** Post-strip diagnostic. Same value the branch closures received. */\n diagnostic: TDiag;\n /** Present iff failure. The synthesized public-surface envelope. */\n failure?: FailureEnvelope;\n /** Present iff failure AND `onFailure` was called AND it returned (i.e., a non-empty `RecoveryOption[]` was presented). */\n recoveryChoice?: RecoveryChoice;\n}\n\nexport interface VerifyAndRecoverCallbacks {\n /**\n * Rich-form failure handler used by `deployApp`. Receives the\n * `FailureEnvelope` synthesized by the matched branch + the closure-\n * built `RecoveryOption[]` and returns the user's pick.\n *\n * Simple-form callers (manageDomain / closeLease / troubleshoot) wrap\n * via an adapter in PR 4 — they don't pass an `onFailure` here directly.\n */\n onFailure?: (\n failure: FailureEnvelope,\n options: RecoveryOption[],\n ) => Promise<RecoveryChoice>;\n}\n\n/**\n * Run the verifier; classify the outcome; on failure, build the public\n * envelope + recovery options and (optionally) invoke the host's\n * `onFailure` callback for a user pick.\n *\n * Throws synchronously on:\n * - Spec runtime-shape violations (missing verifier function, non-array\n * successValues, non-object branches).\n * - Verifier-returned shape violations (missing `outcome` key,\n * non-string `outcome`, missing `diagnostic` key, non-object\n * `diagnostic`).\n * Propagates any error the verifier itself throws.\n */\nexport async function verifyAndRecover<\n TContext,\n TOutcome extends string,\n TDiag = Record<string, unknown>,\n>(\n spec: VerificationSpec<TContext, TOutcome, TDiag>,\n context: TContext,\n callbacks: VerifyAndRecoverCallbacks = {},\n): Promise<VerifyAndRecoverResult<TOutcome, TDiag>> {\n validateSpec(spec);\n\n const verifierResult = await spec.verifier(context);\n validateVerifierResult(verifierResult);\n\n // Strip secret-shaped keys + prototype-pollution keys from the\n // diagnostic BEFORE it flows into any branch closure, host callback,\n // or the result object. The strip is the same posture `_journal.cjs`'s\n // `validateRecord` enforces on the write side.\n const diagnostic = stripDenylist(verifierResult.diagnostic) as TDiag;\n const outcome = verifierResult.outcome;\n\n const isSuccess = spec.successValues.includes(outcome);\n if (isSuccess) {\n return {\n result: 'success',\n verifierOutcome: outcome,\n branchId: null,\n journalActionTags: [],\n diagnostic,\n };\n }\n\n // Failure path: dispatch to named branch, `__other__` fallback, or\n // synthesized `unclassified`.\n const branch = selectBranch<TOutcome, TDiag>(spec.branches, outcome);\n const failure = branch.buildFailureEnvelope(diagnostic);\n const options = branch.buildRecoveryOptions(diagnostic);\n\n // Inform-only branches (lease_terminal, unclassified) return [] for\n // RecoveryOption[]. Surface the failure envelope without prompting\n // the host — there's no choice to present.\n if (options.length === 0 || callbacks.onFailure === undefined) {\n return {\n result: 'failure',\n verifierOutcome: outcome,\n branchId: branch.branchId,\n journalActionTags: branch.journalActionTags,\n diagnostic,\n failure,\n };\n }\n\n const recoveryChoice = await callbacks.onFailure(failure, options);\n return {\n result: 'failure',\n verifierOutcome: outcome,\n branchId: branch.branchId,\n journalActionTags: branch.journalActionTags,\n diagnostic,\n failure,\n recoveryChoice,\n };\n}\n\nfunction validateSpec<TContext, TOutcome extends string, TDiag>(\n spec: VerificationSpec<TContext, TOutcome, TDiag>,\n): void {\n if (spec === null || typeof spec !== 'object') {\n throw new Error('verifyAndRecover: spec must be an object');\n }\n if (typeof spec.verifier !== 'function') {\n throw new Error('verifyAndRecover: spec.verifier must be a function');\n }\n if (!Array.isArray(spec.successValues)) {\n throw new Error('verifyAndRecover: spec.successValues must be an array');\n }\n // `typeof null === 'object'` would otherwise let a `branches: null` value\n // slip past a bare typeof check and silently route every failure through\n // the synthesized `unclassified` branch. Explicit guard mirrors the\n // CJS's null-check at line 256-263 of verify-recover.cjs.\n if (\n spec.branches === null ||\n typeof spec.branches !== 'object' ||\n Array.isArray(spec.branches)\n ) {\n throw new Error('verifyAndRecover: spec.branches must be an object');\n }\n}\n\nfunction validateVerifierResult(\n value: unknown,\n): asserts value is VerifierResult<string, unknown> {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(\n 'verifyAndRecover: verifier must return an object with shape { outcome, diagnostic }',\n );\n }\n const r = value as { outcome?: unknown; diagnostic?: unknown };\n if (typeof r.outcome !== 'string') {\n throw new Error(\n 'verifyAndRecover: verifier result is missing the required \"outcome\" string field',\n );\n }\n if (\n r.diagnostic === null ||\n typeof r.diagnostic !== 'object' ||\n Array.isArray(r.diagnostic)\n ) {\n throw new Error(\n 'verifyAndRecover: verifier result is missing a \"diagnostic\" object field',\n );\n }\n}\n\nfunction selectBranch<TOutcome extends string, TDiag>(\n branches: Partial<Record<TOutcome | '__other__', VerificationBranch<TDiag>>>,\n outcome: TOutcome,\n): VerificationBranch<TDiag> {\n const named = branches[outcome];\n if (named !== undefined) return named;\n const other = branches.__other__;\n if (other !== undefined) return other;\n return synthesizeUnclassified<TDiag>(outcome);\n}\n\n/**\n * Fabricate the `unclassified` fallback when no named branch and no\n * `__other__` catch-all match. Mirrors the CJS behavior at line 222-232:\n * journal action tag is `verify-unclassified`; the recovery options list\n * is empty (inform-only); the failure envelope conveys the unrecognized\n * outcome verbatim in `reason`.\n */\nfunction synthesizeUnclassified<TDiag>(\n outcome: string,\n): VerificationBranch<TDiag> {\n return {\n branchId: 'unclassified',\n journalActionTags: ['verify-unclassified'],\n buildFailureEnvelope: () => ({\n outcome: 'failed',\n reason: `Verifier returned outcome '${outcome}' — unrecognized; no branch matched.`,\n }),\n buildRecoveryOptions: () => [],\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAsJA,eAAsB,iBAKpB,MACA,SACA,YAAuC,EAAE,EACS;AAClD,cAAa,KAAK;CAElB,MAAM,iBAAiB,MAAM,KAAK,SAAS,QAAQ;AACnD,wBAAuB,eAAe;CAMtC,MAAM,aAAa,cAAc,eAAe,WAAW;CAC3D,MAAM,UAAU,eAAe;AAG/B,KADkB,KAAK,cAAc,SAAS,QACjC,CACX,QAAO;EACL,QAAQ;EACR,iBAAiB;EACjB,UAAU;EACV,mBAAmB,EAAE;EACrB;EACD;CAKH,MAAM,SAAS,aAA8B,KAAK,UAAU,QAAQ;CACpE,MAAM,UAAU,OAAO,qBAAqB,WAAW;CACvD,MAAM,UAAU,OAAO,qBAAqB,WAAW;AAKvD,KAAI,QAAQ,WAAW,KAAK,UAAU,cAAc,KAAA,EAClD,QAAO;EACL,QAAQ;EACR,iBAAiB;EACjB,UAAU,OAAO;EACjB,mBAAmB,OAAO;EAC1B;EACA;EACD;CAGH,MAAM,iBAAiB,MAAM,UAAU,UAAU,SAAS,QAAQ;AAClE,QAAO;EACL,QAAQ;EACR,iBAAiB;EACjB,UAAU,OAAO;EACjB,mBAAmB,OAAO;EAC1B;EACA;EACA;EACD;;AAGH,SAAS,aACP,MACM;AACN,KAAI,SAAS,QAAQ,OAAO,SAAS,SACnC,OAAM,IAAI,MAAM,2CAA2C;AAE7D,KAAI,OAAO,KAAK,aAAa,WAC3B,OAAM,IAAI,MAAM,qDAAqD;AAEvE,KAAI,CAAC,MAAM,QAAQ,KAAK,cAAc,CACpC,OAAM,IAAI,MAAM,wDAAwD;AAM1E,KACE,KAAK,aAAa,QAClB,OAAO,KAAK,aAAa,YACzB,MAAM,QAAQ,KAAK,SAAS,CAE5B,OAAM,IAAI,MAAM,oDAAoD;;AAIxE,SAAS,uBACP,OACkD;AAClD,KAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CACrE,OAAM,IAAI,MACR,sFACD;CAEH,MAAM,IAAI;AACV,KAAI,OAAO,EAAE,YAAY,SACvB,OAAM,IAAI,MACR,qFACD;AAEH,KACE,EAAE,eAAe,QACjB,OAAO,EAAE,eAAe,YACxB,MAAM,QAAQ,EAAE,WAAW,CAE3B,OAAM,IAAI,MACR,6EACD;;AAIL,SAAS,aACP,UACA,SAC2B;CAC3B,MAAM,QAAQ,SAAS;AACvB,KAAI,UAAU,KAAA,EAAW,QAAO;CAChC,MAAM,QAAQ,SAAS;AACvB,KAAI,UAAU,KAAA,EAAW,QAAO;AAChC,QAAO,uBAA8B,QAAQ;;;;;;;;;AAU/C,SAAS,uBACP,SAC2B;AAC3B,QAAO;EACL,UAAU;EACV,mBAAmB,CAAC,sBAAsB;EAC1C,6BAA6B;GAC3B,SAAS;GACT,QAAQ,8BAA8B,QAAQ;GAC/C;EACD,4BAA4B,EAAE;EAC/B"}
|
|
1
|
+
{"version":3,"file":"verify-recover.js","names":[],"sources":["../../src/internals/verify-recover.ts"],"sourcesContent":["import type {\n FailureEnvelope,\n RecoveryChoice,\n RecoveryOption,\n} from '../types.js';\nimport { stripDenylist } from './secret-denylist.js';\n\n/**\n * In-process verify-and-recover driver. Uses an inline async verifier\n * function (L7: agent-core MUST NOT spawn subprocesses).\n *\n * Out of scope (subprocess-only concerns):\n * - verifier-script path sanitization (no path; verifier is a function)\n * - stdin-source indirection (verifier receives typed context)\n * - argv interpolation (verifier closes over context)\n * - `{{var}}` template interpolation on user_message (recovery options\n * carry their own typed-diagnostic closures via `buildRecoveryOptions`)\n * - `timeout` / `maxBuffer` operational caps (no subprocess; an optional\n * AbortController-based timeout can be added per-verifier if needed)\n * - `NODE_ENV` test-override env vars (none of the above need them)\n *\n * Keeps (in-process security still relevant):\n * - `SECRET_KEY_DENYLIST` strip on the diagnostic before it reaches\n * `buildFailureEnvelope` / `buildRecoveryOptions` / the host callback / the result.\n * - Prototype-pollution guard on `__proto__` / `constructor` / `prototype`\n * in the diagnostic walk (defense for verifier-output objects that\n * could have come via `JSON.parse`).\n * - Branch dispatch: `branches[outcome]` → `branches.__other__` →\n * synthesized `unclassified` fallback (CJS calls it `'other'`; the TS\n * port uses `'__other__'` to avoid collisions with a literal outcome\n * string `'other'`).\n *\n * Branch IDs are an internal, closed-set string-literal union — they\n * identify branches for journal/logging purposes but are NOT part of the\n * public type contract (Option A from ENG-128). The public surface for\n * recovery is the frozen `RecoveryOption[]` array, materialized by each\n * branch's inline `buildRecoveryOptions(diag)` closure.\n */\n\n/** Closed-set internal branch identifier. Surfaces via journal/log only. */\nexport type BranchId =\n | 'partial_success_domain'\n | 'lease_terminal'\n | 'domain_verification_mismatch'\n | 'domain_not_found'\n | 'pending_drift'\n | 'unclassified';\n\n/**\n * Per-branch behavior contract. Authored inline at each high-level\n * function's call site (deployApp, manageDomain, etc.) so the closures\n * can bind diagnostic data into the surfaced label/description text.\n */\nexport interface VerificationBranch<TDiag = Record<string, unknown>> {\n /** Internal id for journal write + log; not surfaced to host callbacks directly. */\n readonly branchId: BranchId;\n /** Pass-through tags for the ENG-124 journal `recovery_actions[]`. Empty when not journaling. */\n readonly journalActionTags: readonly string[];\n /** Synthesize the public `FailureEnvelope` (frozen contract) from the post-strip diagnostic. */\n buildFailureEnvelope: (diagnostic: TDiag) => FailureEnvelope;\n /**\n * Materialize the `RecoveryOption[]` for the host's `onFailure` callback.\n * Returning an empty array marks the branch as inform-only:\n * `verifyAndRecover` will return the failure envelope without invoking\n * `onFailure` so callers don't waste a user prompt asking what to do\n * when there's nothing to choose between.\n */\n buildRecoveryOptions: (diagnostic: TDiag) => RecoveryOption[];\n}\n\n/** Verifier function — async; receives typed context; returns typed outcome + free-form diagnostic. */\nexport type Verifier<\n TContext,\n TOutcome extends string,\n TDiag = Record<string, unknown>,\n> = (context: TContext) => Promise<VerifierResult<TOutcome, TDiag>>;\n\n/** Shape returned by every verifier. `outcome` drives branch selection; `diagnostic` flows into the branch's closures. */\nexport interface VerifierResult<TOutcome extends string, TDiag> {\n outcome: TOutcome;\n diagnostic: TDiag;\n}\n\n/**\n * Verification spec — declarative description of how to verify post-state\n * and dispatch to a recovery branch. Mirrors the CJS spec shape with\n * the subprocess-specific fields dropped.\n *\n * `__other__` is the catch-all branch key, equivalent to the CJS's `'other'`.\n * Renamed to avoid collisions with an outcome literally equal to `'other'`.\n */\nexport interface VerificationSpec<\n TContext,\n TOutcome extends string,\n TDiag = Record<string, unknown>,\n> {\n readonly verifier: Verifier<TContext, TOutcome, TDiag>;\n /** Outcome values that count as success — no branch dispatch, host's `onFailure` is NOT called. */\n readonly successValues: readonly TOutcome[];\n /** Branch dictionary keyed by outcome string. `__other__` is the catch-all fallback. */\n readonly branches: Partial<\n Record<TOutcome | '__other__', VerificationBranch<TDiag>>\n >;\n}\n\nexport interface VerifyAndRecoverResult<\n TOutcome extends string,\n TDiag = Record<string, unknown>,\n> {\n result: 'success' | 'failure';\n verifierOutcome: TOutcome;\n /** `null` on success; the matched branch's id (or `'unclassified'`) on failure. */\n branchId: BranchId | null;\n journalActionTags: readonly string[];\n /** Post-strip diagnostic. Same value the branch closures received. */\n diagnostic: TDiag;\n /** Present iff failure. The synthesized public-surface envelope. */\n failure?: FailureEnvelope;\n /** Present iff failure AND `onFailure` was called AND it returned (i.e., a non-empty `RecoveryOption[]` was presented). */\n recoveryChoice?: RecoveryChoice;\n}\n\nexport interface VerifyAndRecoverCallbacks {\n /**\n * Rich-form failure handler used by `deployApp`. Receives the\n * `FailureEnvelope` synthesized by the matched branch + the closure-\n * built `RecoveryOption[]` and returns the user's pick.\n *\n * Simple-form callers (manageDomain / closeLease / troubleshoot) wrap\n * via an adapter in PR 4 — they don't pass an `onFailure` here directly.\n */\n onFailure?: (\n failure: FailureEnvelope,\n options: RecoveryOption[],\n ) => Promise<RecoveryChoice>;\n}\n\n/**\n * Run the verifier; classify the outcome; on failure, build the public\n * envelope + recovery options and (optionally) invoke the host's\n * `onFailure` callback for a user pick.\n *\n * Throws synchronously on:\n * - Spec runtime-shape violations (missing verifier function, non-array\n * successValues, non-object branches).\n * - Verifier-returned shape violations (missing `outcome` key,\n * non-string `outcome`, missing `diagnostic` key, non-object\n * `diagnostic`).\n * Propagates any error the verifier itself throws.\n */\nexport async function verifyAndRecover<\n TContext,\n TOutcome extends string,\n TDiag = Record<string, unknown>,\n>(\n spec: VerificationSpec<TContext, TOutcome, TDiag>,\n context: TContext,\n callbacks: VerifyAndRecoverCallbacks = {},\n): Promise<VerifyAndRecoverResult<TOutcome, TDiag>> {\n validateSpec(spec);\n\n const verifierResult = await spec.verifier(context);\n validateVerifierResult(verifierResult);\n\n // Strip secret-shaped keys + prototype-pollution keys from the\n // diagnostic BEFORE it flows into any branch closure, host callback,\n // or the result object. The strip is the same posture `_journal.cjs`'s\n // `validateRecord` enforces on the write side.\n const diagnostic = stripDenylist(verifierResult.diagnostic) as TDiag;\n const outcome = verifierResult.outcome;\n\n const isSuccess = spec.successValues.includes(outcome);\n if (isSuccess) {\n return {\n result: 'success',\n verifierOutcome: outcome,\n branchId: null,\n journalActionTags: [],\n diagnostic,\n };\n }\n\n // Failure path: dispatch to named branch, `__other__` fallback, or\n // synthesized `unclassified`.\n const branch = selectBranch<TOutcome, TDiag>(spec.branches, outcome);\n const failure = branch.buildFailureEnvelope(diagnostic);\n const options = branch.buildRecoveryOptions(diagnostic);\n\n // Inform-only branches (lease_terminal, unclassified) return [] for\n // RecoveryOption[]. Surface the failure envelope without prompting\n // the host — there's no choice to present.\n if (options.length === 0 || callbacks.onFailure === undefined) {\n return {\n result: 'failure',\n verifierOutcome: outcome,\n branchId: branch.branchId,\n journalActionTags: branch.journalActionTags,\n diagnostic,\n failure,\n };\n }\n\n const recoveryChoice = await callbacks.onFailure(failure, options);\n return {\n result: 'failure',\n verifierOutcome: outcome,\n branchId: branch.branchId,\n journalActionTags: branch.journalActionTags,\n diagnostic,\n failure,\n recoveryChoice,\n };\n}\n\nfunction validateSpec<TContext, TOutcome extends string, TDiag>(\n spec: VerificationSpec<TContext, TOutcome, TDiag>,\n): void {\n if (spec === null || typeof spec !== 'object') {\n throw new Error('verifyAndRecover: spec must be an object');\n }\n if (typeof spec.verifier !== 'function') {\n throw new Error('verifyAndRecover: spec.verifier must be a function');\n }\n if (!Array.isArray(spec.successValues)) {\n throw new Error('verifyAndRecover: spec.successValues must be an array');\n }\n // `typeof null === 'object'` would otherwise let a `branches: null` value\n // slip past a bare typeof check and silently route every failure through\n // the synthesized `unclassified` branch. Explicit guard mirrors the\n // CJS's null-check at line 256-263 of verify-recover.cjs.\n if (\n spec.branches === null ||\n typeof spec.branches !== 'object' ||\n Array.isArray(spec.branches)\n ) {\n throw new Error('verifyAndRecover: spec.branches must be an object');\n }\n}\n\nfunction validateVerifierResult(\n value: unknown,\n): asserts value is VerifierResult<string, unknown> {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(\n 'verifyAndRecover: verifier must return an object with shape { outcome, diagnostic }',\n );\n }\n const r = value as { outcome?: unknown; diagnostic?: unknown };\n if (typeof r.outcome !== 'string') {\n throw new Error(\n 'verifyAndRecover: verifier result is missing the required \"outcome\" string field',\n );\n }\n if (\n r.diagnostic === null ||\n typeof r.diagnostic !== 'object' ||\n Array.isArray(r.diagnostic)\n ) {\n throw new Error(\n 'verifyAndRecover: verifier result is missing a \"diagnostic\" object field',\n );\n }\n}\n\nfunction selectBranch<TOutcome extends string, TDiag>(\n branches: Partial<Record<TOutcome | '__other__', VerificationBranch<TDiag>>>,\n outcome: TOutcome,\n): VerificationBranch<TDiag> {\n const named = branches[outcome];\n if (named !== undefined) return named;\n const other = branches.__other__;\n if (other !== undefined) return other;\n return synthesizeUnclassified<TDiag>(outcome);\n}\n\n/**\n * Fabricate the `unclassified` fallback when no named branch and no\n * `__other__` catch-all match. Mirrors the CJS behavior at line 222-232:\n * journal action tag is `verify-unclassified`; the recovery options list\n * is empty (inform-only); the failure envelope conveys the unrecognized\n * outcome verbatim in `reason`.\n */\nfunction synthesizeUnclassified<TDiag>(\n outcome: string,\n): VerificationBranch<TDiag> {\n return {\n branchId: 'unclassified',\n journalActionTags: ['verify-unclassified'],\n buildFailureEnvelope: () => ({\n outcome: 'failed',\n reason: `Verifier returned outcome '${outcome}' — unrecognized; no branch matched.`,\n }),\n buildRecoveryOptions: () => [],\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAsJA,eAAsB,iBAKpB,MACA,SACA,YAAuC,CAAC,GACU;CAClD,aAAa,IAAI;CAEjB,MAAM,iBAAiB,MAAM,KAAK,SAAS,OAAO;CAClD,uBAAuB,cAAc;CAMrC,MAAM,aAAa,cAAc,eAAe,UAAU;CAC1D,MAAM,UAAU,eAAe;CAG/B,IADkB,KAAK,cAAc,SAAS,OAClC,GACV,OAAO;EACL,QAAQ;EACR,iBAAiB;EACjB,UAAU;EACV,mBAAmB,CAAC;EACpB;CACF;CAKF,MAAM,SAAS,aAA8B,KAAK,UAAU,OAAO;CACnE,MAAM,UAAU,OAAO,qBAAqB,UAAU;CACtD,MAAM,UAAU,OAAO,qBAAqB,UAAU;CAKtD,IAAI,QAAQ,WAAW,KAAK,UAAU,cAAc,KAAA,GAClD,OAAO;EACL,QAAQ;EACR,iBAAiB;EACjB,UAAU,OAAO;EACjB,mBAAmB,OAAO;EAC1B;EACA;CACF;CAGF,MAAM,iBAAiB,MAAM,UAAU,UAAU,SAAS,OAAO;CACjE,OAAO;EACL,QAAQ;EACR,iBAAiB;EACjB,UAAU,OAAO;EACjB,mBAAmB,OAAO;EAC1B;EACA;EACA;CACF;AACF;AAEA,SAAS,aACP,MACM;CACN,IAAI,SAAS,QAAQ,OAAO,SAAS,UACnC,MAAM,IAAI,MAAM,0CAA0C;CAE5D,IAAI,OAAO,KAAK,aAAa,YAC3B,MAAM,IAAI,MAAM,oDAAoD;CAEtE,IAAI,CAAC,MAAM,QAAQ,KAAK,aAAa,GACnC,MAAM,IAAI,MAAM,uDAAuD;CAMzE,IACE,KAAK,aAAa,QAClB,OAAO,KAAK,aAAa,YACzB,MAAM,QAAQ,KAAK,QAAQ,GAE3B,MAAM,IAAI,MAAM,mDAAmD;AAEvE;AAEA,SAAS,uBACP,OACkD;CAClD,IAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GACpE,MAAM,IAAI,MACR,qFACF;CAEF,MAAM,IAAI;CACV,IAAI,OAAO,EAAE,YAAY,UACvB,MAAM,IAAI,MACR,oFACF;CAEF,IACE,EAAE,eAAe,QACjB,OAAO,EAAE,eAAe,YACxB,MAAM,QAAQ,EAAE,UAAU,GAE1B,MAAM,IAAI,MACR,4EACF;AAEJ;AAEA,SAAS,aACP,UACA,SAC2B;CAC3B,MAAM,QAAQ,SAAS;CACvB,IAAI,UAAU,KAAA,GAAW,OAAO;CAChC,MAAM,QAAQ,SAAS;CACvB,IAAI,UAAU,KAAA,GAAW,OAAO;CAChC,OAAO,uBAA8B,OAAO;AAC9C;;;;;;;;AASA,SAAS,uBACP,SAC2B;CAC3B,OAAO;EACL,UAAU;EACV,mBAAmB,CAAC,qBAAqB;EACzC,6BAA6B;GAC3B,SAAS;GACT,QAAQ,8BAA8B,QAAQ;EAChD;EACA,4BAA4B,CAAC;CAC/B;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manage-domain.js","names":[],"sources":["../src/manage-domain.ts"],"sourcesContent":["/**\n * Public entry point: orchestrate setting, clearing, or looking up a\n * lease item's custom domain.\n *\n * Composition (mirrors `deploy-app.ts`'s shape):\n *\n * - `set` / `clear` render a confirmation block, optionally call\n * `onConfirm`, broadcast `setItemCustomDomain` against the agent's\n * bound chain client, then verify the post-broadcast on-chain state\n * via `verifyAndRecover` driving `verify-domain-state` over a direct\n * `billing.v1.lease({ leaseUuid })` single-lease query (tenant-\n * agnostic, no pagination edge cases). Branches are inline closures\n * bound to the per-action context; recovery options are intentionally\n * empty so the verifier surfaces failures via the simple-form\n * `onFailure({ reason })` adapter rather than the rich-form\n * `RecoveryOption[]` prompt (manage-domain has no recovery primitives\n * the orchestrator can dispatch; the user re-runs after a real fix).\n *\n * - `lookup` skips broadcast/verify and resolves the FQDN via the\n * `lease_by_custom_domain` chain query; returns `null` lease when\n * the FQDN isn't claimed.\n */\n\nimport {\n ManifestMCPError,\n ManifestMCPErrorCode,\n setItemCustomDomain,\n} from '@manifest-network/manifest-mcp-core';\nimport {\n type VerifyDomainOutcome,\n type VerifyDomainResult,\n verifyDomainState,\n} from './internals/verify-domain-state.js';\nimport {\n type VerificationSpec,\n verifyAndRecover,\n} from './internals/verify-recover.js';\nimport type {\n DeploymentPlanBlock,\n ManageDomainArgs,\n ManageDomainCallbacks,\n ManageDomainOptions,\n ManageDomainResult,\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\n// RFC 1123 hostname: each label 1-63 chars, alphanumeric + hyphens, no leading/\n// trailing hyphen; total ≤253 chars; ≥2 labels (FQDN, not single-label host).\n// Rejects scheme prefixes ('http://'), whitespace, trailing dots, and raw\n// unicode (ASCII punycode `xn--...` is accepted — it matches the regex's\n// `[A-Za-z0-9-]` label character class, which is the standard wire form\n// for IDN labels).\n//\n// Client-side typo gate only. The chain's `MsgSetItemCustomDomain` keeper is\n// the authoritative validator (canonical lowercase, reserved-suffix rules,\n// FQDN format). This anchored regex catches the obvious-malformed-input\n// cases pre-broadcast so we don't waste a tx on `\"\"`, `\" \"`, `\"http://x.y\"`,\n// or `\"not a domain\"`. Anything that passes here still goes through the\n// chain's own validation.\nconst FQDN_RE =\n /^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)+[A-Za-z](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/;\n\nconst SCHEME_PREFIX_RE = /^https?:\\/\\//i;\n\n/**\n * Cosmos SDK / gRPC NotFound message patterns. Match against\n * `Error.message` to distinguish chain-keeper NotFound (treated as\n * \"unclaimed FQDN\" → typed `null` result) from real failures (treated\n * as `QUERY_FAILED` throws). Anchored loose patterns to tolerate\n * different keeper / transport formatting (\"not found\", \"NotFound\",\n * \"no such record\", \"does not exist\").\n *\n * Per CLAUDE.md: use `String.prototype.match()` over `RegExp.test()`\n * to avoid the CI security hook's false-positive on shell-execution\n * tokens.\n */\nconst NOT_FOUND_RES: readonly RegExp[] = [\n /not.?found/i,\n /no.?such/i,\n /does.?not.?exist/i,\n];\n\n/**\n * Set / clear / look up a lease item's custom domain.\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 `setItemCustomDomain()` broadcast step in `set` / `clear`\n * paths. Broadcast errors do NOT invoke `onFailure` — that callback\n * is reserved for post-broadcast verification failures.\n * `setItemCustomDomain` already raises a structured `ManifestMCPError`\n * from the core package; wrapping it again at this layer would be\n * redundant. Callers wanting to react to broadcast errors should\n * catch them at the call site.\n * @throws `ManifestMCPError(TX_FAILED)` when post-broadcast verification\n * reaches a `not_found` / `mismatch` outcome (after `onFailure` has\n * been invoked so the caller can react).\n * @throws `ManifestMCPError(QUERY_FAILED)` when a chain query raises a\n * non-NotFound error (RPC / transport / decoding failure). Two paths\n * surface this:\n * - the `lookup` chain query (`lease_by_custom_domain`); the keeper's\n * `NotFound` on an unclaimed FQDN is surfaced as a typed\n * `{ lease: null }` result, not a throw.\n * - the post-broadcast verify chain query (`billing.v1.lease`) in\n * the `set` / `clear` paths (wrapped inside the verifier closure\n * so the failure flows through `onFailure({ reason })` before the\n * throw).\n * Structured `ManifestMCPError`s raised by the chain client are\n * re-thrown as-is (with `onFailure` invoked first).\n */\nexport async function manageDomain(\n args: ManageDomainArgs,\n callbacks: ManageDomainCallbacks,\n opts: ManageDomainOptions,\n): Promise<ManageDomainResult> {\n validateArgs(args);\n\n if (args.action === 'lookup') {\n return await lookupDomain(args.fqdn, callbacks, opts);\n }\n\n const serviceName = args.serviceName;\n // Trim the FQDN silently for `set` (Copilot review PR #60, comment\n // 3276519081): align with the underlying `setItemCustomDomain`\n // primitive (which trims at `packages/core/src/tools/setItemCustomDomain.ts:78-81`)\n // and with `lookupDomain` (which already trims). `validateArgs`\n // continues to reject the empty / whitespace-only case via\n // `args.fqdn.trim() === ''`; this just normalizes the surviving\n // input.\n const fqdn = args.action === 'set' ? args.fqdn.trim() : '';\n\n // --- Confirmation block ---------------------------------------------\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 manage-domain ${args.action}.`,\n );\n }\n }\n callbacks.onProgress?.({ kind: 'user_confirmed' });\n\n // --- Broadcast ------------------------------------------------------\n const setOpts =\n args.action === 'set'\n ? serviceName\n ? { serviceName }\n : undefined\n : {\n clear: true as const,\n ...(serviceName ? { serviceName } : {}),\n };\n await setItemCustomDomain(opts.clientManager, args.leaseUuid, fqdn, setOpts);\n\n // --- Verify ---------------------------------------------------------\n // Direct single-lease query (Copilot review PR #60, comment 3275999569):\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 //\n // We wrap the single-lease result as `{ leases: [result.lease] }`\n // (or an empty array if the chain returns no match) so\n // `verifyDomainState` stays untouched — its `findLease` walks the\n // same shape, and a `not_found` outcome falls out naturally when the\n // wrapper array is empty.\n const spec: VerificationSpec<\n unknown,\n VerifyDomainOutcome,\n VerifyDomainResult\n > = {\n verifier: async () => {\n // Wrap the chain call in try/catch (Copilot review PR #60,\n // comment 3276419210): 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 ${args.action}-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 const leases = lease === null || lease === undefined ? [] : [lease];\n const decoded = verifyDomainState(\n { leases },\n {\n leaseUuid: args.leaseUuid,\n ...(serviceName ? { serviceName } : {}),\n expected: fqdn,\n },\n );\n return { outcome: decoded.outcome, diagnostic: decoded };\n },\n successValues: ['match'],\n branches: {\n mismatch: {\n branchId: 'domain_verification_mismatch',\n journalActionTags: ['domain-verification-mismatch'],\n buildFailureEnvelope: (d) => ({\n outcome: 'failed',\n reason:\n args.action === 'set'\n ? `Chain shows custom_domain=\"${d.actual ?? ''}\" for lease ${args.leaseUuid}; expected \"${fqdn}\".`\n : `Chain still shows custom_domain=\"${d.actual ?? ''}\" for lease ${args.leaseUuid}; expected cleared.`,\n }),\n buildRecoveryOptions: () => [],\n },\n not_found: {\n branchId: 'domain_not_found',\n journalActionTags: ['domain-verification-not-found'],\n buildFailureEnvelope: (d) => ({\n outcome: 'failed',\n reason:\n d.reason ??\n `Lease ${args.leaseUuid} not found when verifying domain state.`,\n }),\n buildRecoveryOptions: () => [],\n },\n },\n };\n\n const verifyResult = await verifyAndRecover(spec, undefined);\n const verified = verifyResult.result === 'success';\n const finalCustomDomain = deriveFinalCustomDomain(\n verifyResult.diagnostic,\n args.action,\n );\n\n if (!verified) {\n const reason =\n verifyResult.failure?.reason ??\n `manage-domain ${args.action} verification failed.`;\n if (callbacks.onFailure) {\n await callbacks.onFailure({ reason });\n }\n throw new ManifestMCPError(ManifestMCPErrorCode.TX_FAILED, reason);\n }\n\n const result: ManageDomainResult = {\n action: args.action,\n leaseUuid: args.leaseUuid,\n verified,\n finalCustomDomain,\n };\n callbacks.onComplete?.(result);\n return result;\n}\n\n// --- Helpers --------------------------------------------------------\n\nfunction validateArgs(args: ManageDomainArgs): void {\n if (\n args.action !== 'set' &&\n args.action !== 'clear' &&\n args.action !== 'lookup'\n ) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `manageDomain: unknown action \"${(args as { action?: string }).action}\".`,\n );\n }\n if (args.action === 'lookup') {\n if (typeof args.fqdn !== 'string' || args.fqdn.trim() === '') {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'manageDomain lookup: fqdn must be a non-empty string.',\n );\n }\n return;\n }\n if (typeof args.leaseUuid !== 'string' || !args.leaseUuid.match(UUID_RE)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `manageDomain ${args.action}: leaseUuid must be a UUID; got \"${args.leaseUuid}\".`,\n );\n }\n if (args.action === 'set') {\n if (typeof args.fqdn !== 'string' || args.fqdn.trim() === '') {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'manageDomain set: fqdn must be a non-empty string.',\n );\n }\n // Trim silently (Copilot review PR #60, comment 3276519081):\n // align with `setItemCustomDomain` (which trims at\n // `packages/core/src/tools/setItemCustomDomain.ts:78-81`) and with\n // `lookupDomain` (which already trims). Validation gates below\n // run against the trimmed candidate; the broadcast and confirm\n // block (rendered in the caller) also use the trimmed form.\n const candidate = args.fqdn.trim();\n if (candidate.match(SCHEME_PREFIX_RE)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `manageDomain set: fqdn must be a bare hostname (no scheme); got \"${args.fqdn}\".`,\n );\n }\n if (!candidate.match(FQDN_RE)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `manageDomain set: fqdn \"${args.fqdn}\" is not a valid RFC 1123 hostname (≤253 chars, ≥2 dot-separated labels of 1-63 alphanumeric/hyphen chars; no leading/trailing hyphens).`,\n );\n }\n }\n}\n\nfunction renderConfirmationBlock(\n args: Exclude<ManageDomainArgs, { action: 'lookup' }>,\n): DeploymentPlanBlock {\n const lines: string[] = [];\n if (args.action === 'set') {\n lines.push(`Set custom domain on lease ${args.leaseUuid}:`);\n // Display the trimmed FQDN — matches the value that will be\n // broadcast (per the silent-trim semantics aligned with\n // setItemCustomDomain) so users don't see whitespace in the\n // confirm prompt that won't appear on-chain.\n lines.push(` FQDN: ${args.fqdn.trim()}`);\n if (args.serviceName) {\n lines.push(` Service: ${args.serviceName}`);\n }\n lines.push('');\n lines.push('Proceed?');\n } else {\n lines.push(`Clear custom domain on lease ${args.leaseUuid}:`);\n if (args.serviceName) {\n lines.push(` Service: ${args.serviceName}`);\n }\n lines.push('');\n lines.push('Proceed?');\n }\n return { text: lines.join('\\n') };\n}\n\nasync function lookupDomain(\n fqdn: string,\n callbacks: ManageDomainCallbacks,\n opts: ManageDomainOptions,\n): Promise<ManageDomainResult> {\n const customDomain = fqdn.trim();\n let result: unknown;\n try {\n // Pull `getQueryClient()` INSIDE the try (Copilot review PR #60,\n // comment 3276719558). `getQueryClient()` can throw\n // `INVALID_CONFIG` (neither rpcUrl nor restUrl set) or\n // `RPC_CONNECTION_FAILED` (connect failure). Catching here routes\n // those init-time failures through the same `onFailure` +\n // QUERY_FAILED / structured-passthrough normalization the chain-\n // query failure mode already gets. The set/clear verifier closure\n // (in `manageDomain` body above) already wraps `getQueryClient()`\n // since commit d9793c1; this brings `lookupDomain` to parity.\n const queryClient = await opts.clientManager.getQueryClient();\n result = await queryClient.liftedinit.billing.v1.leaseByCustomDomain({\n customDomain,\n });\n } catch (err) {\n // Narrowed disambiguation (Copilot review PR #60): the chain keeper\n // raises a NotFound-shaped error when the FQDN is unclaimed (cosmjs/\n // grpc surfaces this as a plain `Error` whose message matches\n // `/not.?found|no.?such|does.?not.?exist/i`). Only that case is\n // collapsed to the typed `{ lease: null }` result. Every other\n // failure mode (RPC transport, decoding, structured\n // `ManifestMCPError`, etc.) flows through `onFailure({ reason })`\n // then a typed throw — matching the lease-package's\n // `lease_by_custom_domain` handler (packages/lease/src/index.ts:442)\n // and `getBalance`'s `catchNotFound` pattern (packages/core/src/\n // tools/getBalance.ts:4). The bare `catch` was masking real failures.\n if (isNotFoundError(err)) {\n const notFoundResult: ManageDomainResult = {\n action: 'lookup',\n fqdn: customDomain,\n lease: null,\n };\n callbacks.onComplete?.(notFoundResult);\n return notFoundResult;\n }\n const reason = `lease_by_custom_domain lookup failed for \"${customDomain}\": ${\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 uuid = readLeaseUuid((result as { lease?: unknown })?.lease);\n const lookupResult: ManageDomainResult = {\n action: 'lookup',\n fqdn: customDomain,\n lease: uuid ? { leaseUuid: uuid } : null,\n };\n // Symmetric `onComplete` fire (Copilot review PR #60, comment\n // 3288656598). Pre-fix, the lookup path returned without invoking\n // `onComplete` — asymmetric vs the set/clear paths in this same\n // function and vs `closeLease` / `troubleshootDeployment`. Was\n // documented as \"intentional\" in PR_DESCRIPTION.md Risks #1 but\n // was really an oversight rationalized post-hoc. Now consistent.\n callbacks.onComplete?.(lookupResult);\n return lookupResult;\n}\n\nfunction isNotFoundError(err: unknown): boolean {\n // Pass-through guard for structured failures: a `ManifestMCPError` is\n // always a real, intentional error — never silently re-classified as\n // \"FQDN unclaimed\" even if its message happens to contain \"not found\".\n if (err instanceof ManifestMCPError) return false;\n if (!(err instanceof Error)) return false;\n const msg = err.message;\n return NOT_FOUND_RES.some((re) => msg.match(re) !== null);\n}\n\nfunction readLeaseUuid(lease: unknown): string | undefined {\n if (lease === null || typeof lease !== 'object') return undefined;\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 return typeof u === 'string' && u.length > 0 ? u : undefined;\n}\n\nfunction deriveFinalCustomDomain(\n diagnostic: VerifyDomainResult,\n action: 'set' | 'clear',\n): string | null {\n if (action === 'clear') return null;\n const actual = diagnostic.actual ?? '';\n return actual.length > 0 ? actual : null;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"manage-domain.js","names":[],"sources":["../src/manage-domain.ts"],"sourcesContent":["/**\n * Public entry point: orchestrate setting, clearing, or looking up a\n * lease item's custom domain.\n *\n * Composition (mirrors `deploy-app.ts`'s shape):\n *\n * - `set` / `clear` render a confirmation block, optionally call\n * `onConfirm`, broadcast `setItemCustomDomain` against the agent's\n * bound chain client, then verify the post-broadcast on-chain state\n * via `verifyAndRecover` driving `verify-domain-state` over a direct\n * `billing.v1.lease({ leaseUuid })` single-lease query (tenant-\n * agnostic, no pagination edge cases). Branches are inline closures\n * bound to the per-action context; recovery options are intentionally\n * empty so the verifier surfaces failures via the simple-form\n * `onFailure({ reason })` adapter rather than the rich-form\n * `RecoveryOption[]` prompt (manage-domain has no recovery primitives\n * the orchestrator can dispatch; the user re-runs after a real fix).\n *\n * - `lookup` skips broadcast/verify and resolves the FQDN via the\n * `lease_by_custom_domain` chain query; returns `null` lease when\n * the FQDN isn't claimed.\n */\n\nimport {\n ManifestMCPError,\n ManifestMCPErrorCode,\n setItemCustomDomain,\n} from '@manifest-network/manifest-mcp-core';\nimport {\n type VerifyDomainOutcome,\n type VerifyDomainResult,\n verifyDomainState,\n} from './internals/verify-domain-state.js';\nimport {\n type VerificationSpec,\n verifyAndRecover,\n} from './internals/verify-recover.js';\nimport type {\n DeploymentPlanBlock,\n ManageDomainArgs,\n ManageDomainCallbacks,\n ManageDomainOptions,\n ManageDomainResult,\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\n// RFC 1123 hostname: each label 1-63 chars, alphanumeric + hyphens, no leading/\n// trailing hyphen; total ≤253 chars; ≥2 labels (FQDN, not single-label host).\n// Rejects scheme prefixes ('http://'), whitespace, trailing dots, and raw\n// unicode (ASCII punycode `xn--...` is accepted — it matches the regex's\n// `[A-Za-z0-9-]` label character class, which is the standard wire form\n// for IDN labels).\n//\n// Client-side typo gate only. The chain's `MsgSetItemCustomDomain` keeper is\n// the authoritative validator (canonical lowercase, reserved-suffix rules,\n// FQDN format). This anchored regex catches the obvious-malformed-input\n// cases pre-broadcast so we don't waste a tx on `\"\"`, `\" \"`, `\"http://x.y\"`,\n// or `\"not a domain\"`. Anything that passes here still goes through the\n// chain's own validation.\nconst FQDN_RE =\n /^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)+[A-Za-z](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/;\n\nconst SCHEME_PREFIX_RE = /^https?:\\/\\//i;\n\n/**\n * Cosmos SDK / gRPC NotFound message patterns. Match against\n * `Error.message` to distinguish chain-keeper NotFound (treated as\n * \"unclaimed FQDN\" → typed `null` result) from real failures (treated\n * as `QUERY_FAILED` throws). Anchored loose patterns to tolerate\n * different keeper / transport formatting (\"not found\", \"NotFound\",\n * \"no such record\", \"does not exist\").\n *\n * Per CLAUDE.md: use `String.prototype.match()` over `RegExp.test()`\n * to avoid the CI security hook's false-positive on shell-execution\n * tokens.\n */\nconst NOT_FOUND_RES: readonly RegExp[] = [\n /not.?found/i,\n /no.?such/i,\n /does.?not.?exist/i,\n];\n\n/**\n * Set / clear / look up a lease item's custom domain.\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 `setItemCustomDomain()` broadcast step in `set` / `clear`\n * paths. Broadcast errors do NOT invoke `onFailure` — that callback\n * is reserved for post-broadcast verification failures.\n * `setItemCustomDomain` already raises a structured `ManifestMCPError`\n * from the core package; wrapping it again at this layer would be\n * redundant. Callers wanting to react to broadcast errors should\n * catch them at the call site.\n * @throws `ManifestMCPError(TX_FAILED)` when post-broadcast verification\n * reaches a `not_found` / `mismatch` outcome (after `onFailure` has\n * been invoked so the caller can react).\n * @throws `ManifestMCPError(QUERY_FAILED)` when a chain query raises a\n * non-NotFound error (RPC / transport / decoding failure). Two paths\n * surface this:\n * - the `lookup` chain query (`lease_by_custom_domain`); the keeper's\n * `NotFound` on an unclaimed FQDN is surfaced as a typed\n * `{ lease: null }` result, not a throw.\n * - the post-broadcast verify chain query (`billing.v1.lease`) in\n * the `set` / `clear` paths (wrapped inside the verifier closure\n * so the failure flows through `onFailure({ reason })` before the\n * throw).\n * Structured `ManifestMCPError`s raised by the chain client are\n * re-thrown as-is (with `onFailure` invoked first).\n */\nexport async function manageDomain(\n args: ManageDomainArgs,\n callbacks: ManageDomainCallbacks,\n opts: ManageDomainOptions,\n): Promise<ManageDomainResult> {\n validateArgs(args);\n\n if (args.action === 'lookup') {\n return await lookupDomain(args.fqdn, callbacks, opts);\n }\n\n const serviceName = args.serviceName;\n // Trim the FQDN silently for `set` (Copilot review PR #60, comment\n // 3276519081): align with the underlying `setItemCustomDomain`\n // primitive (which trims at `packages/core/src/tools/setItemCustomDomain.ts:78-81`)\n // and with `lookupDomain` (which already trims). `validateArgs`\n // continues to reject the empty / whitespace-only case via\n // `args.fqdn.trim() === ''`; this just normalizes the surviving\n // input.\n const fqdn = args.action === 'set' ? args.fqdn.trim() : '';\n\n // --- Confirmation block ---------------------------------------------\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 manage-domain ${args.action}.`,\n );\n }\n }\n callbacks.onProgress?.({ kind: 'user_confirmed' });\n\n // --- Broadcast ------------------------------------------------------\n const setOpts =\n args.action === 'set'\n ? serviceName\n ? { serviceName }\n : undefined\n : {\n clear: true as const,\n ...(serviceName ? { serviceName } : {}),\n };\n await setItemCustomDomain(opts.clientManager, args.leaseUuid, fqdn, setOpts);\n\n // --- Verify ---------------------------------------------------------\n // Direct single-lease query (Copilot review PR #60, comment 3275999569):\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 //\n // We wrap the single-lease result as `{ leases: [result.lease] }`\n // (or an empty array if the chain returns no match) so\n // `verifyDomainState` stays untouched — its `findLease` walks the\n // same shape, and a `not_found` outcome falls out naturally when the\n // wrapper array is empty.\n const spec: VerificationSpec<\n unknown,\n VerifyDomainOutcome,\n VerifyDomainResult\n > = {\n verifier: async () => {\n // Wrap the chain call in try/catch (Copilot review PR #60,\n // comment 3276419210): 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 ${args.action}-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 const leases = lease === null || lease === undefined ? [] : [lease];\n const decoded = verifyDomainState(\n { leases },\n {\n leaseUuid: args.leaseUuid,\n ...(serviceName ? { serviceName } : {}),\n expected: fqdn,\n },\n );\n return { outcome: decoded.outcome, diagnostic: decoded };\n },\n successValues: ['match'],\n branches: {\n mismatch: {\n branchId: 'domain_verification_mismatch',\n journalActionTags: ['domain-verification-mismatch'],\n buildFailureEnvelope: (d) => ({\n outcome: 'failed',\n reason:\n args.action === 'set'\n ? `Chain shows custom_domain=\"${d.actual ?? ''}\" for lease ${args.leaseUuid}; expected \"${fqdn}\".`\n : `Chain still shows custom_domain=\"${d.actual ?? ''}\" for lease ${args.leaseUuid}; expected cleared.`,\n }),\n buildRecoveryOptions: () => [],\n },\n not_found: {\n branchId: 'domain_not_found',\n journalActionTags: ['domain-verification-not-found'],\n buildFailureEnvelope: (d) => ({\n outcome: 'failed',\n reason:\n d.reason ??\n `Lease ${args.leaseUuid} not found when verifying domain state.`,\n }),\n buildRecoveryOptions: () => [],\n },\n },\n };\n\n const verifyResult = await verifyAndRecover(spec, undefined);\n const verified = verifyResult.result === 'success';\n const finalCustomDomain = deriveFinalCustomDomain(\n verifyResult.diagnostic,\n args.action,\n );\n\n if (!verified) {\n const reason =\n verifyResult.failure?.reason ??\n `manage-domain ${args.action} verification failed.`;\n if (callbacks.onFailure) {\n await callbacks.onFailure({ reason });\n }\n throw new ManifestMCPError(ManifestMCPErrorCode.TX_FAILED, reason);\n }\n\n const result: ManageDomainResult = {\n action: args.action,\n leaseUuid: args.leaseUuid,\n verified,\n finalCustomDomain,\n };\n callbacks.onComplete?.(result);\n return result;\n}\n\n// --- Helpers --------------------------------------------------------\n\nfunction validateArgs(args: ManageDomainArgs): void {\n if (\n args.action !== 'set' &&\n args.action !== 'clear' &&\n args.action !== 'lookup'\n ) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `manageDomain: unknown action \"${(args as { action?: string }).action}\".`,\n );\n }\n if (args.action === 'lookup') {\n if (typeof args.fqdn !== 'string' || args.fqdn.trim() === '') {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'manageDomain lookup: fqdn must be a non-empty string.',\n );\n }\n return;\n }\n if (typeof args.leaseUuid !== 'string' || !args.leaseUuid.match(UUID_RE)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `manageDomain ${args.action}: leaseUuid must be a UUID; got \"${args.leaseUuid}\".`,\n );\n }\n if (args.action === 'set') {\n if (typeof args.fqdn !== 'string' || args.fqdn.trim() === '') {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n 'manageDomain set: fqdn must be a non-empty string.',\n );\n }\n // Trim silently (Copilot review PR #60, comment 3276519081):\n // align with `setItemCustomDomain` (which trims at\n // `packages/core/src/tools/setItemCustomDomain.ts:78-81`) and with\n // `lookupDomain` (which already trims). Validation gates below\n // run against the trimmed candidate; the broadcast and confirm\n // block (rendered in the caller) also use the trimmed form.\n const candidate = args.fqdn.trim();\n if (candidate.match(SCHEME_PREFIX_RE)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `manageDomain set: fqdn must be a bare hostname (no scheme); got \"${args.fqdn}\".`,\n );\n }\n if (!candidate.match(FQDN_RE)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `manageDomain set: fqdn \"${args.fqdn}\" is not a valid RFC 1123 hostname (≤253 chars, ≥2 dot-separated labels of 1-63 alphanumeric/hyphen chars; no leading/trailing hyphens).`,\n );\n }\n }\n}\n\nfunction renderConfirmationBlock(\n args: Exclude<ManageDomainArgs, { action: 'lookup' }>,\n): DeploymentPlanBlock {\n const lines: string[] = [];\n if (args.action === 'set') {\n lines.push(`Set custom domain on lease ${args.leaseUuid}:`);\n // Display the trimmed FQDN — matches the value that will be\n // broadcast (per the silent-trim semantics aligned with\n // setItemCustomDomain) so users don't see whitespace in the\n // confirm prompt that won't appear on-chain.\n lines.push(` FQDN: ${args.fqdn.trim()}`);\n if (args.serviceName) {\n lines.push(` Service: ${args.serviceName}`);\n }\n lines.push('');\n lines.push('Proceed?');\n } else {\n lines.push(`Clear custom domain on lease ${args.leaseUuid}:`);\n if (args.serviceName) {\n lines.push(` Service: ${args.serviceName}`);\n }\n lines.push('');\n lines.push('Proceed?');\n }\n return { text: lines.join('\\n') };\n}\n\nasync function lookupDomain(\n fqdn: string,\n callbacks: ManageDomainCallbacks,\n opts: ManageDomainOptions,\n): Promise<ManageDomainResult> {\n const customDomain = fqdn.trim();\n let result: unknown;\n try {\n // Pull `getQueryClient()` INSIDE the try (Copilot review PR #60,\n // comment 3276719558). `getQueryClient()` can throw\n // `INVALID_CONFIG` (neither rpcUrl nor restUrl set) or\n // `RPC_CONNECTION_FAILED` (connect failure). Catching here routes\n // those init-time failures through the same `onFailure` +\n // QUERY_FAILED / structured-passthrough normalization the chain-\n // query failure mode already gets. The set/clear verifier closure\n // (in `manageDomain` body above) already wraps `getQueryClient()`\n // since commit d9793c1; this brings `lookupDomain` to parity.\n const queryClient = await opts.clientManager.getQueryClient();\n result = await queryClient.liftedinit.billing.v1.leaseByCustomDomain({\n customDomain,\n });\n } catch (err) {\n // Narrowed disambiguation (Copilot review PR #60): the chain keeper\n // raises a NotFound-shaped error when the FQDN is unclaimed (cosmjs/\n // grpc surfaces this as a plain `Error` whose message matches\n // `/not.?found|no.?such|does.?not.?exist/i`). Only that case is\n // collapsed to the typed `{ lease: null }` result. Every other\n // failure mode (RPC transport, decoding, structured\n // `ManifestMCPError`, etc.) flows through `onFailure({ reason })`\n // then a typed throw — matching the lease-package's\n // `lease_by_custom_domain` handler (packages/lease/src/index.ts:442)\n // and `getBalance`'s `catchNotFound` pattern (packages/core/src/\n // tools/getBalance.ts:4). The bare `catch` was masking real failures.\n if (isNotFoundError(err)) {\n const notFoundResult: ManageDomainResult = {\n action: 'lookup',\n fqdn: customDomain,\n lease: null,\n };\n callbacks.onComplete?.(notFoundResult);\n return notFoundResult;\n }\n const reason = `lease_by_custom_domain lookup failed for \"${customDomain}\": ${\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 uuid = readLeaseUuid((result as { lease?: unknown })?.lease);\n const lookupResult: ManageDomainResult = {\n action: 'lookup',\n fqdn: customDomain,\n lease: uuid ? { leaseUuid: uuid } : null,\n };\n // Symmetric `onComplete` fire (Copilot review PR #60, comment\n // 3288656598). Pre-fix, the lookup path returned without invoking\n // `onComplete` — asymmetric vs the set/clear paths in this same\n // function and vs `closeLease` / `troubleshootDeployment`. Was\n // documented as \"intentional\" in PR_DESCRIPTION.md Risks #1 but\n // was really an oversight rationalized post-hoc. Now consistent.\n callbacks.onComplete?.(lookupResult);\n return lookupResult;\n}\n\nfunction isNotFoundError(err: unknown): boolean {\n // Pass-through guard for structured failures: a `ManifestMCPError` is\n // always a real, intentional error — never silently re-classified as\n // \"FQDN unclaimed\" even if its message happens to contain \"not found\".\n if (err instanceof ManifestMCPError) return false;\n if (!(err instanceof Error)) return false;\n const msg = err.message;\n return NOT_FOUND_RES.some((re) => msg.match(re) !== null);\n}\n\nfunction readLeaseUuid(lease: unknown): string | undefined {\n if (lease === null || typeof lease !== 'object') return undefined;\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 return typeof u === 'string' && u.length > 0 ? u : undefined;\n}\n\nfunction deriveFinalCustomDomain(\n diagnostic: VerifyDomainResult,\n action: 'set' | 'clear',\n): string | null {\n if (action === 'clear') return null;\n const actual = diagnostic.actual ?? '';\n return actual.length > 0 ? actual : null;\n}\n"],"mappings":"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"}
|
package/dist/troubleshoot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"troubleshoot.js","names":["decodeLeaseState"],"sources":["../src/troubleshoot.ts"],"sourcesContent":["/**\n * Public entry point: produce a markdown-formatted diagnostic report\n * for a given lease.\n *\n * Chain-only (no provider HTTP calls — `TroubleshootOptions` has no\n * `walletProvider` for ADR-036 auth). Composes:\n *\n * - `queryClient.liftedinit.billing.v1.lease({ leaseUuid })` for the\n * authoritative chain-side lease record.\n * - `lease-state.decode` + `isTerminal` to translate the integer\n * state into a canonical `LEASE_STATE_*` name and a\n * guidance-routing terminal/non-terminal classification.\n * - `lease-items.normalizeItem` to surface each item's serviceName\n * and customDomain regardless of snake/camelCase payload shape.\n *\n * The returned `markdown` is plain text with markdown formatting — host\n * surfaces can render it in chat directly or embed in a richer\n * diagnostic UI.\n *\n * **Scope:** chain-only. `TroubleshootOptions` carries no `walletProvider`,\n * so provider-side diagnostics (`appStatus` / `getLeaseProvision` /\n * `getAppLogs`) are out of scope for this function. If the report\n * surfaces a recovery-worthy state (e.g. terminal / drift), the caller\n * composes `closeLease()` separately — agent-core's simple-form\n * `onFailure({ reason })` does not carry recovery options, so the\n * orchestration of \"report → decide → close\" lives at the host surface.\n */\n\nimport {\n ManifestMCPError,\n ManifestMCPErrorCode,\n} from '@manifest-network/manifest-mcp-core';\nimport { normalizeItem } from './internals/lease-items.js';\nimport {\n decode as decodeLeaseState,\n isTerminal,\n} from './internals/lease-state.js';\nimport type {\n LeaseStateName,\n TroubleshootArgs,\n TroubleshootCallbacks,\n TroubleshootOptions,\n TroubleshootReport,\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\n/**\n * Generate a diagnostic markdown report for `args.leaseUuid`.\n *\n * @throws `ManifestMCPError(INVALID_CONFIG)` for args validation.\n * @throws `ManifestMCPError(QUERY_FAILED)` in two cases, both with\n * `onFailure({ reason })` invoked first:\n * - the chain query rejects with a plain `Error` (RPC / transport\n * / decoding failure); or\n * - the chain query succeeds but returns `{ lease: null }` /\n * `undefined` (lease UUID not on-chain — the `billing.v1.lease`\n * no-such-lease response shape).\n * Structured `ManifestMCPError`s raised by the chain client\n * (e.g. `INVALID_CONFIG` from missing rpc/rest url config,\n * `RPC_CONNECTION_FAILED` from connect failure) are re-thrown\n * as-is with their original code, with `onFailure` invoked first.\n */\nexport async function troubleshootDeployment(\n args: TroubleshootArgs,\n callbacks: TroubleshootCallbacks,\n opts: TroubleshootOptions,\n): Promise<TroubleshootReport> {\n validateArgs(args);\n\n let leasePayload: unknown;\n try {\n // Pull `getQueryClient()` INSIDE the try (Copilot review PR #60,\n // comment 3276719462). `getQueryClient()` can throw\n // `INVALID_CONFIG` (neither rpcUrl nor restUrl set) or\n // `RPC_CONNECTION_FAILED` (connect failure). Catching here routes\n // those init-time failures through the same `onFailure` +\n // QUERY_FAILED / structured-passthrough normalization the chain-\n // query failure mode already gets — three modes, one disambiguation.\n const queryClient = await opts.clientManager.getQueryClient();\n const result = await queryClient.liftedinit.billing.v1.lease({\n leaseUuid: args.leaseUuid,\n });\n leasePayload = result.lease;\n } catch (err) {\n // Preserve structured `ManifestMCPError`s from the chain client\n // (Copilot review PR #60, comment 3276172289). Wrapping every\n // failure as `QUERY_FAILED` erases upstream error codes — a real\n // `INVALID_CONFIG` from the chain layer should surface to callers\n // with that code, not be collapsed to a less-specific category.\n // Mirrors the disambiguation `manage-domain.ts:lookupDomain`\n // adopted in commit aaa5cc5. Note: chain-NotFound for\n // `billing.v1.lease({ leaseUuid })` returns `{ lease: null }`\n // (handled below), so errors landing here are genuinely transport\n // or structured failures.\n const reason = `Failed to query lease ${args.leaseUuid}: ${err instanceof Error ? err.message : String(err)}`;\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\n if (leasePayload === null || leasePayload === undefined) {\n const reason = `Lease ${args.leaseUuid} not found on chain.`;\n if (callbacks.onFailure) {\n await callbacks.onFailure({ reason });\n }\n throw new ManifestMCPError(ManifestMCPErrorCode.QUERY_FAILED, reason);\n }\n\n const markdown = renderReport(args.leaseUuid, leasePayload);\n const report: TroubleshootReport = { markdown };\n callbacks.onComplete?.(report);\n return report;\n}\n\n// --- Helpers --------------------------------------------------------\n\nfunction validateArgs(args: TroubleshootArgs): void {\n if (typeof args.leaseUuid !== 'string' || !args.leaseUuid.match(UUID_RE)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `troubleshootDeployment: leaseUuid must be a UUID; got \"${args.leaseUuid}\".`,\n );\n }\n}\n\ninterface LeaseShape {\n uuid?: unknown;\n state?: unknown;\n providerUuid?: unknown;\n provider_uuid?: unknown;\n createdAt?: unknown;\n created_at?: unknown;\n closedAt?: unknown;\n closed_at?: unknown;\n items?: unknown;\n}\n\nfunction renderReport(leaseUuid: string, lease: unknown): string {\n const l = (lease ?? {}) as LeaseShape;\n const rawState = l.state;\n const stateName = decodeLeaseState(\n typeof rawState === 'number' || typeof rawState === 'string'\n ? rawState\n : undefined,\n );\n const stateLabel = stateName ?? `UNKNOWN(${String(rawState)})`;\n const providerUuid =\n readString(l.providerUuid) || readString(l.provider_uuid) || '(unknown)';\n const createdAt = readTimestamp(l.createdAt) ?? readTimestamp(l.created_at);\n const closedAt = readTimestamp(l.closedAt) ?? readTimestamp(l.closed_at);\n\n const rawItems = Array.isArray(l.items) ? l.items : [];\n const items = rawItems.map(normalizeItem);\n\n const lines: string[] = [];\n lines.push(`# Lease diagnostic — ${leaseUuid}`);\n lines.push('');\n lines.push('## Chain state');\n lines.push('');\n lines.push(`- **State:** ${stateLabel}`);\n lines.push(`- **Provider:** ${providerUuid}`);\n if (createdAt) lines.push(`- **Created:** ${createdAt}`);\n if (closedAt) lines.push(`- **Closed:** ${closedAt}`);\n lines.push('');\n\n lines.push('## Items');\n lines.push('');\n if (items.length === 0) {\n lines.push('_No items found on this lease._');\n } else {\n for (const item of items) {\n const svc = item.serviceName.length > 0 ? item.serviceName : '(default)';\n const dom =\n item.customDomain.length > 0 ? item.customDomain : '(no custom domain)';\n lines.push(`- **${svc}** → ${dom}`);\n }\n }\n lines.push('');\n\n lines.push('## Guidance');\n lines.push('');\n for (const tip of guidanceFor(stateName)) {\n lines.push(`- ${tip}`);\n }\n\n return lines.join('\\n');\n}\n\nfunction guidanceFor(state: LeaseStateName | undefined): string[] {\n if (state === undefined) {\n return [\n 'Lease state could not be decoded. Re-query in a moment, or check the chain client logs for transport errors.',\n ];\n }\n if (isTerminal(state)) {\n return [\n `Lease is in terminal state \\`${state}\\`. No further provider activity expected.`,\n 'To redeploy, create a new lease via `deployApp`.',\n ];\n }\n switch (state) {\n case 'LEASE_STATE_PENDING':\n return [\n 'Lease is awaiting provider acknowledgement.',\n 'If pending persists for more than a few minutes, the provider may be offline; consider closing and redeploying.',\n ];\n case 'LEASE_STATE_ACTIVE':\n return [\n 'Lease is active on the provider. App-level status / logs require a provider HTTP call with an ADR-036 auth token (out of scope for this chain-only diagnostic).',\n ];\n default:\n return [\n `Lease state is \\`${state}\\`. Review the chain proto for the expected next transition.`,\n ];\n }\n}\n\nfunction readString(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\nfunction readTimestamp(value: unknown): string | undefined {\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'string' && value.length > 0) return value;\n return undefined;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"troubleshoot.js","names":["decodeLeaseState"],"sources":["../src/troubleshoot.ts"],"sourcesContent":["/**\n * Public entry point: produce a markdown-formatted diagnostic report\n * for a given lease.\n *\n * Chain-only (no provider HTTP calls — `TroubleshootOptions` has no\n * `walletProvider` for ADR-036 auth). Composes:\n *\n * - `queryClient.liftedinit.billing.v1.lease({ leaseUuid })` for the\n * authoritative chain-side lease record.\n * - `lease-state.decode` + `isTerminal` to translate the integer\n * state into a canonical `LEASE_STATE_*` name and a\n * guidance-routing terminal/non-terminal classification.\n * - `lease-items.normalizeItem` to surface each item's serviceName\n * and customDomain regardless of snake/camelCase payload shape.\n *\n * The returned `markdown` is plain text with markdown formatting — host\n * surfaces can render it in chat directly or embed in a richer\n * diagnostic UI.\n *\n * **Scope:** chain-only. `TroubleshootOptions` carries no `walletProvider`,\n * so provider-side diagnostics (`appStatus` / `getLeaseProvision` /\n * `getAppLogs`) are out of scope for this function. If the report\n * surfaces a recovery-worthy state (e.g. terminal / drift), the caller\n * composes `closeLease()` separately — agent-core's simple-form\n * `onFailure({ reason })` does not carry recovery options, so the\n * orchestration of \"report → decide → close\" lives at the host surface.\n */\n\nimport {\n ManifestMCPError,\n ManifestMCPErrorCode,\n} from '@manifest-network/manifest-mcp-core';\nimport { normalizeItem } from './internals/lease-items.js';\nimport {\n decode as decodeLeaseState,\n isTerminal,\n} from './internals/lease-state.js';\nimport type {\n LeaseStateName,\n TroubleshootArgs,\n TroubleshootCallbacks,\n TroubleshootOptions,\n TroubleshootReport,\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\n/**\n * Generate a diagnostic markdown report for `args.leaseUuid`.\n *\n * @throws `ManifestMCPError(INVALID_CONFIG)` for args validation.\n * @throws `ManifestMCPError(QUERY_FAILED)` in two cases, both with\n * `onFailure({ reason })` invoked first:\n * - the chain query rejects with a plain `Error` (RPC / transport\n * / decoding failure); or\n * - the chain query succeeds but returns `{ lease: null }` /\n * `undefined` (lease UUID not on-chain — the `billing.v1.lease`\n * no-such-lease response shape).\n * Structured `ManifestMCPError`s raised by the chain client\n * (e.g. `INVALID_CONFIG` from missing rpc/rest url config,\n * `RPC_CONNECTION_FAILED` from connect failure) are re-thrown\n * as-is with their original code, with `onFailure` invoked first.\n */\nexport async function troubleshootDeployment(\n args: TroubleshootArgs,\n callbacks: TroubleshootCallbacks,\n opts: TroubleshootOptions,\n): Promise<TroubleshootReport> {\n validateArgs(args);\n\n let leasePayload: unknown;\n try {\n // Pull `getQueryClient()` INSIDE the try (Copilot review PR #60,\n // comment 3276719462). `getQueryClient()` can throw\n // `INVALID_CONFIG` (neither rpcUrl nor restUrl set) or\n // `RPC_CONNECTION_FAILED` (connect failure). Catching here routes\n // those init-time failures through the same `onFailure` +\n // QUERY_FAILED / structured-passthrough normalization the chain-\n // query failure mode already gets — three modes, one disambiguation.\n const queryClient = await opts.clientManager.getQueryClient();\n const result = await queryClient.liftedinit.billing.v1.lease({\n leaseUuid: args.leaseUuid,\n });\n leasePayload = result.lease;\n } catch (err) {\n // Preserve structured `ManifestMCPError`s from the chain client\n // (Copilot review PR #60, comment 3276172289). Wrapping every\n // failure as `QUERY_FAILED` erases upstream error codes — a real\n // `INVALID_CONFIG` from the chain layer should surface to callers\n // with that code, not be collapsed to a less-specific category.\n // Mirrors the disambiguation `manage-domain.ts:lookupDomain`\n // adopted in commit aaa5cc5. Note: chain-NotFound for\n // `billing.v1.lease({ leaseUuid })` returns `{ lease: null }`\n // (handled below), so errors landing here are genuinely transport\n // or structured failures.\n const reason = `Failed to query lease ${args.leaseUuid}: ${err instanceof Error ? err.message : String(err)}`;\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\n if (leasePayload === null || leasePayload === undefined) {\n const reason = `Lease ${args.leaseUuid} not found on chain.`;\n if (callbacks.onFailure) {\n await callbacks.onFailure({ reason });\n }\n throw new ManifestMCPError(ManifestMCPErrorCode.QUERY_FAILED, reason);\n }\n\n const markdown = renderReport(args.leaseUuid, leasePayload);\n const report: TroubleshootReport = { markdown };\n callbacks.onComplete?.(report);\n return report;\n}\n\n// --- Helpers --------------------------------------------------------\n\nfunction validateArgs(args: TroubleshootArgs): void {\n if (typeof args.leaseUuid !== 'string' || !args.leaseUuid.match(UUID_RE)) {\n throw new ManifestMCPError(\n ManifestMCPErrorCode.INVALID_CONFIG,\n `troubleshootDeployment: leaseUuid must be a UUID; got \"${args.leaseUuid}\".`,\n );\n }\n}\n\ninterface LeaseShape {\n uuid?: unknown;\n state?: unknown;\n providerUuid?: unknown;\n provider_uuid?: unknown;\n createdAt?: unknown;\n created_at?: unknown;\n closedAt?: unknown;\n closed_at?: unknown;\n items?: unknown;\n}\n\nfunction renderReport(leaseUuid: string, lease: unknown): string {\n const l = (lease ?? {}) as LeaseShape;\n const rawState = l.state;\n const stateName = decodeLeaseState(\n typeof rawState === 'number' || typeof rawState === 'string'\n ? rawState\n : undefined,\n );\n const stateLabel = stateName ?? `UNKNOWN(${String(rawState)})`;\n const providerUuid =\n readString(l.providerUuid) || readString(l.provider_uuid) || '(unknown)';\n const createdAt = readTimestamp(l.createdAt) ?? readTimestamp(l.created_at);\n const closedAt = readTimestamp(l.closedAt) ?? readTimestamp(l.closed_at);\n\n const rawItems = Array.isArray(l.items) ? l.items : [];\n const items = rawItems.map(normalizeItem);\n\n const lines: string[] = [];\n lines.push(`# Lease diagnostic — ${leaseUuid}`);\n lines.push('');\n lines.push('## Chain state');\n lines.push('');\n lines.push(`- **State:** ${stateLabel}`);\n lines.push(`- **Provider:** ${providerUuid}`);\n if (createdAt) lines.push(`- **Created:** ${createdAt}`);\n if (closedAt) lines.push(`- **Closed:** ${closedAt}`);\n lines.push('');\n\n lines.push('## Items');\n lines.push('');\n if (items.length === 0) {\n lines.push('_No items found on this lease._');\n } else {\n for (const item of items) {\n const svc = item.serviceName.length > 0 ? item.serviceName : '(default)';\n const dom =\n item.customDomain.length > 0 ? item.customDomain : '(no custom domain)';\n lines.push(`- **${svc}** → ${dom}`);\n }\n }\n lines.push('');\n\n lines.push('## Guidance');\n lines.push('');\n for (const tip of guidanceFor(stateName)) {\n lines.push(`- ${tip}`);\n }\n\n return lines.join('\\n');\n}\n\nfunction guidanceFor(state: LeaseStateName | undefined): string[] {\n if (state === undefined) {\n return [\n 'Lease state could not be decoded. Re-query in a moment, or check the chain client logs for transport errors.',\n ];\n }\n if (isTerminal(state)) {\n return [\n `Lease is in terminal state \\`${state}\\`. No further provider activity expected.`,\n 'To redeploy, create a new lease via `deployApp`.',\n ];\n }\n switch (state) {\n case 'LEASE_STATE_PENDING':\n return [\n 'Lease is awaiting provider acknowledgement.',\n 'If pending persists for more than a few minutes, the provider may be offline; consider closing and redeploying.',\n ];\n case 'LEASE_STATE_ACTIVE':\n return [\n 'Lease is active on the provider. App-level status / logs require a provider HTTP call with an ADR-036 auth token (out of scope for this chain-only diagnostic).',\n ];\n default:\n return [\n `Lease state is \\`${state}\\`. Review the chain proto for the expected next transition.`,\n ];\n }\n}\n\nfunction readString(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\nfunction readTimestamp(value: unknown): string | undefined {\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'string' && value.length > 0) return value;\n return undefined;\n}\n"],"mappings":"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"}
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;UAkBiB,IAAA;EACf,KAAA;EACA,
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"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.13.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",
|
|
@@ -43,12 +43,12 @@
|
|
|
43
43
|
"dist"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@manifest-network/manifest-mcp-core": "^0.
|
|
47
|
-
"@manifest-network/manifest-mcp-fred": "^0.
|
|
46
|
+
"@manifest-network/manifest-mcp-core": "^0.13.0",
|
|
47
|
+
"@manifest-network/manifest-mcp-fred": "^0.13.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@types/node": "22.
|
|
50
|
+
"@types/node": "22.19.19",
|
|
51
51
|
"typescript": "5.9.3",
|
|
52
|
-
"vitest": "4.1.
|
|
52
|
+
"vitest": "4.1.8"
|
|
53
53
|
}
|
|
54
54
|
}
|