UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

1 lines • 11 kB
{"version":3,"file":"boot-check.mjs","names":[],"sources":["../../../src/services/resolution/boot-check.ts"],"sourcesContent":["import type { BosConfigInput } from \"../../types\";\nimport { BosConfigSchema } from \"../../types\";\nimport {\n CircularExtendsError,\n ConfigExtendsError,\n ConfigLoadError,\n ConfigNotfoundError,\n PUBLISHED_CONFIG_ENV,\n} from \"./load\";\nimport { resolveExtendsRef } from \"./merge\";\nimport { type ResolutionIo, walkExtendsChain } from \"./session\";\n\n/**\n * The publish boot-check: given a *hypothetical* leaf payload (the train's\n * canonicalized publish payload, or a rollback snapshot), decide whether a\n * consumer boot would accept it, and report why not.\n *\n * Boot truth is a property of the extends-merged shape, never the leaf: a\n * consumer fetches the stored leaf, walks the extends chain, merges\n * child-wins, and parses with BosConfigSchema (resolvePublishedConfig). The\n * check re-runs exactly that pipeline — the same walker, merge, and schema —\n * with the payload substituted for the leaf node (`seedConfig`), so parity\n * holds by construction rather than by imitation. It deliberately does NOT\n * parse the leaf as-is when the leaf carries an extends ref: that is the\n * leaf-vs-merged confusion behind the rc.23 gate regression, where every\n * extends child was rejected for an `app.host` slot it legitimately inherits.\n *\n * Invariants:\n * - `ok` is one-directional soundness: `ok: true` means a consumer boot of\n * this payload — against the parent-chain state observed during the check —\n * would parse. The parent can drift between check and boot; the\n * post-publish smoke remains the authoritative whole-boot gate. This check\n * covers the config plane only (walk + merge + schema); pin/SRI byte\n * verification stays with `verifyRollbackSnapshot`.\n * - Root payloads (no resolvable extends ref) do zero I/O — the walk's seed\n * short-circuit returns the payload untouched and the check degenerates to\n * a plain `safeParse`.\n * - Never throws for data: unreachable parents, circular chains, network\n * failures, and garbage payloads all become issues. Only a thrown non-Error\n * (a programmer error) propagates.\n * - Never mutates the payload, never exposes the merged shape (defu's merge\n * reuses input subtrees — handing callers the merged object would alias\n * their payload).\n * - `chain` is diagnostic display only — callers never branch on it.\n */\n\nexport interface BootCheckIssue {\n /** \"chain\" — the extends walk itself failed (nothing to parse);\n * \"schema\" — BosConfigSchema rejected the effective (merged) shape. */\n code: \"schema\" | \"chain\";\n /** Dotted config path for schema issues (\"app.host\"); the offending\n * extends ref for chain issues; \"\" for whole-config schema failures. */\n path: string;\n /** Whose bytes contributed the failure: the payload itself, or a parent\n * link (best-effort — a failing dotted path present in the leaf is\n * attributed to the leaf; anything else to the chain). */\n origin: \"payload\" | \"parent\";\n message: string;\n}\n\nexport interface BootCheckReport {\n /** Strictly mirrors what a consumer boot would accept — no false greens,\n * no false reds relative to boot (against observed parent state). */\n ok: boolean;\n issues: readonly BootCheckIssue[];\n /** The parent refs walked (leaf excluded), in walk order — nearest parent\n * first; empty for root payloads and failed walks (the issue message\n * carries the tail). */\n chain: readonly string[];\n}\n\nexport interface BootCheckOptions {\n /** Registry override, threaded to parent fetches exactly as\n * resolvePublishedConfig threads it. */\n registry?: string;\n /** The one substitutable seam — the io `openResolution` itself takes.\n * Tests supply in-memory fetchers; production never passes it. */\n io?: ResolutionIo;\n}\n\nexport interface ResolvedBootShape {\n /** The parent refs walked (leaf excluded), in walk order — nearest parent\n * first; empty for root payloads. */\n chain: readonly string[];\n /** The extends-merged (child-wins) shape — a structured clone, safe to\n * hand to callers that may mutate it (the defu merge reuses input\n * subtrees). Root payloads clone the payload itself. */\n config: BosConfigInput;\n}\n\nfunction extendsRefOf(payload: unknown): string | undefined {\n return payload !== null && typeof payload === \"object\" && (payload as BosConfigInput).extends\n ? resolveExtendsRef((payload as BosConfigInput).extends, PUBLISHED_CONFIG_ENV)\n : undefined;\n}\n\n/**\n * The boot walk itself, exposed so consumers beside `verifyBootPayload` can\n * gate on the merged shape (the deploy train's composition gate) instead of\n * re-walking under their own walker. Throws on chain failure; the caller\n * decides how to report it.\n */\nexport async function resolveBootShape(\n payload: unknown,\n slot: { account: string; gateway: string },\n opts: BootCheckOptions = {},\n): Promise<ResolvedBootShape> {\n if (payload === null || typeof payload !== \"object\") {\n throw new Error(\"payload is not a config object\");\n }\n const input = payload as BosConfigInput;\n if (!extendsRefOf(input)) {\n return { chain: [], config: structuredClone(input) };\n }\n const walked = await walkExtendsChain(`bos://${slot.account}/${slot.gateway}`, {\n env: PUBLISHED_CONFIG_ENV,\n ...(opts.registry ? { registry: opts.registry } : {}),\n ...(opts.io ? { io: opts.io } : {}),\n seedConfig: structuredClone(input),\n visit: async () => {},\n });\n return { chain: walked.chain.slice(1), config: structuredClone(walked.config) };\n}\n\n/** Verify a payload against a shape the caller already walked with\n * `resolveBootShape` for this exact payload — the pure half of\n * `verifyBootPayload`, so a shared walk cannot drift from its verification. */\nexport function verifyResolvedBootShape(\n payload: unknown,\n shape: ResolvedBootShape,\n): BootCheckReport {\n const extendsRef = extendsRefOf(payload);\n if (!extendsRef) {\n return { chain: [], ...parseShape(payload as object, payload, false) };\n }\n return { chain: shape.chain, ...parseShape(payload as object, shape.config, true) };\n}\n\nexport async function verifyBootPayload(\n payload: unknown,\n slot: { account: string; gateway: string },\n opts: BootCheckOptions = {},\n): Promise<BootCheckReport> {\n if (payload === null || typeof payload !== \"object\") {\n return {\n ok: false,\n issues: [\n {\n code: \"schema\",\n path: \"\",\n origin: \"payload\",\n message: \"payload is not a config object\",\n },\n ],\n chain: [],\n };\n }\n try {\n return verifyResolvedBootShape(payload, await resolveBootShape(payload, slot, opts));\n } catch (error) {\n if (!(error instanceof Error)) throw error;\n return {\n ok: false,\n chain: [],\n issues: [\n {\n code: \"chain\",\n path: extendsRefOf(payload) ?? \"\",\n origin: \"parent\",\n message: chainFailureMessage(error),\n },\n ],\n };\n }\n}\n\n/** Parse a shape with BosConfigSchema and map the outcome onto a report —\n * the merged shape in the child path, the payload itself in the root path.\n * Origin attribution (child path only): a failing dotted path that exists\n * in the leaf payload is the leaf's; anything else came from the chain. */\nfunction parseShape(\n payload: object,\n shape: unknown,\n attributed: boolean,\n): Pick<BootCheckReport, \"ok\" | \"issues\"> {\n const parsed = BosConfigSchema.safeParse(shape);\n if (parsed.success) return { ok: true, issues: [] };\n return {\n ok: false,\n issues: parsed.error.issues.map((issue) => ({\n code: \"schema\" as const,\n path: issue.path.join(\".\"),\n origin:\n !attributed || pathExistsIn(payload, issue.path)\n ? (\"payload\" as const)\n : (\"parent\" as const),\n message: issue.message,\n })),\n };\n}\n\nfunction pathExistsIn(payload: object, path: PropertyKey[]): boolean {\n let cursor: unknown = payload;\n for (const key of path) {\n if (cursor === null || typeof cursor !== \"object\" || !(key in (cursor as object))) {\n return false;\n }\n cursor = (cursor as Record<PropertyKey, unknown>)[key];\n }\n return true;\n}\n\nfunction chainFailureMessage(error: Error): string {\n if (error instanceof CircularExtendsError) return `circular extends: ${error.message}`;\n if (error instanceof ConfigNotfoundError)\n return `a parent config could not be found: ${error.message}`;\n if (error instanceof ConfigLoadError)\n return `a parent config could not be loaded: ${error.message}`;\n if (error instanceof ConfigExtendsError)\n return `the extends chain could not be resolved: ${error.message}`;\n return `the extends chain could not be resolved: ${error.message}`;\n}\n"],"mappings":";;;;;;AA0FA,SAAS,aAAa,SAAsC;CAC1D,OAAO,YAAY,QAAQ,OAAO,YAAY,YAAa,QAA2B,UAClF,kBAAmB,QAA2B,SAAS,oBAAoB,IAC3E;AACN;;;;;;;AAQA,eAAsB,iBACpB,SACA,MACA,OAAyB,CAAC,GACE;CAC5B,IAAI,YAAY,QAAQ,OAAO,YAAY,UACzC,MAAM,IAAI,MAAM,gCAAgC;CAElD,MAAM,QAAQ;CACd,IAAI,CAAC,aAAa,KAAK,GACrB,OAAO;EAAE,OAAO,CAAC;EAAG,QAAQ,gBAAgB,KAAK;CAAE;CAErD,MAAM,SAAS,MAAM,iBAAiB,SAAS,KAAK,QAAQ,GAAG,KAAK,WAAW;EAC7E,KAAK;EACL,GAAI,KAAK,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;EACnD,GAAI,KAAK,KAAK,EAAE,IAAI,KAAK,GAAG,IAAI,CAAC;EACjC,YAAY,gBAAgB,KAAK;EACjC,OAAO,YAAY,CAAC;CACtB,CAAC;CACD,OAAO;EAAE,OAAO,OAAO,MAAM,MAAM,CAAC;EAAG,QAAQ,gBAAgB,OAAO,MAAM;CAAE;AAChF;;;;AAKA,SAAgB,wBACd,SACA,OACiB;CAEjB,IAAI,CADe,aAAa,OAClB,GACZ,OAAO;EAAE,OAAO,CAAC;EAAG,GAAG,WAAW,SAAmB,SAAS,KAAK;CAAE;CAEvE,OAAO;EAAE,OAAO,MAAM;EAAO,GAAG,WAAW,SAAmB,MAAM,QAAQ,IAAI;CAAE;AACpF;AAEA,eAAsB,kBACpB,SACA,MACA,OAAyB,CAAC,GACA;CAC1B,IAAI,YAAY,QAAQ,OAAO,YAAY,UACzC,OAAO;EACL,IAAI;EACJ,QAAQ,CACN;GACE,MAAM;GACN,MAAM;GACN,QAAQ;GACR,SAAS;EACX,CACF;EACA,OAAO,CAAC;CACV;CAEF,IAAI;EACF,OAAO,wBAAwB,SAAS,MAAM,iBAAiB,SAAS,MAAM,IAAI,CAAC;CACrF,SAAS,OAAO;EACd,IAAI,EAAE,iBAAiB,QAAQ,MAAM;EACrC,OAAO;GACL,IAAI;GACJ,OAAO,CAAC;GACR,QAAQ,CACN;IACE,MAAM;IACN,MAAM,aAAa,OAAO,KAAK;IAC/B,QAAQ;IACR,SAAS,oBAAoB,KAAK;GACpC,CACF;EACF;CACF;AACF;;;;;AAMA,SAAS,WACP,SACA,OACA,YACwC;CACxC,MAAM,SAAS,gBAAgB,UAAU,KAAK;CAC9C,IAAI,OAAO,SAAS,OAAO;EAAE,IAAI;EAAM,QAAQ,CAAC;CAAE;CAClD,OAAO;EACL,IAAI;EACJ,QAAQ,OAAO,MAAM,OAAO,KAAK,WAAW;GAC1C,MAAM;GACN,MAAM,MAAM,KAAK,KAAK,GAAG;GACzB,QACE,CAAC,cAAc,aAAa,SAAS,MAAM,IAAI,IAC1C,YACA;GACP,SAAS,MAAM;EACjB,EAAE;CACJ;AACF;AAEA,SAAS,aAAa,SAAiB,MAA8B;CACnE,IAAI,SAAkB;CACtB,KAAK,MAAM,OAAO,MAAM;EACtB,IAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,EAAE,OAAQ,SAC7D,OAAO;EAET,SAAU,OAAwC;CACpD;CACA,OAAO;AACT;AAEA,SAAS,oBAAoB,OAAsB;CACjD,IAAI,iBAAiB,sBAAsB,OAAO,qBAAqB,MAAM;CAC7E,IAAI,iBAAiB,qBACnB,OAAO,uCAAuC,MAAM;CACtD,IAAI,iBAAiB,iBACnB,OAAO,wCAAwC,MAAM;CACvD,IAAI,iBAAiB,oBACnB,OAAO,4CAA4C,MAAM;CAC3D,OAAO,4CAA4C,MAAM;AAC3D"}