@bedrock-rbx/core 0.1.0-beta.15 → 0.1.0-beta.17

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.
@@ -1 +1 @@
1
- {"version":3,"file":"migrate-mantle-state-ClQ40EFD.mjs","names":["OPTIONAL_BOOLEAN","socialLinkOrUndefined","toCurrentState","toCurrentState","OPTIONAL_BOOLEAN","entrySchema","flatten","normalize","changedFieldsBetween","fieldsEqual","entrySchema","flatten","normalize","changedFieldsBetween","fieldsEqual","entrySchema","flatten","normalize","changedFieldsBetween","fieldsEqual","compositeKey","c12LoadConfig","loadConfig","resolvePath","defaultLoadConfig","readFile","nodeReadFile","buildEntry","isObjectPayload","readString","readPrice","coerceRobloxId","isObjectPayload","coerceRobloxId","PLACE_CONFIGURATION_KIND","BLOCKED_FIELDS","isObjectPayload","PLACE_KIND","PLACE_CONFIGURATION_KIND","bucketByKind","isStartPlace","readString","isObjectPayload","coerceRobloxId","isObjectPayload","coerceRobloxId","EXPERIENCE_KIND","EXPERIENCE_CONFIGURATION_KIND","isObjectPayload","isObjectPayload","isObjectPayload","isObjectPayload","isObjectPayload","readFile","nodeReadFile"],"sources":["../src/cli/clack-port.ts","../src/cli/render.ts","../src/core/resolve-state-config.ts","../src/types/ids.ts","../src/core/environment.ts","../src/core/kinds/hash.ts","../src/core/redacted-icon.ts","../src/core/kinds/read-bytes.ts","../src/core/icons.ts","../src/core/validate-universe-xor.ts","../src/core/schema.ts","../src/adapters/clack-progress-adapter.ts","../src/core/derive-price-fields.ts","../src/core/plan-follow-up-patch.ts","../src/adapters/developer-product-driver.ts","../src/adapters/game-pass-driver.ts","../src/core/state-file.ts","../src/adapters/gist-state-adapter.ts","../src/adapters/no-op-progress-adapter.ts","../src/core/resources.ts","../src/adapters/place-driver.ts","../src/adapters/universe-driver.ts","../src/cli/default-spawner.ts","../src/cli/credential-environment-overrides.ts","../src/cli/dispatch-override.ts","../src/core/kinds/developer-product.ts","../src/core/kinds/game-pass.ts","../src/core/kinds/place.ts","../src/core/kinds/universe.ts","../src/core/kinds/index.ts","../src/core/diff.ts","../src/core/display-name-prefix.ts","../src/core/flatten.ts","../src/core/redact-resources.ts","../src/core/select-environment.ts","../src/shell/apply-ops.ts","../src/shell/build-default-registry.ts","../src/shell/build-desired.ts","../src/shell/build-state-port.ts","../src/core/assert-all-reconcilable.ts","../src/shell/load-config-internal.ts","../src/adapters/lute-luau-evaluator.ts","../src/shell/load-config.ts","../src/shell/deploy.ts","../src/core/migrate/build-state.ts","../src/core/migrate/extract-display-name-prefix.ts","../src/core/migrate/environment-label.ts","../src/core/migrate/factorize-environments-warnings.ts","../src/core/migrate/factorize-places.ts","../src/core/migrate/factorize-products.ts","../src/core/migrate/factorize-universe.ts","../src/core/migrate/factorize-environments.ts","../src/core/migrate/fold-passes.ts","../src/core/migrate/fold-universe-shared.ts","../src/core/migrate/fold-blocked-place-fields.ts","../src/core/migrate/fold-places.ts","../src/core/migrate/fold-products.ts","../src/core/migrate/fold-blocked-experience-fields.ts","../src/core/migrate/fold-display-name.ts","../src/core/migrate/fold-experience-icon.ts","../src/core/migrate/fold-social-links.ts","../src/core/migrate/fold-universe.ts","../src/core/migrate/fold-unsupported.ts","../src/core/migrate/fold-environment.ts","../src/core/migrate/parse-state.ts","../src/core/migrate/serialize-config.ts","../src/core/migrate/summarize-warnings.ts","../src/shell/recompute-icon-hashes.ts","../src/shell/migrate-mantle-state.ts"],"sourcesContent":["import { cancel, intro, log, outro } from \"@clack/prompts\";\n\nimport type { ClackPort } from \"./render.ts\";\n\n/**\n * Construct a `ClackPort` whose methods delegate to `@clack/prompts`. The\n * resulting port writes to `process.stdout` via clack's defaults. Kept in\n * its own module so consumers that never need the clack-backed rendering\n * (programmatic deploys, custom adapters) do not pull `@clack/prompts`\n * into their bundle.\n *\n * @example\n *\n * ```ts\n * import { createClackPort } from \"@bedrock-rbx/core\";\n *\n * const port = createClackPort();\n *\n * expect(typeof port.logSuccess).toBe(\"function\");\n * ```\n *\n * @returns A port whose six methods each invoke the matching clack helper.\n */\nexport function createClackPort(): ClackPort {\n\treturn {\n\t\tcancel: (message) => {\n\t\t\tcancel(message);\n\t\t},\n\t\tintro: (message) => {\n\t\t\tintro(message);\n\t\t},\n\t\tlogError: (message) => {\n\t\t\tlog.error(message);\n\t\t},\n\t\tlogMessage: (message) => {\n\t\t\tlog.message(message);\n\t\t},\n\t\tlogSuccess: (message) => {\n\t\t\tlog.success(message);\n\t\t},\n\t\toutro: (message) => {\n\t\t\toutro(message);\n\t\t},\n\t};\n}\n","import { PermissionError } from \"@bedrock-rbx/ocale\";\n\nimport type { ConfigError } from \"../core/config-error.ts\";\nimport type { MigrateError, MigrationSummary } from \"../core/migrate/migration-report.ts\";\nimport type { StateError } from \"../core/state.ts\";\nimport type { ApplyError } from \"../shell/apply-ops.ts\";\nimport type { BuildDesiredError } from \"../shell/build-desired.ts\";\nimport type { MissingCredentialError, UnsupportedBackendError } from \"../shell/build-state-port.ts\";\nimport type { DeployError } from \"../shell/deploy.ts\";\nimport type { SpawnOverrideError } from \"./dispatch-override.ts\";\nimport type { ParseMigrateError } from \"./parse-migrate-options.ts\";\nimport type { ParseOptionsError } from \"./parse-options.ts\";\n\n/**\n * Output port the CLI renders through. Mirrors the subset of `@clack/prompts`\n * the bedrock CLI uses today; tests inject a fake to assert what was rendered.\n *\n * @example\n *\n * ```ts\n * import type { ClackPort } from \"@bedrock-rbx/core\";\n *\n * const lines: Array<string> = [];\n * const port: ClackPort = {\n * cancel: (message) => lines.push(`cancel: ${message}`),\n * intro: (message) => lines.push(`intro: ${message}`),\n * logError: (message) => lines.push(`error: ${message}`),\n * logMessage: (message) => lines.push(`log: ${message}`),\n * logSuccess: (message) => lines.push(`ok: ${message}`),\n * outro: (message) => lines.push(`outro: ${message}`),\n * };\n *\n * port.logSuccess(\"done\");\n *\n * expect(lines).toEqual([\"ok: done\"]);\n * ```\n */\nexport interface ClackPort {\n\t/** End an interactive flow with a cancellation marker. */\n\tcancel(message: string): void;\n\t/** Open a framed section with a title (used for command intros). */\n\tintro(message: string): void;\n\t/** Render a single error line inside an open frame. */\n\tlogError(message: string): void;\n\t/** Render a single neutral line inside an open frame. */\n\tlogMessage(message: string): void;\n\t/** Render a single success line inside an open frame. */\n\tlogSuccess(message: string): void;\n\t/** Close the current framed section with a final message. */\n\toutro(message: string): void;\n}\n\n/** Inputs for {@link renderStateWriteError}. */\ninterface StateWriteErrorRender {\n\t/** Environment whose state could not be written. */\n\treadonly environment: string;\n\t/** The state-error returned by the adapter. */\n\treadonly err: StateError;\n}\n\n/** Inputs for {@link renderMigrationSummary}. */\ninterface MigrationSummaryRender {\n\t/** Path to the Markdown report on disk. Pointed at from the action-required and review-needed lines. */\n\treadonly reportPath: string;\n\t/** Aggregate counts from a `MigrationReport`. */\n\treadonly summary: MigrationSummary;\n}\n\n/** Inputs for {@link renderOverrideError}. */\ninterface OverrideErrorRender {\n\t/** Environment whose override spawn produced the error. */\n\treadonly environment: string;\n\t/** The spawn-override error returned by `dispatchOverride`. */\n\treadonly err: SpawnOverrideError;\n}\n\n/**\n * Render a `DeployError` to the supplied `ClackPort`. Most variants emit a\n * single error line; `applyFailed` emits one line per failing op in the\n * aggregate (in Phase 1 then Phase 2 input order). Wrapped variants\n * (`applyFailed`, `buildDesiredFailed`, `configLoadFailed`,\n * `stateReadFailed`, `stateWriteFailed`) surface the inner cause's\n * actionable detail (file path, resource key, parser message, HTTP failure,\n * validator issue) so the reader does not have to inspect the full cause to\n * act.\n * @param err - The deploy error to describe.\n * @param port - The output port the diagnostic is written to.\n */\nexport function renderDeployError(err: DeployError, port: ClackPort): void {\n\tif (err.kind === \"applyFailed\") {\n\t\tfor (const failure of err.cause.failures) {\n\t\t\tport.logError(`apply failed for '${failure.key}': ${applyCauseDetail(failure)}`);\n\t\t}\n\n\t\treturn;\n\t}\n\n\tport.logError(deployErrorMessage(err));\n}\n\n/**\n * Render a `ParseOptionsError` to the supplied `ClackPort` as a single\n * error line. Each variant names the offending flag so the diagnostic\n * pinpoints what the caller needs to change.\n * @param err - The parse error to describe.\n * @param port - The output port the diagnostic is written to.\n */\nexport function renderParseError(err: ParseOptionsError, port: ClackPort): void {\n\tport.logError(parseErrorMessage(err));\n}\n\n/**\n * Render a `SpawnOverrideError` to the supplied `ClackPort` as a single\n * error line that names the environment alongside the failure mode. On\n * `launchFailed` the child never produced output of its own, so the parent\n * carries the diagnostic; on `nonZeroExit` the parent's line attributes the\n * exit code to a specific environment when several spawns are running.\n * @param input - Environment + spawn-override error to describe.\n * @param port - The output port the diagnostic is written to.\n */\nexport function renderOverrideError(input: OverrideErrorRender, port: ClackPort): void {\n\tport.logError(overrideErrorMessage(input));\n}\n\n/**\n * Render the failure surfaced when override discovery throws a non-absence\n * filesystem error (for example `EACCES` on a `.bedrock/<command>.ts` that\n * exists but cannot be read). Discovery refuses to fall through to the\n * built-in path in that case, so the CLI reports the cause and exits rather\n * than crashing on the unhandled throw.\n * @param error - The value thrown during override discovery.\n * @param port - The output port the diagnostic is written to.\n */\nexport function renderOverrideDiscoveryError(error: unknown, port: ClackPort): void {\n\tport.logError(`override discovery failed: ${safeStringify(error)}`);\n}\n\n/**\n * Render a `ParseMigrateError` to the supplied `ClackPort`. Reuses\n * `parseErrorMessage` for the three flag-shape variants and adds a\n * dedicated message for `unknownSource` listing the supported sources.\n * @param err - The parse error to describe.\n * @param port - The output port the diagnostic is written to.\n */\nexport function renderMigrateParseError(err: ParseMigrateError, port: ClackPort): void {\n\tport.logError(migrateParseErrorMessage(err));\n}\n\n/**\n * Render a `MigrateError` to the supplied `ClackPort` as a single error\n * line. Each variant points at the offending Mantle state file path,\n * primary-environment input, or wrapped `ConfigError` so the reader can\n * act without inspecting the raw error object.\n * @param err - The migrate error to describe.\n * @param port - The output port the diagnostic is written to.\n */\nexport function renderMigrateError(err: MigrateError, port: ClackPort): void {\n\tport.logError(migrateErrorMessage(err));\n}\n\n/**\n * Render a `MissingCredentialError` or `UnsupportedBackendError`\n * surfaced when the migrate command tried to default-construct the\n * configured `StatePort` and was missing its inputs.\n * @param err - The error returned by `buildStatePort`.\n * @param port - The output port the diagnostic is written to.\n */\nexport function renderBuildStatePortError(\n\terr: MissingCredentialError | UnsupportedBackendError,\n\tport: ClackPort,\n): void {\n\tport.logError(buildStatePortErrorMessage(err));\n}\n\n/**\n * Render the post-migrate review prompt to the supplied `ClackPort`.\n * Three outcomes:\n *\n * - Any `ambiguous` warnings exist: emit a single error line directing\n * the user to the report. The migration ran but there are decisions\n * the user still needs to make before deploy will be meaningful.\n * - No `ambiguous` warnings but non-zero `blocked` / `deferred` /\n * `interpretive`: emit a single success line pointing at the report\n * for auditing.\n * - All counts zero: silent. The closing `outro(\"migrate succeeded\")`\n * already speaks for the run.\n * @param input - Counts plus the path of the Markdown report.\n * @param port - The output port the line is written to.\n */\nexport function renderMigrationSummary(input: MigrationSummaryRender, port: ClackPort): void {\n\tconst { ambiguousCount, blockedCount, deferredCount, interpretiveCount } = input.summary;\n\tif (ambiguousCount > 0) {\n\t\tport.logError(\n\t\t\t`action required: ${String(ambiguousCount)} fields need your input. See ${input.reportPath}`,\n\t\t);\n\t\treturn;\n\t}\n\n\tconst reviewable = blockedCount + deferredCount + interpretiveCount;\n\tif (reviewable > 0) {\n\t\tport.logSuccess(\n\t\t\t`migration complete; see ${input.reportPath} for ${String(reviewable)} auto-mapped or skipped fields`,\n\t\t);\n\t}\n}\n\n/**\n * Render a `StateError` produced when the migrator wrote a per-environment\n * state through the `StatePort`. Names the environment alongside the\n * adapter's failure reason so the reader knows which write failed.\n * @param input - Environment + state-error to describe.\n * @param port - The output port the diagnostic is written to.\n */\nexport function renderStateWriteError(input: StateWriteErrorRender, port: ClackPort): void {\n\tport.logError(\n\t\t`state write failed for '${input.environment}' (${input.err.file}): ${input.err.reason}`,\n\t);\n}\n\nfunction permissionDetail(err: PermissionError): string {\n\tconst isPlural = err.requiredScopes.length > 1;\n\tconst label = isPlural ? \"scopes\" : \"scope\";\n\tconst pronoun = isPlural ? \"them\" : \"it\";\n\tconst scopeList = err.requiredScopes.map((scope) => `'${scope}'`).join(\", \");\n\treturn `${err.message} on ${err.operationKey}: missing required ${label} ${scopeList}. Grant ${pronoun} on the API key at https://create.roblox.com/credentials`;\n}\n\nfunction safeStringify(value: unknown): string {\n\tif (value instanceof Error) {\n\t\treturn value.message;\n\t}\n\n\t// `String(value)` can throw on null-prototype objects or values whose\n\t// `toString` / `Symbol.toPrimitive` rejects coercion; fall back so the\n\t// renderer never crashes mid-diagnostic.\n\ttry {\n\t\treturn String(value);\n\t} catch {\n\t\treturn \"<unprintable cause>\";\n\t}\n}\n\nfunction applyCauseDetail(cause: ApplyError): string {\n\tswitch (cause.kind) {\n\t\tcase \"driverFailure\": {\n\t\t\tif (cause.cause instanceof PermissionError) {\n\t\t\t\treturn permissionDetail(cause.cause);\n\t\t\t}\n\n\t\t\treturn cause.cause.message;\n\t\t}\n\t\tcase \"unexpectedThrow\": {\n\t\t\treturn `unexpected error: ${safeStringify(cause.cause)}`;\n\t\t}\n\t\tcase \"updateUnsupported\": {\n\t\t\treturn \"update not supported\";\n\t\t}\n\t}\n}\n\nfunction buildDesiredDetail(cause: BuildDesiredError): string {\n\tswitch (cause.kind) {\n\t\tcase \"fileReadFailed\": {\n\t\t\treturn `for '${cause.key}' (${cause.filePath}): ${cause.reason}`;\n\t\t}\n\t\tcase \"iconRemovalRejected\": {\n\t\t\treturn `for '${cause.key}': ${cause.message}`;\n\t\t}\n\t\tcase \"redactedNameCollision\": {\n\t\t\tconst [first, second] = cause.keys;\n\t\t\treturn `for '${first}' and '${second}': ${cause.message}`;\n\t\t}\n\t}\n}\n\nfunction configErrorDetail(err: ConfigError): string {\n\tswitch (err.kind) {\n\t\tcase \"configFunctionFailed\": {\n\t\t\treturn `${err.sourceFile}: config function threw: ${err.message}`;\n\t\t}\n\t\tcase \"fileNotFound\": {\n\t\t\treturn `no bedrock config under ${err.searchedFrom}`;\n\t\t}\n\t\tcase \"luauRuntimeMissing\": {\n\t\t\treturn `${err.sourceFile}: ${err.hint}`;\n\t\t}\n\t\tcase \"parseFailed\": {\n\t\t\treturn `${err.sourceFile}: ${err.message}`;\n\t\t}\n\t\tcase \"validationFailed\": {\n\t\t\tconst first = err.issues[0];\n\t\t\treturn first === undefined\n\t\t\t\t? `${err.sourceFile}: invalid`\n\t\t\t\t: `${err.sourceFile}: ${first.path.join(\".\")} ${first.message}`;\n\t\t}\n\t}\n}\n\nfunction stateErrorDetail(cause: StateError): string {\n\treturn `(${cause.file}): ${cause.reason}`;\n}\n\n/* eslint-disable-next-line max-lines-per-function -- single exhaustive switch over every DeployError variant is clearer than splitting into a wrapped-vs-unwrapped predicate plus a parallel prefix table. */\nfunction deployErrorMessage(err: Exclude<DeployError, { kind: \"applyFailed\" }>): string {\n\tswitch (err.kind) {\n\t\tcase \"buildDesiredFailed\": {\n\t\t\treturn `build desired state failed ${buildDesiredDetail(err.cause)}`;\n\t\t}\n\t\tcase \"configLoadFailed\": {\n\t\t\treturn `config load failed: ${configErrorDetail(err.cause)}`;\n\t\t}\n\t\tcase \"incompletePassEntry\": {\n\t\t\treturn `pass '${err.key}' is missing '${err.missingField}' under environment '${err.environment}'`;\n\t\t}\n\t\tcase \"incompletePlaceEntry\": {\n\t\t\treturn `place '${err.key}' is missing '${err.missingField}' under environment '${err.environment}'`;\n\t\t}\n\t\tcase \"incompleteUniverseEntry\": {\n\t\t\treturn `universe is missing '${err.missingField}' under environment '${err.environment}'`;\n\t\t}\n\t\tcase \"missingCredential\": {\n\t\t\treturn `missing credential: environment variable ${err.variable} is not set`;\n\t\t}\n\t\tcase \"registryConfigMissing\": {\n\t\t\treturn `registry config missing '${err.missing}' (${err.hint})`;\n\t\t}\n\t\tcase \"stateNotConfigured\": {\n\t\t\treturn `state not configured for environment '${err.environment}'`;\n\t\t}\n\t\tcase \"stateReadFailed\": {\n\t\t\treturn `state read failed ${stateErrorDetail(err.cause)}`;\n\t\t}\n\t\tcase \"stateWriteFailed\": {\n\t\t\treturn `state write failed ${stateErrorDetail(err.cause)}`;\n\t\t}\n\t\tcase \"unknownEnvironment\": {\n\t\t\treturn `unknown environment '${err.environment}' (declared: ${err.declared.join(\", \")})`;\n\t\t}\n\t\tcase \"unsupportedBackend\": {\n\t\t\treturn `unsupported state backend '${err.backend}' (${err.hint})`;\n\t\t}\n\t}\n}\n\nfunction parseErrorMessage(err: ParseOptionsError): string {\n\tswitch (err.kind) {\n\t\tcase \"invalidValue\": {\n\t\t\treturn `invalid value for flag '--${err.flag}' (expected a string)`;\n\t\t}\n\t\tcase \"missingRequired\": {\n\t\t\treturn `missing required flag --${err.flag}`;\n\t\t}\n\t\tcase \"unknownFlag\": {\n\t\t\treturn `unknown flag '--${err.flag}'`;\n\t\t}\n\t}\n}\n\nfunction overrideErrorMessage(input: OverrideErrorRender): string {\n\tconst { environment, err } = input;\n\tif (err.kind === \"launchFailed\") {\n\t\treturn `${environment}: failed to launch override - ${err.cause.message}`;\n\t}\n\n\treturn `${environment}: override exited with code ${String(err.exitCode)}`;\n}\n\nfunction migrateParseErrorMessage(err: ParseMigrateError): string {\n\tif (err.kind === \"unknownSource\") {\n\t\treturn `unknown migration source '${err.received}' (supported: ${err.supported.join(\", \")})`;\n\t}\n\n\treturn parseErrorMessage(err);\n}\n\nfunction migrateErrorMessage(err: MigrateError): string {\n\tswitch (err.kind) {\n\t\tcase \"internalError\": {\n\t\t\treturn `migrate internal error: ${err.reason} (${configErrorDetail(err.cause)})`;\n\t\t}\n\t\tcase \"primaryEnvironmentNotFound\": {\n\t\t\treturn `primary environment '${err.primary}' not found (available: ${err.available.join(\", \")})`;\n\t\t}\n\t\tcase \"primaryEnvironmentRequired\": {\n\t\t\treturn `primary environment required (available: ${err.available.join(\", \")})`;\n\t\t}\n\t\tcase \"stateFileNotFound\": {\n\t\t\treturn `Mantle state file not found at '${err.path}'`;\n\t\t}\n\t\tcase \"stateParseFailed\": {\n\t\t\treturn `Mantle state file at '${err.path}' could not be parsed: ${err.reason}`;\n\t\t}\n\t\tcase \"unsupportedMantleStateVersion\": {\n\t\t\treturn `unsupported Mantle state version '${err.found}' (supported: ${err.supported.join(\", \")})`;\n\t\t}\n\t}\n}\n\nfunction buildStatePortErrorMessage(err: MissingCredentialError | UnsupportedBackendError): string {\n\tswitch (err.kind) {\n\t\tcase \"missingCredential\": {\n\t\t\treturn `missing credential: environment variable ${err.variable} is not set`;\n\t\t}\n\t\tcase \"unsupportedBackend\": {\n\t\t\treturn `unsupported state backend '${err.backend}' (${err.hint})`;\n\t\t}\n\t}\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport type { StateConfig } from \"./schema.ts\";\n\n/**\n * Failure surfaced when no `StateConfig` is configured for the requested\n * environment. The shell layer wraps this in a `DeployError` when default\n * state-port construction is requested but the project has not declared\n * where state should live.\n */\nexport interface StateNotConfiguredError {\n\t/** Environment that the resolver was called against. */\n\treadonly environment: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"stateNotConfigured\";\n}\n\n/**\n * Minimal structural input the state resolver needs. Both `Config`\n * (pre-merge, discriminated XOR union) and `ResolvedConfig` (post-merge)\n * satisfy this shape, so callers can route either in without coupling\n * the resolver to the discriminated-union arms.\n */\ninterface StateResolutionInputs {\n\treadonly environments: Record<string, undefined | { readonly state?: StateConfig }>;\n\treadonly state?: StateConfig;\n}\n\n/**\n * Pick the `StateConfig` that applies to `environment`. Per-environment\n * overrides win over the root block; if neither is present, returns\n * `Err(stateNotConfigured)` so the deploy boundary can surface a typed\n * error instead of silently falling back.\n *\n * @param config - Validated project config.\n * @param environment - Target environment name.\n * @returns The resolved `StateConfig`, or `Err(stateNotConfigured)` when\n * neither the environment override nor the root block is set.\n * @example\n *\n * ```ts\n * import { resolveStateConfig } from \"@bedrock-rbx/core\";\n *\n * const result = resolveStateConfig(\n * {\n * state: { backend: \"gist\", gistId: \"root-gist\" },\n * environments: {\n * production: { state: { backend: \"gist\", gistId: \"prod-gist\" } },\n * },\n * },\n * \"production\",\n * );\n *\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data).toContainEntry([\"gistId\", \"prod-gist\"]);\n * }\n * ```\n */\nexport function resolveStateConfig(\n\tconfig: StateResolutionInputs,\n\tenvironment: string,\n): Result<StateConfig, StateNotConfiguredError> {\n\tconst override = config.environments[environment]?.state;\n\tif (override !== undefined) {\n\t\treturn { data: override, success: true };\n\t}\n\n\tif (config.state !== undefined) {\n\t\treturn { data: config.state, success: true };\n\t}\n\n\treturn { err: { environment, kind: \"stateNotConfigured\" }, success: false };\n}\n","import type { Tagged } from \"type-fest\";\n\n/**\n * Regex source shared by the `ResourceKey` brand validator and the runtime\n * config schema. Kept as a string (not a `RegExp`) so arktype can consume it\n * directly in keyed-map signatures without re-escaping.\n */\nexport const RESOURCE_KEY_PATTERN_SOURCE = \"^[A-Za-z0-9_-]+$\";\n\nconst RESOURCE_KEY_PATTERN = new RegExp(RESOURCE_KEY_PATTERN_SOURCE);\n\nconst ROBLOX_ASSET_ID_PATTERN = /^\\d+$/;\n\nconst SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;\n\n/**\n * User-supplied identifier for a resource within a config (e.g. `\"vip-pass\"`).\n * Stable across deploys; used to correlate desired ↔ current state.\n */\nexport type ResourceKey = Tagged<string, \"ResourceKey\">;\n\n/**\n * Roblox-assigned numeric asset ID, represented as a string to avoid int64\n * precision loss in JavaScript.\n */\nexport type RobloxAssetId = Tagged<string, \"RobloxAssetId\">;\n\n/**\n * Lowercase hex-encoded SHA-256 digest (exactly 64 characters drawn from\n * `0-9a-f`). Used to detect changes to file-backed resource inputs such as\n * game-pass icons without re-uploading the file.\n */\nexport type Sha256Hex = Tagged<string, \"Sha256Hex\">;\n\n/**\n * Type predicate: test whether a raw string is a valid {@link ResourceKey}.\n *\n * Prefer this when the caller owns error handling (for example, constructing a\n * `Result` error in a shell-layer parser). Use {@link asResourceKey} when an\n * exception is the right failure mode.\n *\n * @example\n *\n * ```ts\n * import { isResourceKey } from \"@bedrock-rbx/core\";\n *\n * const valid = isResourceKey(\"vip-pass\");\n * const invalid = isResourceKey(\"vip pass\");\n * expect(valid).toBe(true);\n * expect(invalid).toBe(false);\n * ```\n *\n * @param raw - String to test.\n * @returns `true` when `raw` matches the ResourceKey shape; narrows `raw`.\n */\nexport function isResourceKey(raw: string): raw is ResourceKey {\n\treturn RESOURCE_KEY_PATTERN.test(raw);\n}\n\n/**\n * Validate and brand a raw string as a {@link ResourceKey}.\n *\n * Accepts non-empty strings of alphanumeric characters, hyphens, and\n * underscores (matching `/^[A-Za-z0-9_-]+$/`).\n *\n * @example\n *\n * ```ts\n * import { asResourceKey } from \"@bedrock-rbx/core\";\n *\n * const key = asResourceKey(\"vip-pass\");\n *\n * let thrown: unknown;\n * try {\n * asResourceKey(\"vip pass\");\n * } catch (error) {\n * thrown = error;\n * }\n *\n * expect(key).toBe(\"vip-pass\");\n * expect(thrown).toBeInstanceOf(RangeError);\n * ```\n *\n * @param raw - Raw string to validate and brand.\n * @returns The input re-typed as a {@link ResourceKey}.\n * @throws RangeError when `raw` is empty or contains disallowed characters.\n */\nexport function asResourceKey(raw: string): ResourceKey {\n\tif (!isResourceKey(raw)) {\n\t\tthrow new RangeError(\n\t\t\t`ResourceKey must match ${String(RESOURCE_KEY_PATTERN)} (got ${JSON.stringify(raw)})`,\n\t\t);\n\t}\n\n\treturn raw;\n}\n\n/**\n * Type predicate: test whether a raw string is a valid {@link RobloxAssetId}.\n *\n * Prefer this when the caller owns error handling (for example, constructing a\n * `Result` error in a shell-layer parser). Use {@link asRobloxAssetId} when an\n * exception is the right failure mode.\n *\n * @example\n *\n * ```ts\n * import { isRobloxAssetId } from \"@bedrock-rbx/core\";\n *\n * const valid = isRobloxAssetId(\"12345\");\n * const invalid = isRobloxAssetId(\"12345abc\");\n * expect(valid).toBe(true);\n * expect(invalid).toBe(false);\n * ```\n *\n * @param raw - String to test.\n * @returns `true` when `raw` matches the RobloxAssetId shape; narrows `raw`.\n */\nexport function isRobloxAssetId(raw: string): raw is RobloxAssetId {\n\treturn ROBLOX_ASSET_ID_PATTERN.test(raw);\n}\n\n/**\n * Validate and brand a raw string as a {@link RobloxAssetId}.\n *\n * Accepts non-empty digit-only strings (matching `/^\\d+$/`). Roblox asset IDs\n * are int64 values carried as strings because JavaScript's `number` cannot\n * represent the full int64 range.\n *\n * @example\n *\n * ```ts\n * import { asRobloxAssetId } from \"@bedrock-rbx/core\";\n *\n * const id = asRobloxAssetId(\"12345\");\n *\n * let thrown: unknown;\n * try {\n * asRobloxAssetId(\"12345abc\");\n * } catch (error) {\n * thrown = error;\n * }\n *\n * expect(id).toBe(\"12345\");\n * expect(thrown).toBeInstanceOf(RangeError);\n * ```\n *\n * @param raw - Raw string to validate and brand.\n * @returns The input re-typed as a {@link RobloxAssetId}.\n * @throws RangeError when `raw` is empty or contains non-digit characters.\n */\nexport function asRobloxAssetId(raw: string): RobloxAssetId {\n\tif (!isRobloxAssetId(raw)) {\n\t\tthrow new RangeError(\n\t\t\t`RobloxAssetId must be a non-empty digit-only string matching ${String(\n\t\t\t\tROBLOX_ASSET_ID_PATTERN,\n\t\t\t)} (got ${JSON.stringify(raw)})`,\n\t\t);\n\t}\n\n\treturn raw;\n}\n\n/**\n * Type predicate: test whether a raw string is a valid {@link Sha256Hex}.\n *\n * Accepts exactly 64 lowercase hexadecimal characters (matching\n * `/^[0-9a-f]{64}$/`). Prefer this when the caller owns error handling;\n * use {@link asSha256Hex} when throwing is the right failure mode.\n *\n * @example\n *\n * ```ts\n * import { isSha256Hex } from \"@bedrock-rbx/core\";\n *\n * const digest = \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\";\n * const valid = isSha256Hex(digest);\n * const invalid = isSha256Hex(digest.toUpperCase());\n * expect(valid).toBe(true);\n * expect(invalid).toBe(false);\n * ```\n *\n * @param raw - String to test.\n * @returns `true` when `raw` matches the Sha256Hex shape; narrows `raw`.\n */\nexport function isSha256Hex(raw: string): raw is Sha256Hex {\n\treturn SHA256_HEX_PATTERN.test(raw);\n}\n\n/**\n * Validate and brand a raw string as a {@link Sha256Hex}.\n *\n * Accepts exactly 64 lowercase hexadecimal characters. Uppercase hex, lengths\n * other than 64, and any non-hex character are rejected so the brand is a\n * canonical representation.\n *\n * @example\n *\n * ```ts\n * import { asSha256Hex } from \"@bedrock-rbx/core\";\n *\n * const digest = asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * );\n *\n * let thrown: unknown;\n * try {\n * asSha256Hex(\"deadbeef\");\n * } catch (error) {\n * thrown = error;\n * }\n *\n * expect(digest).toHaveLength(64);\n * expect(thrown).toBeInstanceOf(RangeError);\n * ```\n *\n * @param raw - Raw string to validate and brand.\n * @returns The input re-typed as a {@link Sha256Hex}.\n * @throws RangeError when `raw` is not exactly 64 lowercase hex characters.\n */\nexport function asSha256Hex(raw: string): Sha256Hex {\n\tif (!isSha256Hex(raw)) {\n\t\tthrow new RangeError(\n\t\t\t`Sha256Hex must be 64 lowercase hex characters matching ${String(\n\t\t\t\tSHA256_HEX_PATTERN,\n\t\t\t)} (got ${raw.length} chars: ${JSON.stringify(raw)})`,\n\t\t);\n\t}\n\n\treturn raw;\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport type { StateError } from \"./state.ts\";\n\n// Character class mirrors `RESOURCE_KEY_PATTERN_SOURCE` in types/ids.ts\n// (both are \"safe identifier\" alphabets). They're kept in separate literals\n// because the contracts are distinct: ResourceKey is unbounded, this one\n// caps length at 64 so adapter-stored filenames can't exceed filesystem\n// limits. If one alphabet changes, update the other deliberately.\n/**\n * Source pattern for environment names, including `^` and `$` anchors.\n * Letters, digits, `-`, `_`, length 1-64.\n *\n * Exported so the config schema can validate `environments` keys against\n * the same alphabet and length cap that adapters enforce on storage\n * identifiers. Single source of truth: changing the alphabet here changes\n * both the runtime check and the schema-level key constraint.\n *\n * Anchors are embedded so callers do not have to re-add them, matching\n * the `RESOURCE_KEY_PATTERN_SOURCE` convention in `types/ids.ts`.\n */\nexport const ENV_NAME_PATTERN_SOURCE = \"^[A-Za-z0-9_-]{1,64}$\";\n\nconst ENVIRONMENT_NAME_PATTERN = new RegExp(ENV_NAME_PATTERN_SOURCE);\n\n/**\n * Validate an environment name at a state-adapter boundary.\n *\n * Adapters that map environment names onto filesystem-like identifiers\n * (gist filenames, S3 keys) must reject names that could collide or escape\n * their storage layout. This helper accepts letters, digits, `-`, and `_`\n * only, with length between 1 and 64, and returns a `StateError` for\n * anything outside that set so the adapter can fail loudly instead of\n * silently stripping characters.\n *\n * @example\n *\n * ```ts\n * import { validateEnvironmentName } from \"@bedrock-rbx/core\";\n *\n * const ok = validateEnvironmentName(\"production\");\n * expect(ok.success).toBeTrue();\n *\n * const bad = validateEnvironmentName(\"prod/staging\");\n * expect(bad.success).toBeFalse();\n * ```\n *\n * @param environment - Raw environment name supplied by a caller.\n * @returns `Ok(environment)` when the name is safe to use, or\n * `Err(StateError)` with a descriptive reason when it is not.\n */\nexport function validateEnvironmentName(environment: string): Result<string, StateError> {\n\tif (!ENVIRONMENT_NAME_PATTERN.test(environment)) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tfile: environment,\n\t\t\t\tkind: \"stateError\",\n\t\t\t\treason: `invalid environment name: must match ${String(ENVIRONMENT_NAME_PATTERN)}`,\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: environment, success: true };\n}\n","/**\n * Compute the SHA-256 hex digest of a byte sequence. Shared by kind modules\n * that hash file contents as part of pre-I/O normalization.\n *\n * @param bytes - Source bytes; typically the output of an injected `readFile`.\n * @returns Lowercase 64-character hex string.\n */\nexport async function sha256Hex(bytes: Uint8Array): Promise<string> {\n\t// `Uint8Array.from(bytes)` narrows `Uint8Array<ArrayBufferLike>` to\n\t// `Uint8Array<ArrayBuffer>` for `crypto.subtle.digest`, which rejects the\n\t// SharedArrayBuffer variant at the type level.\n\tconst buffer = await crypto.subtle.digest(\"SHA-256\", Uint8Array.from(bytes));\n\treturn Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, \"0\")).join(\n\t\t\"\",\n\t);\n}\n","// 64x64 grayscale PNG (color type 0, bit depth 8) rendering the word\n// \"REDACTED\" in a built-in 5x7 pixel font on a horizontally striped\n// field. Inlined as bytes so the placeholder ships inside the published\n// package and resolves without any filesystem access.\nconst REDACTED_ICON_BYTES = new Uint8Array([\n\t0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,\n\t0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x02, 0x2e,\n\t0x02, 0x00, 0x00, 0x00, 0x89, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0xed, 0x56, 0xd1, 0x0e, 0x80,\n\t0x20, 0x08, 0xf4, 0x53, 0xfc, 0x94, 0xfe, 0xff, 0xa7, 0xae, 0x25, 0x62, 0x82, 0xbd, 0x14, 0x6e,\n\t0x39, 0x77, 0x6c, 0x1a, 0xc2, 0xbc, 0x40, 0x0f, 0x2a, 0xe5, 0xa0, 0xa4, 0x0d, 0x00, 0x8e, 0xa0,\n\t0x2c, 0x90, 0x02, 0x01, 0xc8, 0x03, 0xf2, 0x60, 0x52, 0x2d, 0xb0, 0x1f, 0xb0, 0xa1, 0xf4, 0x3c,\n\t0x00, 0x00, 0x99, 0x51, 0xf5, 0xeb, 0xa1, 0x8e, 0xdb, 0xde, 0xbc, 0xfe, 0xbb, 0x80, 0x32, 0xe0,\n\t0x74, 0xb5, 0xa8, 0xcd, 0x6a, 0x86, 0xca, 0x71, 0x00, 0x00, 0x6d, 0x96, 0xad, 0x65, 0x61, 0x00,\n\t0x3a, 0xef, 0x43, 0x04, 0xfe, 0x1d, 0xef, 0x53, 0xa8, 0xe8, 0xb1, 0x33, 0x90, 0xb0, 0xfb, 0x50,\n\t0x65, 0xd1, 0x52, 0x50, 0xef, 0x00, 0xf0, 0x55, 0xd8, 0x0f, 0xd8, 0x0f, 0xc8, 0x83, 0x59, 0xc5,\n\t0xb4, 0x01, 0x00, 0xff, 0x0f, 0x56, 0xb8, 0x05, 0x02, 0xe4, 0xff, 0xcf, 0xe0, 0x04, 0xe9, 0xf3,\n\t0xa6, 0xdb, 0xea, 0x95, 0x15, 0x74, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42,\n\t0x60, 0x82,\n]);\n\n/**\n * Sentinel path written into a resource's `icon[\"en-us\"]` field when\n * redaction substitutes the bedrock-supplied placeholder image. Callers\n * route this path through {@link withRedactedIcon} or through the\n * `readBytes` short-circuit; neither touches the filesystem.\n */\nexport const REDACTED_ICON_PATH = \"<bedrock:redacted-icon.png>\";\n\n/**\n * Embedded bytes of the bedrock-supplied placeholder icon. A 64x64\n * grayscale PNG that renders the word \"REDACTED\" centered on a striped\n * field. Used both for the icon-hash digest at normalize time and for\n * the multipart upload body the resource driver sends to Open Cloud.\n */\nexport { REDACTED_ICON_BYTES };\n\n/**\n * `true` when `path` is the redacted-icon sentinel. `readBytes` and\n * {@link withRedactedIcon} both use this predicate to decide whether to\n * bypass the injected file reader.\n *\n * @param path - Icon path supplied by a flattened or normalized resource entry.\n * @returns `true` for {@link REDACTED_ICON_PATH}; otherwise `false`.\n */\nexport function isRedactedIconPath(path: string): boolean {\n\treturn path === REDACTED_ICON_PATH;\n}\n\n/**\n * Wrap a `readFile` so the sentinel resolves to {@link REDACTED_ICON_BYTES}\n * without touching the inner reader. Applied once at the shell deploy /\n * preview boundary; the wrapped reader flows to every consumer (normalize,\n * registry drivers) unchanged.\n *\n * @param readFile - Inner reader that handles every non-sentinel path.\n * @returns Sentinel-aware reader with the same callable shape as `readFile`.\n */\nexport function withRedactedIcon(\n\treadFile: (path: string) => Promise<Uint8Array>,\n): (path: string) => Promise<Uint8Array> {\n\treturn async (path) => {\n\t\tif (isRedactedIconPath(path)) {\n\t\t\treturn new Uint8Array(REDACTED_ICON_BYTES);\n\t\t}\n\n\t\treturn readFile(path);\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport type { ResourceKey } from \"../../types/ids.ts\";\nimport { isRedactedIconPath, REDACTED_ICON_BYTES } from \"../redacted-icon.ts\";\nimport type { BuildDesiredError, KindIo } from \"./module.ts\";\n\n/**\n * Read file bytes via the injected reader, translating rejections into a\n * `fileReadFailed` `BuildDesiredError`. Shared by kind modules whose\n * pre-I/O normalization hashes a file the user declared by path. The\n * redacted-icon sentinel short-circuits to the embedded placeholder\n * bytes without invoking the injected reader, so a redaction-substituted\n * icon path produces a deterministic hash on every deploy.\n *\n * @param target - Path to read plus the resource key blamed on failure.\n * @param io - I/O surface carrying the injected `readFile` function.\n * @returns `Ok` with the bytes, or `Err` with a `fileReadFailed` error.\n */\nexport async function readBytes(\n\ttarget: { readonly filePath: string; readonly key: ResourceKey },\n\tio: KindIo,\n): Promise<Result<Uint8Array, BuildDesiredError>> {\n\tif (isRedactedIconPath(target.filePath)) {\n\t\treturn { data: new Uint8Array(REDACTED_ICON_BYTES), success: true };\n\t}\n\n\ttry {\n\t\treturn { data: await io.readFile(target.filePath), success: true };\n\t} catch (err) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tkey: target.key,\n\t\t\t\tfilePath: target.filePath,\n\t\t\t\tkind: \"fileReadFailed\",\n\t\t\t\treason: err instanceof Error ? err.message : String(err),\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { type, type Type } from \"arktype\";\n\nimport { asSha256Hex, type ResourceKey, type Sha256Hex } from \"../types/ids.ts\";\nimport { sha256Hex } from \"./kinds/hash.ts\";\nimport type { BuildDesiredError, KindIo } from \"./kinds/module.ts\";\nimport { readBytes } from \"./kinds/read-bytes.ts\";\n\n/**\n * ArkType schema for the locale-keyed icon path map. Today the only\n * accepted locale is `\"en-us\"`; declaring any other locale fails\n * validation. Shared by every icon-bearing entry schema (passes,\n * universe, and any future kind that adopts the same shape) so all\n * locale-rejection messages attribute the issue path identically.\n */\nexport const iconMap: Type<Record<\"en-us\", string>> = type({\n\t\"en-us\": \"string\",\n}).onUndeclaredKey(\"reject\");\n\n/**\n * Read one icon file via the injected I/O surface and return its branded\n * SHA-256 hex digest. Composes `readBytes` (which translates rejections\n * into `fileReadFailed` errors) with `sha256Hex` so kind modules and their\n * locale-iterating wrappers do not re-author the same orchestration.\n *\n * @param target - Path of the icon file plus the resource key blamed on\n * read failure; both are carried unchanged onto `BuildDesiredError`.\n * @param io - I/O surface carrying the injected `readFile`.\n * @returns `Ok` with the branded digest, or `Err` with a `fileReadFailed`\n * error carrying both `filePath` and `key` unchanged.\n */\nexport async function hashIconFile(\n\ttarget: { readonly filePath: string; readonly key: ResourceKey },\n\tio: KindIo,\n): Promise<Result<Sha256Hex, BuildDesiredError>> {\n\tconst read = await readBytes(target, io);\n\tif (!read.success) {\n\t\treturn read;\n\t}\n\n\treturn { data: asSha256Hex(await sha256Hex(read.data)), success: true };\n}\n\n/**\n * Hash every locale entry on a declared icon map, producing a hash map\n * keyed by the same locales. Iterates `hashIconFile` per locale so each\n * read failure surfaces the offending file's path verbatim. Stamped onto\n * desired-state entries by per-kind `normalize` implementations.\n *\n * @param input - Declared icon paths plus the resource key blamed on\n * read failure.\n * @param io - I/O surface carrying the injected `readFile`.\n * @returns `Ok` with hashes mirroring the locale shape of the input, or\n * `Err` from the first locale whose file could not be read.\n */\nexport async function hashIconLocales(\n\tinput: { readonly icon: Record<\"en-us\", string>; readonly key: ResourceKey },\n\tio: KindIo,\n): Promise<Result<Record<\"en-us\", Sha256Hex>, BuildDesiredError>> {\n\tconst enUs = await hashIconFile({ key: input.key, filePath: input.icon[\"en-us\"] }, io);\n\tif (!enUs.success) {\n\t\treturn enUs;\n\t}\n\n\treturn { data: { \"en-us\": enUs.data }, success: true };\n}\n\n/**\n * Locale-aware tri-state equality on icon-file hash maps. Used by per-kind\n * `fieldsEqual` implementations to detect drift on resources that carry an\n * optional locale-keyed icon (universe, game-pass, and any future kind that\n * adopts the same shape).\n *\n * The signature widens from kind-typed inputs to hash maps so the helper\n * does not couple to any specific resource-kind shape; callers project the\n * `iconFileHashes` field off their own current/desired values. Parameter\n * order mirrors `shouldReuploadIcon` so the two helpers can be read side\n * by side without re-orienting at each call.\n *\n * Tri-state semantics:\n *\n * - both `undefined` (icon absent on both sides): `true`.\n * - one `undefined`, the other present: `false`.\n * - both present: per-locale hash equality on the `\"en-us\"` key.\n *\n * @param current - Hashes recorded on the prior current-state entry.\n * @param desired - Hashes layered onto the desired-state entry by `normalize`.\n * @returns `true` when no re-upload is implied by the hash comparison.\n */\nexport function iconHashesEqual(\n\tcurrent: Record<\"en-us\", Sha256Hex> | undefined,\n\tdesired: Record<\"en-us\", Sha256Hex> | undefined,\n): boolean {\n\tif (current === undefined) {\n\t\treturn desired === undefined;\n\t}\n\n\tif (desired === undefined) {\n\t\treturn false;\n\t}\n\n\treturn current[\"en-us\"] === desired[\"en-us\"];\n}\n\n/**\n * Cost-gate for icon re-uploads. Returns `true` when the locally-hashed\n * desired icon differs from the hash recorded on the prior current-state\n * entry, signalling that the driver must re-upload before reconciling.\n * Returns `false` when the hashes match (no re-upload needed) and when\n * both sides report no icon.\n *\n * The signature takes hash maps directly (not whole-state) so the helper\n * is independent of any specific resource-kind shape; every icon-bearing\n * driver projects its own `iconFileHashes` and `outputs.iconFileHashes`\n * fields before calling.\n *\n * @param currentHashes - Hashes recorded on the prior current-state entry.\n * @param desiredHashes - Hashes layered onto the desired-state entry by\n * `normalize` from the local icon file's bytes.\n * @returns `true` when the driver should re-upload the icon.\n *\n * @example\n *\n * ```ts\n * import { asSha256Hex, shouldReuploadIcon } from \"@bedrock-rbx/core\";\n *\n * const previous = asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * );\n * const fresh = asSha256Hex(\n * \"2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881\",\n * );\n *\n * expect(shouldReuploadIcon({ \"en-us\": previous }, { \"en-us\": previous })).toBe(false);\n * expect(shouldReuploadIcon({ \"en-us\": previous }, { \"en-us\": fresh })).toBe(true);\n * ```\n */\nexport function shouldReuploadIcon(\n\tcurrentHashes: Record<\"en-us\", Sha256Hex> | undefined,\n\tdesiredHashes: Record<\"en-us\", Sha256Hex> | undefined,\n): boolean {\n\treturn !iconHashesEqual(currentHashes, desiredHashes);\n}\n","import type { EnvironmentEntry, UniverseEntry } from \"./schema.ts\";\n\n/**\n * Loose authored-shape that the runtime narrow operates on before the\n * config gets cast to the discriminated `Config` union. Mirrors the\n * arktype schema's inferred shape: every field is structurally a\n * supertype of every `Config` variant arm, so the narrow can run before\n * the XOR rule has discriminated the value into one arm.\n */\ninterface LooseConfigForValidation {\n\treadonly environments: Record<string, EnvironmentEntry>;\n\treadonly universe?: UniverseEntry;\n}\n\ninterface UniverseIdIssue {\n\treadonly message: string;\n\treadonly path: ReadonlyArray<string>;\n}\n\n/**\n * Walk the loose authored-shape and surface every place the\n * universeId-XOR-between-root-and-env rule is violated. Pure: returns\n * the issue list; the caller hands it to arktype's `ctx.reject` so each\n * one lands at the offending config path. The schema's runtime narrow\n * uses this to enforce the rule at validation time before the validated\n * value is cast to the strict `Config` discriminated union.\n *\n * @param value - Parsed config the schema is validating.\n * @returns Zero or more issues. Empty when the config satisfies the rule.\n */\nexport function collectUniverseIdIssues(\n\tvalue: LooseConfigForValidation,\n): ReadonlyArray<UniverseIdIssue> {\n\tconst rootUniverseId = value.universe?.universeId;\n\tconst hasRootUniverseBlock = value.universe !== undefined;\n\tconst environmentEntries = Object.entries(value.environments);\n\tconst hasEnvironmentUniverseId = environmentEntries.some(\n\t\t([, environment]) => environment.universe?.universeId !== undefined,\n\t);\n\n\tconst environmentIssues = collectEnvironmentIssues(rootUniverseId, environmentEntries);\n\tconst rootIssues =\n\t\thasRootUniverseBlock && rootUniverseId === undefined && !hasEnvironmentUniverseId\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\"universeId must be declared on the root universe block, or on every environment that declares its own universe overlay.\",\n\t\t\t\t\t\tpath: [\"universe\", \"universeId\"],\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: [];\n\n\treturn [...environmentIssues, ...rootIssues];\n}\n\nfunction collectEnvironmentIssues(\n\trootUniverseId: string | undefined,\n\tenvironmentEntries: ReadonlyArray<readonly [string, EnvironmentEntry]>,\n): ReadonlyArray<UniverseIdIssue> {\n\treturn environmentEntries.flatMap(([environmentName, environment]) => {\n\t\tif (environment.universe === undefined) {\n\t\t\treturn [];\n\t\t}\n\n\t\tif (rootUniverseId !== undefined && environment.universe.universeId !== undefined) {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"universeId is declared at the root universe block; remove it from this environment overlay (root is authoritative) or remove it from the root and declare it on every environment.\",\n\t\t\t\t\tpath: [\"environments\", environmentName, \"universe\", \"universeId\"],\n\t\t\t\t},\n\t\t\t];\n\t\t}\n\n\t\tif (rootUniverseId === undefined && environment.universe.universeId === undefined) {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"universeId must be declared on this environment overlay because the root universe block does not provide one.\",\n\t\t\t\t\tpath: [\"environments\", environmentName, \"universe\", \"universeId\"],\n\t\t\t\t},\n\t\t\t];\n\t\t}\n\n\t\treturn [];\n\t});\n}\n","/* eslint-disable max-lines -- centralized public-API schema; growing the surface here is expected. */\nimport type { Result } from \"@bedrock-rbx/ocale\";\nimport type { SocialLink } from \"@bedrock-rbx/ocale/universes\";\n\nimport { ArkErrors, type, type Type } from \"arktype\";\nimport type { SetRequired } from \"type-fest\";\n\nimport { RESOURCE_KEY_PATTERN_SOURCE } from \"../types/ids.ts\";\nimport type { ConfigError } from \"./config-error.ts\";\nimport { ENV_NAME_PATTERN_SOURCE } from \"./environment.ts\";\nimport { iconMap } from \"./icons.ts\";\nimport { collectUniverseIdIssues } from \"./validate-universe-xor.ts\";\n\n/**\n * Per-field redaction override for a game-pass entry. Each supplied field\n * replaces the matching bedrock-supplied placeholder; omitted fields fall\n * through to the placeholder defaults. The object form implies redaction\n * is enabled, so authors who want only defaults should write\n * `redacted: true` instead of an empty object.\n *\n * @example\n *\n * ```ts\n * import type { GamePassEntry, RedactedGamePassOverride } from \"@bedrock-rbx/core/config\";\n *\n * const override: RedactedGamePassOverride = { name: \"Closed Beta\", price: 500 };\n *\n * const entry: GamePassEntry = {\n * name: \"VIP Pass\",\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip.png\" },\n * price: 1500,\n * redacted: override,\n * };\n *\n * expect(entry.redacted).toStrictEqual({ name: \"Closed Beta\", price: 500 });\n * ```\n */\nexport interface RedactedGamePassOverride {\n\t/** Override name; falls through to the bedrock default when omitted. */\n\tname?: string | undefined;\n\t/** Override description; falls through to the bedrock default when omitted. */\n\tdescription?: string | undefined;\n\t/** Override icon path; falls through to the embedded placeholder when omitted. */\n\ticon?: Record<\"en-us\", string> | undefined;\n\t/**\n\t * Override Robux price; falls through to the bedrock default (`99999`) when\n\t * omitted. Ignored when the entry's `price` is `undefined` so an off-sale\n\t * pass stays off-sale through redaction.\n\t */\n\tprice?: number | undefined;\n}\n\n/**\n * Per-field redaction override for a place entry. Each supplied field\n * replaces the matching bedrock-supplied placeholder; omitted `description`\n * falls through to the placeholder default. `displayName` has no\n * placeholder default; an omitted `displayName` preserves the real value\n * declared on the entry. The object form implies redaction is enabled, so\n * authors who want only the description default should write\n * `redacted: true` instead of an empty object.\n *\n * @example\n *\n * ```ts\n * import type { PlaceEntry, RedactedPlaceOverride } from \"@bedrock-rbx/core/config\";\n *\n * const override: RedactedPlaceOverride = { displayName: \"Hidden Project\" };\n *\n * const entry: PlaceEntry = {\n * description: \"The lobby place.\",\n * displayName: \"Start Place\",\n * filePath: \"places/start.rbxl\",\n * redacted: override,\n * };\n *\n * expect(entry.redacted).toStrictEqual({ displayName: \"Hidden Project\" });\n * ```\n */\nexport interface RedactedPlaceOverride {\n\t/** Override description; falls through to the bedrock default when omitted. */\n\tdescription?: string | undefined;\n\t/** Override display name; preserves the real entry value when omitted (no default). */\n\tdisplayName?: string | undefined;\n}\n\n/**\n * Env-scoped redaction override that applies across every redactable kind in\n * a single environment. Each field is projected onto the kinds whose own\n * override type names it: `price`, `name`, and `icon` reach passes and\n * products; `description` reaches passes, products, and places; `displayName`\n * reaches places. Fields a kind does not recognize are silently ignored for\n * that kind.\n *\n * Composes field-by-field with per-resource overrides at the root and inside\n * an env overlay; the most-specific layer wins per field. Boolean `true`\n * contributes no fields; `false` carves the resource out at its layer.\n *\n * @example\n *\n * ```ts\n * import type { Config, RedactedEnvironmentOverride } from \"@bedrock-rbx/core/config\";\n *\n * const devRedaction: RedactedEnvironmentOverride = { price: 1 };\n *\n * const config: Config = {\n * environments: { dev: { redacted: devRedaction } },\n * passes: {\n * \"vip-pass\": {\n * name: \"VIP Pass\",\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip.png\" },\n * price: 500,\n * },\n * },\n * state: { backend: \"gist\", gistId: \"abc123\" },\n * };\n *\n * expect(config.environments[\"dev\"]?.redacted).toStrictEqual({ price: 1 });\n * ```\n */\nexport interface RedactedEnvironmentOverride {\n\t/** Override name applied to every passes and products entry the env redacts. */\n\tname?: string | undefined;\n\t/** Override description applied to every passes, products, and places entry the env redacts. */\n\tdescription?: string | undefined;\n\t/** Override display name applied only to places (and universes, when their redaction lands). */\n\tdisplayName?: string | undefined;\n\t/** Override icon path applied to every passes and products entry the env redacts. */\n\ticon?: Record<\"en-us\", string> | undefined;\n\t/** Override Robux price applied to every on-sale passes and products entry the env redacts. */\n\tprice?: number | undefined;\n}\n\n/**\n * Body of a single entry in the `passes` collection. Keys in the parent\n * record are `ResourceKey`-shaped strings enforced at schema validation.\n */\nexport interface GamePassEntry {\n\t/** Name shown on the Roblox storefront. */\n\tname: string;\n\t/** Description shown on the game-pass detail page. */\n\tdescription: string;\n\t/**\n\t * Locale-keyed icon path. The Roblox game-pass API is monolingual, so\n\t * only the `\"en-us\"` key is accepted; the map shape leaves room for\n\t * translated icons should the API ever expose them.\n\t */\n\ticon: Record<\"en-us\", string>;\n\t/** Robux price, or omitted / `undefined` for off-sale. */\n\tprice?: number | undefined;\n\t/**\n\t * Set to `true` to deploy this pass with bedrock-supplied placeholder\n\t * content (default name, empty description, embedded placeholder icon,\n\t * price `99999` Robux when the entry is on-sale) in place of the real\n\t * values declared above. Off-sale passes (`price` omitted) stay off-sale.\n\t * Set to a {@link RedactedGamePassOverride} to substitute selected\n\t * placeholders with custom values while leaving the rest at bedrock\n\t * defaults; the object form implies redaction is enabled. Omit or set\n\t * `false` to push the real values unchanged. Environment overlays accept\n\t * the same shape and compose field-by-field with this layer.\n\t */\n\tredacted?: boolean | RedactedGamePassOverride | undefined;\n}\n\n/**\n * Per-field redaction override for a developer-product entry. Each supplied\n * field replaces the matching bedrock-supplied placeholder; omitted fields\n * fall through to the placeholder defaults. The object form implies\n * redaction is enabled, so authors who want only defaults should write\n * `redacted: true` instead of an empty object.\n *\n * @example\n *\n * ```ts\n * import type {\n * DeveloperProductEntry,\n * RedactedDeveloperProductOverride,\n * } from \"@bedrock-rbx/core/config\";\n *\n * const override: RedactedDeveloperProductOverride = {\n * name: \"Closed Beta Pack\",\n * price: 500,\n * };\n *\n * const entry: DeveloperProductEntry = {\n * name: \"Gem Pack\",\n * description: \"Stocks the player up with 1,000 premium gems.\",\n * price: 1500,\n * redacted: override,\n * };\n *\n * expect(entry.redacted).toStrictEqual({ name: \"Closed Beta Pack\", price: 500 });\n * ```\n */\nexport interface RedactedDeveloperProductOverride {\n\t/** Override name; falls through to the bedrock default when omitted. */\n\tname?: string | undefined;\n\t/** Override description; falls through to the bedrock default when omitted. */\n\tdescription?: string | undefined;\n\t/** Override icon path; falls through to the embedded placeholder when omitted. */\n\ticon?: Record<\"en-us\", string> | undefined;\n\t/**\n\t * Override Robux price; falls through to the bedrock default (`99999`) when\n\t * omitted. Ignored when the entry's `price` is `undefined` so an off-sale\n\t * product stays off-sale through redaction.\n\t */\n\tprice?: number | undefined;\n}\n\n/**\n * Body of a single entry in the `products` collection. Keys in the parent\n * record are `ResourceKey`-shaped strings enforced at schema validation.\n */\nexport interface DeveloperProductEntry {\n\t/** Name shown on the Roblox storefront. */\n\tname: string;\n\t/** Description shown on the developer-product detail page. */\n\tdescription: string;\n\t/**\n\t * Locale-keyed icon path. Mirrors `GamePassEntry.icon`; the Roblox\n\t * developer-product API is monolingual, so only the `\"en-us\"` key is\n\t * accepted.\n\t */\n\ticon?: Record<\"en-us\", string>;\n\t/**\n\t * Whether Roblox-managed regional pricing applies to the product.\n\t * Tri-state: omit (or set `undefined`) to leave the flag unmanaged;\n\t * setting `true` or `false` is propagated to Roblox on every deploy.\n\t */\n\tisRegionalPricingEnabled?: boolean | undefined;\n\t/**\n\t * Robux price. Omit (or set `undefined`) for an off-sale product;\n\t * re-adding the field puts the product back on sale on the next deploy.\n\t */\n\tprice?: number | undefined;\n\t/**\n\t * Set to `true` to deploy this product with bedrock-supplied placeholder\n\t * content (default name, empty description, embedded placeholder icon,\n\t * price `99999` Robux when the entry is on-sale) in place of the real\n\t * values declared above. Off-sale products (`price` omitted) stay\n\t * off-sale. Set to a {@link RedactedDeveloperProductOverride} to\n\t * substitute selected placeholders with custom values while leaving the\n\t * rest at bedrock defaults; the object form implies redaction is enabled.\n\t * Omit or set `false` to push the real values unchanged. Environment\n\t * overlays accept the same shape and compose field-by-field with this\n\t * layer.\n\t */\n\tredacted?: boolean | RedactedDeveloperProductOverride | undefined;\n\t/**\n\t * Whether the product appears on the universe's external store page.\n\t * Tri-state: omit (or set `undefined`) to leave the flag unmanaged.\n\t * The Roblox v2 create endpoint does not accept this field, so the\n\t * driver applies it via a follow-up PATCH after the create POST.\n\t */\n\tstorePageEnabled?: boolean | undefined;\n}\n\n/**\n * Body of a single entry under the root `places` collection. Carries the\n * file-path environments share plus the optional Open-Cloud-supported\n * metadata fields. The Roblox `placeId` is environment-specific and lives\n * on each per-environment overlay so the same `.rbxl` file can publish to\n * different places across staging, production, and so on.\n */\nexport interface PlaceEntry {\n\t/** User-facing description shown on the place's detail page. */\n\tdescription?: string | undefined;\n\t/** User-facing place name shown on the Roblox storefront. */\n\tdisplayName?: string | undefined;\n\t/** Path to the `.rbxl` or `.rbxlx` file; handed to `readFile` verbatim by `buildDesired`. */\n\tfilePath: string;\n\t/**\n\t * Set to `true` to deploy this place with bedrock-supplied placeholder\n\t * content (empty description) in place of the real values declared\n\t * above. `displayName` is preserved by default because it surfaces in\n\t * Roblox Studio's place picker and the Creator Hub experience list;\n\t * authors who want full opacity write the object form\n\t * {@link RedactedPlaceOverride} to substitute selected placeholders\n\t * with custom values, including `displayName`. The object form\n\t * implies redaction is enabled. Omit or set `false` to push the real\n\t * values unchanged. Environment overlays accept the same shape and\n\t * compose field-by-field with this layer.\n\t */\n\tredacted?: boolean | RedactedPlaceOverride | undefined;\n\t/** Maximum players per server; positive integer. */\n\tserverSize?: number | undefined;\n}\n\n/**\n * Body of a places entry after `selectEnvironment` has merged the\n * matching per-environment overlay onto the root entry. `filePath` flows\n * from the root (or an overlay override), `placeId` is supplied by the\n * per-environment overlay, and the optional metadata fields fall through\n * from the root unless overridden per-environment.\n *\n * `placeId` is user-supplied because Open Cloud cannot mint places; the\n * place must already exist in Roblox before Bedrock can publish versions\n * to it.\n */\nexport interface ResolvedPlaceEntry {\n\t/** User-facing description shown on the place's detail page. */\n\tdescription?: string | undefined;\n\t/** User-facing place name shown on the Roblox storefront. */\n\tdisplayName?: string | undefined;\n\t/** Path to the `.rbxl` or `.rbxlx` file; handed to `readFile` verbatim by `buildDesired`. */\n\tfilePath: string;\n\t/** Existing Roblox place ID. */\n\tplaceId: string;\n\t/**\n\t * Resolved redaction setting after merging the per-environment overlay\n\t * onto the root entry. See {@link PlaceEntry.redacted} for the\n\t * authored shape.\n\t */\n\tredacted?: boolean | RedactedPlaceOverride | undefined;\n\t/** Maximum players per server; positive integer. */\n\tserverSize?: number | undefined;\n}\n\n/**\n * Body of the singleton `universe` block. Bedrock synthesizes the\n * `ResourceKey` (`\"main\"`) in `flattenConfig`, so user config supplies\n * only the existing `universeId` plus any managed fields they want\n * bedrock to own. Fields omitted here remain unmanaged (the diff treats\n * them as non-drift and the driver omits them from the `updateMask`).\n *\n * `universeId` is user-supplied because Open Cloud cannot mint universes;\n * the universe must already exist in Roblox before bedrock can reconcile\n * its configuration. Declare `universeId` either here at the root (which\n * applies to every environment) or under each `environments[name].universe`\n * overlay, but never both: the schema rejects a config that sets it in\n * both places, and rejects a `universe` block without a resolvable\n * `universeId`.\n */\nexport interface UniverseEntry {\n\t/** Whether console players can join; omit or set `undefined` to leave unmanaged. */\n\tconsoleEnabled?: boolean | undefined;\n\t/** Whether desktop players can join; omit or set `undefined` to leave unmanaged. */\n\tdesktopEnabled?: boolean | undefined;\n\t/**\n\t * Discord social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\tdiscordSocialLink?: SocialLink | undefined;\n\t/**\n\t * Display name for the universe. Because Roblox derives this from\n\t * the root place's name, the driver routes the update through\n\t * `PlacesClient.update`; omit or set `undefined` to leave unmanaged.\n\t */\n\tdisplayName?: string | undefined;\n\t/**\n\t * Facebook social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\tfacebookSocialLink?: SocialLink | undefined;\n\t/**\n\t * Guilded social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\tguildedSocialLink?: SocialLink | undefined;\n\t/** Whether mobile players can join; omit or set `undefined` to leave unmanaged. */\n\tmobileEnabled?: boolean | undefined;\n\t/**\n\t * Private-server price in Robux. Declare as `undefined` to disable\n\t * private servers (cancels active subscriptions); omit to leave the\n\t * server value untouched.\n\t */\n\tprivateServerPriceRobux?: number | undefined;\n\t/**\n\t * Roblox Group social link; omit to leave the server value untouched, set\n\t * to `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\trobloxGroupSocialLink?: SocialLink | undefined;\n\t/** Whether tablet players can join; omit or set `undefined` to leave unmanaged. */\n\ttabletEnabled?: boolean | undefined;\n\t/**\n\t * Twitch social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\ttwitchSocialLink?: SocialLink | undefined;\n\t/**\n\t * Twitter social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\ttwitterSocialLink?: SocialLink | undefined;\n\t/**\n\t * Existing Roblox universe ID. Optional in this entry shape because\n\t * authors may declare it here (root-authoritative, single universe) or\n\t * on each `environments[name].universe` overlay (per-environment\n\t * universes), but never both.\n\t */\n\tuniverseId?: string | undefined;\n\t/** Whether voice chat is enabled; omit or set `undefined` to leave unmanaged. */\n\tvoiceChatEnabled?: boolean | undefined;\n\t/** Whether VR players can join; omit or set `undefined` to leave unmanaged. */\n\tvrEnabled?: boolean | undefined;\n\t/**\n\t * YouTube social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\tyoutubeSocialLink?: SocialLink | undefined;\n}\n\n/**\n * State configuration for the GitHub Gist backend. Holds the public gist\n * ID; the library reads the GitHub token from `BEDROCK_GITHUB_TOKEN` when\n * it default-constructs the adapter.\n */\nexport interface GistStateConfig {\n\t/** Discriminator selecting the gist adapter. */\n\treadonly backend: \"gist\";\n\t/** ID of an existing GitHub Gist that holds this project's state files. */\n\treadonly gistId: string;\n}\n\n/**\n * Tagged union describing where Bedrock persists its state. The `backend`\n * tag is `\"gist\" | (string & {})` so unknown names autocomplete the\n * builtins while permitting custom values for plugin scenarios. The\n * dispatch path inside `deploy()` rejects unknown names with a typed\n * `unsupportedBackend` error.\n */\nexport type StateConfig = GistStateConfig | { readonly backend: string & {} };\n\n/**\n * Body of a single entry under `environments`. Per-environment overrides\n * narrow root-level settings for that environment without redefining\n * unrelated fields. Resource overlays (`passes`, `places`, `universe`)\n * derive their field shapes from the matching root entry types so adding\n * a field to a base entry surfaces on the overlay automatically.\n *\n * `placeId` stays required when the matching `places` overlay is present\n * because each environment targets its own Roblox place. `universeId` is\n * optional on the `universe` overlay because authors may declare it\n * either at the root (root-authoritative) or per environment, but never\n * both: the schema enforces this XOR at validation time, attributing the\n * failure to the offending field's path.\n */\nexport interface EnvironmentEntry {\n\t/**\n\t * Human-readable label fed to the project-level\n\t * {@link DisplayNamePrefixConfig.format | displayNamePrefix.format}\n\t * template. An environment without a label (or with an empty string)\n\t * is implicitly excluded from prefixing even when the project enables\n\t * it.\n\t */\n\tlabel?: string | undefined;\n\t/**\n\t * Per-environment game-pass overlay. Every field is optional; missing\n\t * fields fall through to the matching root `passes` entry at merge time.\n\t *\n\t * Uses a partial `GamePassEntry` directly rather than `Overlay<T, K>`\n\t * because game passes have no user-supplied identity key (Open Cloud\n\t * mints the asset ID). The `redacted` field accepts the same shape it\n\t * does at the root entry: a boolean toggle or a {@link RedactedGamePassOverride}\n\t * carrying per-field overrides for this resource in this environment.\n\t */\n\tpasses?: Record<string, Partial<GamePassEntry>>;\n\t/**\n\t * Per-environment places overlay. `placeId` is required on every\n\t * declared entry; `filePath` is optional and falls through to the\n\t * matching root `places` entry when omitted.\n\t */\n\tplaces?: Record<string, Overlay<ResolvedPlaceEntry, \"placeId\">>;\n\t/**\n\t * Per-environment developer-product overlay. Every field is optional;\n\t * missing fields fall through to the matching root `products` entry at\n\t * merge time. Mirrors the `passes` shape because developer products\n\t * also have no user-supplied identity key (Open Cloud mints the\n\t * `productId`). The `redacted` field accepts the same shape it does\n\t * at the root entry: a boolean toggle or a\n\t * {@link RedactedDeveloperProductOverride} carrying per-field\n\t * overrides for this resource in this environment.\n\t */\n\tproducts?: Record<string, Partial<DeveloperProductEntry>>;\n\t/**\n\t * Per-environment redaction layer. Accepts a boolean toggle or a\n\t * {@link RedactedEnvironmentOverride} carrying cross-kind override\n\t * fields. Per-resource `redacted` flags on the merged config take\n\t * precedence per field; `false` at any layer carves out at that\n\t * layer.\n\t */\n\tredacted?: boolean | RedactedEnvironmentOverride | undefined;\n\t/** Per-environment state override; takes precedence over root `state`. */\n\tstate?: StateConfig;\n\t/**\n\t * Per-environment universe overlay. Every field is optional, including\n\t * `universeId`: the schema-level XOR rule requires `universeId` here if\n\t * and only if the root `universe` block does not declare one. Other\n\t * fields fall through to the root `universe` block when omitted.\n\t */\n\tuniverse?: Partial<UniverseEntry>;\n}\n\n/**\n * Per-kind entry registry. Each `ResourceKind` must have a matching entry\n * type or `ResourceEntryByKind[K]` is a compile error. Modelled as an\n * interface (not a type alias) so downstream resource kinds can declare\n * their entry type alongside the kind's other domain types without\n * touching this module.\n *\n * @example\n *\n * ```ts\n * import type { ResourceEntryByKind } from \"@bedrock-rbx/core/config\";\n *\n * const entry: ResourceEntryByKind[\"gamePass\"] = {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * name: \"VIP Pass\",\n * price: 500,\n * };\n *\n * expect(entry.name).toBe(\"VIP Pass\");\n * ```\n */\nexport interface ResourceEntryByKind {\n\t/** Authored entry body for a developer-product resource. */\n\tdeveloperProduct: DeveloperProductEntry;\n\t/** Authored entry body for a game-pass resource. */\n\tgamePass: GamePassEntry;\n\t/** Post-merge entry body for a place resource (root + env overlay). */\n\tplace: ResolvedPlaceEntry;\n\t/** Authored entry body for a universe resource. */\n\tuniverse: UniverseEntry;\n}\n\n/**\n * Project-level prefixing policy for universe and place display names.\n * Each environment's `label` flows through `format` to render a prefix\n * that `selectEnvironment` prepends to every declared display name.\n *\n * Defaults: `enabled` is `true`; `format` is `\"[{LABEL}] \"`.\n */\nexport interface DisplayNamePrefixConfig {\n\t/**\n\t * Whether the project applies environment-label prefixing. Treat\n\t * `undefined` as enabled; set `false` to opt out across the project.\n\t */\n\tenabled?: boolean | undefined;\n\t/**\n\t * Template string applied to each environment's `label`. Placeholders:\n\t *\n\t * - `{label}`: label as written.\n\t * - `{LABEL}`: upper-cased label.\n\t * - `{Label}`: capitalized label (first character upper, rest as\n\t * written).\n\t *\n\t * Any other characters in the template flow through verbatim. The\n\t * rendered string is prepended to each declared `displayName`.\n\t */\n\tformat?: string | undefined;\n}\n\n/**\n * Helper that produces a shallow `Omit<T, K>` without using TypeScript's\n * built-in `Omit` (deprecated under the project's lint rules because of\n * its lossy interaction with mapped types).\n *\n * @template T - Source type to project keys away from.\n * @template Key - Key (or union of keys) on `T` to remove.\n */\nexport type WithoutKey<T, Key extends keyof T> = Pick<T, Exclude<keyof T, Key>>;\n\n/**\n * Per-environment universe overlay shape that prevents `universeId` from\n * being redeclared alongside a root-authoritative `universeId`.\n * Used by {@link ConfigRootUniverseId}: when the root universe block\n * declares `universeId`, no per-env overlay may redeclare it. Setting\n * `universeId` here produces a descriptive type error pointing at this\n * field rather than the opaque `never` message.\n */\nexport type UniverseOverlayWithoutId = Partial<WithoutKey<UniverseEntry, \"universeId\">> & {\n\tuniverseId?: \"universeId is already declared on the root universe block; remove it from this environment overlay, or remove it from root and declare it on every environment overlay instead\" & {\n\t\treadonly errorBrand: never;\n\t};\n};\n\n/**\n * Per-environment universe overlay shape that requires `universeId`.\n * Used by {@link ConfigEnvironmentUniverseId}: when the root universe\n * block does not declare `universeId`, every env that declares a\n * `universe` overlay must supply one of its own.\n */\nexport type UniverseOverlayWithId = Partial<WithoutKey<UniverseEntry, \"universeId\">> & {\n\tuniverseId: string;\n};\n\n/**\n * Variant of `Config` where the root `universe` block declares\n * `universeId`. Per-environment universe overlays may carry shared\n * fields (device flags, social links, display name) but cannot\n * redeclare `universeId`; the schema rejects any env overlay that\n * does. The runtime `selectEnvironment` merges shared-field overlays\n * onto the root and inherits `universeId` from the root unchanged.\n */\nexport type ConfigRootUniverseId = ConfigBase & {\n\t/**\n\t * Per-environment overrides keyed by environment name. Required and\n\t * non-empty; environment names match `[A-Za-z0-9_-]{1,64}`. Each env\n\t * entry's `universe` overlay forbids `universeId` because the root\n\t * declares it.\n\t */\n\tenvironments: Record<\n\t\tstring,\n\t\tWithoutKey<EnvironmentEntry, \"universe\"> & { universe?: UniverseOverlayWithoutId }\n\t>;\n\t/**\n\t * Singleton universe block declaring the Roblox universe bedrock\n\t * manages. `universeId` is required in this variant because no\n\t * per-environment overlay may supply one.\n\t */\n\tuniverse?: UniverseEntry & { universeId: string };\n};\n\n/**\n * Variant of `Config` where the root `universe` block omits\n * `universeId`. Every env that declares a `universe` overlay must\n * supply its own `universeId`; envs that omit the overlay deploy no\n * universe at all. The root may still carry shared fields (device\n * flags, social links, display name, icon) which `selectEnvironment`\n * merges onto each env's overlay at resolution time.\n */\nexport type ConfigEnvironmentUniverseId = ConfigBase & {\n\t/**\n\t * Per-environment overrides keyed by environment name. Required and\n\t * non-empty; environment names match `[A-Za-z0-9_-]{1,64}`. Every\n\t * env that declares a `universe` overlay must include `universeId`\n\t * because the root universe block does not provide one.\n\t */\n\tenvironments: Record<\n\t\tstring,\n\t\tWithoutKey<EnvironmentEntry, \"universe\"> & { universe?: UniverseOverlayWithId }\n\t>;\n\t/**\n\t * Singleton universe block declaring the Roblox universe bedrock\n\t * manages. `universeId` is not permitted here in this variant because\n\t * every environment supplies its own; setting it produces a descriptive\n\t * type error rather than the opaque `never` message.\n\t */\n\tuniverse?: WithoutKey<UniverseEntry, \"universeId\"> & {\n\t\tuniverseId?: \"universeId is already declared per environment; remove it from the root universe block, or remove it from every environment overlay and declare it here instead\" & {\n\t\t\treadonly errorBrand: never;\n\t\t};\n\t};\n};\n\n/**\n * Validated project config as accepted by `loadConfig`. Plain mutable so\n * users can adjust fields in a long-running script before deploying.\n *\n * Discriminated union over the location of `universeId`: it lives at the\n * root universe block ({@link ConfigRootUniverseId}) or on every\n * environment universe overlay ({@link ConfigEnvironmentUniverseId}), but never\n * both. The TypeScript types reject the both-set case at compile time,\n * and the arktype runtime narrow rejects every offending field path at\n * `validateConfig` time. State must be configured at the root or under\n * every entry of `environments`; `resolveStateConfig` surfaces the\n * missing case at the deploy boundary as `stateNotConfigured`.\n *\n * @example\n *\n * ```ts\n * import type { Config } from \"@bedrock-rbx/core/config\";\n *\n * const config: Config = {\n * environments: { production: {} },\n * state: { backend: \"gist\", gistId: \"abc123def456\" },\n * passes: {\n * \"vip-pass\": {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * name: \"VIP Pass\",\n * price: 500,\n * },\n * },\n * };\n *\n * expect(config.passes![\"vip-pass\"]!.name).toBe(\"VIP Pass\");\n * ```\n */\nexport type Config = ConfigEnvironmentUniverseId | ConfigRootUniverseId;\n\n/**\n * Body of the singleton `universe` block after `selectEnvironment` has\n * merged a per-environment overlay onto the root. Identical to\n * {@link UniverseEntry} except `universeId` is required: the schema-level\n * XOR rule ensures every projected universe carries a resolved\n * `universeId`. Resource drivers consume this shape rather than\n * `UniverseEntry` so the post-merge invariant is visible in the type\n * system.\n */\nexport interface ResolvedUniverseEntry extends Pick<\n\tUniverseEntry,\n\tExclude<keyof UniverseEntry, \"universeId\">\n> {\n\t/** Existing Roblox universe ID, resolved from the root or per-environment overlay. */\n\tuniverseId: string;\n}\n\n/**\n * Project config after `selectEnvironment` has merged a single\n * environment's overlays onto the root. The shape mirrors `Config`\n * except `places` carries `ResolvedPlaceEntry` (both `filePath` and\n * `placeId`), since the resolver fails before this point if an entry is\n * missing its environment-supplied `placeId`. Downstream consumers\n * (`flattenConfig`, `buildDefaultRegistry`, the deploy pipeline) accept\n * this shape rather than `Config` so the post-merge invariant is visible\n * in the type system.\n *\n * @example\n *\n * ```ts\n * import { selectEnvironment, type ResolvedConfig } from \"@bedrock-rbx/core\";\n * import type { Config } from \"@bedrock-rbx/core/config\";\n *\n * const config: Config = {\n * environments: {\n * production: { places: { \"start-place\": { placeId: \"4711\" } } },\n * },\n * places: { \"start-place\": { filePath: \"places/start.rbxl\" } },\n * state: { backend: \"gist\", gistId: \"abc\" },\n * };\n *\n * const result = selectEnvironment(config, \"production\");\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * const resolved: ResolvedConfig = result.data;\n * expect(resolved.places?.[\"start-place\"]?.placeId).toBe(\"4711\");\n * }\n * ```\n */\nexport interface ResolvedConfig extends Pick<ConfigBase, Exclude<keyof ConfigBase, \"places\">> {\n\t/**\n\t * Per-environment overrides preserved from the source `Config`.\n\t * Carried for downstream context; `selectEnvironment` does not read\n\t * other environments after resolving the requested one.\n\t */\n\tenvironments: Record<string, EnvironmentEntry>;\n\t/** Keyed-map collection of resolved place entries; both `filePath` and `placeId` are present. */\n\tplaces?: Record<string, ResolvedPlaceEntry>;\n\t/**\n\t * Singleton universe block after `selectEnvironment` has resolved the\n\t * XOR between root and per-environment `universeId`. The schema narrow\n\t * rejects any config that would leave `universeId` unresolved, so the\n\t * post-merge invariant promotes `universeId` from optional to required.\n\t */\n\tuniverse?: ResolvedUniverseEntry;\n}\n\n/**\n * Overlay shape used by per-environment entries: every field of `T`\n * becomes optional, except `RequiredKey`, which stays required so the\n * overlay still re-asserts the identity-bearing field of its target\n * resource.\n *\n * @template T - Base entry type whose field shapes the overlay derives from.\n * @template RequiredKey - Identity-bearing key on `T` that the overlay must\n * still declare (for example `\"placeId\"` or `\"universeId\"`).\n */\ntype Overlay<T, RequiredKey extends keyof T> = SetRequired<Partial<T>, RequiredKey>;\n\n/**\n * Fields shared by every {@link Config} variant. The discriminated\n * `Config` union narrows `universe` and `environments` to enforce the\n * `universeId` XOR rule between the root and per-environment overlays;\n * everything else lives here.\n */\ninterface ConfigBase {\n\t/**\n\t * Project-level prefixing of universe and place display names with the\n\t * environment label. Default behaviour (when omitted) is enabled with a\n\t * `\"[{LABEL}] \"` template; set `enabled: false` to opt out, or set\n\t * `format` to a custom template.\n\t */\n\tdisplayNamePrefix?: DisplayNamePrefixConfig;\n\t/** Reserved at the root for c12's config layering / overlay work. */\n\textends?: unknown;\n\t/** Keyed-map collection of game-pass entries by user-supplied ResourceKey. */\n\tpasses?: Record<string, GamePassEntry>;\n\t/** Keyed-map collection of place entries by user-supplied ResourceKey. */\n\tplaces?: Record<string, PlaceEntry>;\n\t/** Keyed-map collection of developer-product entries by user-supplied ResourceKey. */\n\tproducts?: Record<string, DeveloperProductEntry>;\n\t/** Where Bedrock persists state for this project; required at deploy time. */\n\tstate?: StateConfig;\n}\n\n/**\n * Narrow a `StateConfig` to the `GistStateConfig` arm. The `(string & {})`\n * autocomplete idiom prevents TypeScript from narrowing on\n * `backend === \"gist\"` alone, so dispatch sites use this guard to\n * preserve the `gistId` field shape.\n *\n * @example\n *\n * ```ts\n * import { isGistStateConfig } from \"@bedrock-rbx/core\";\n * import type { StateConfig } from \"@bedrock-rbx/core/config\";\n *\n * const config: StateConfig = { backend: \"gist\", gistId: \"abc\" };\n *\n * expect(isGistStateConfig(config)).toBeTrue();\n * if (isGistStateConfig(config)) {\n * expect(config.gistId).toBe(\"abc\");\n * }\n * ```\n *\n * @param config - Resolved state config to inspect.\n * @returns `true` when `config.backend === \"gist\"`; otherwise `false`.\n */\nexport function isGistStateConfig(config: StateConfig): config is GistStateConfig {\n\treturn config.backend === \"gist\";\n}\n\nconst OPTIONAL_BOOLEAN = \"boolean | undefined\";\n\nconst OPTIONAL_STRING = \"string | undefined\";\n\nconst REDACTED_KEY = \"redacted?\";\n\nconst NON_EMPTY_OVERRIDE_MESSAGE =\n\t\"a non-empty override object; use `redacted: true` for default placeholders\";\n\n/**\n * Shared arktype constraint for any optional positive-integer field.\n * Reused by per-kind entry schemas so positive-integer fields validate\n * identically.\n */\nexport const OPTIONAL_POSITIVE_INTEGER = \"(number.integer >= 1) | undefined\";\n\n/**\n * Shared arktype constraint for any optional Robux-price field. The schema\n * rejects negatives, fractional values, `NaN`, and `Infinity` at config\n * validation time so a malformed price surfaces with a path attributing the\n * failure to the offending field, rather than slipping through to the\n * Roblox API and surfacing as an opaque error at apply time. Per-kind entry\n * schemas reuse this constant so all Robux-price fields validate\n * identically.\n */\nexport const OPTIONAL_ROBUX_PRICE = \"number.integer >= 0 | undefined\";\n\nconst gamePassRedactedOverride = type({\n\t\"description?\": \"string\",\n\t\"icon?\": iconMap,\n\t\"name?\": \"string\",\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n})\n\t.onUndeclaredKey(\"reject\")\n\t.narrow((value, ctx) => {\n\t\tif (Object.keys(value).length === 0) {\n\t\t\treturn ctx.mustBe(NON_EMPTY_OVERRIDE_MESSAGE);\n\t\t}\n\n\t\treturn true;\n\t});\n\nconst gamePassRedacted = gamePassRedactedOverride.or(OPTIONAL_BOOLEAN);\n\nconst placeRedactedOverride = type({\n\t\"description?\": \"string\",\n\t\"displayName?\": \"string\",\n})\n\t.onUndeclaredKey(\"reject\")\n\t.narrow((value, ctx) => {\n\t\tif (Object.keys(value).length === 0) {\n\t\t\treturn ctx.mustBe(NON_EMPTY_OVERRIDE_MESSAGE);\n\t\t}\n\n\t\treturn true;\n\t});\n\nconst placeRedacted = placeRedactedOverride.or(OPTIONAL_BOOLEAN);\n\nconst productRedactedOverride = type({\n\t\"description?\": \"string\",\n\t\"icon?\": iconMap,\n\t\"name?\": \"string\",\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n})\n\t.onUndeclaredKey(\"reject\")\n\t.narrow((value, ctx) => {\n\t\tif (Object.keys(value).length === 0) {\n\t\t\treturn ctx.mustBe(NON_EMPTY_OVERRIDE_MESSAGE);\n\t\t}\n\n\t\treturn true;\n\t});\n\nconst productRedacted = productRedactedOverride.or(OPTIONAL_BOOLEAN);\n\nconst environmentRedactedOverride = type({\n\t\"description?\": \"string\",\n\t\"displayName?\": \"string\",\n\t\"icon?\": iconMap,\n\t\"name?\": \"string\",\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n})\n\t.onUndeclaredKey(\"reject\")\n\t.narrow((value, ctx) => {\n\t\tif (Object.keys(value).length === 0) {\n\t\t\treturn ctx.mustBe(NON_EMPTY_OVERRIDE_MESSAGE);\n\t\t}\n\n\t\treturn true;\n\t});\n\nconst environmentRedacted = environmentRedactedOverride.or(OPTIONAL_BOOLEAN);\n\n// Resource-kind entry schemas. Adding a new kind is two additions:\n// 1. Declare its entry schema and keyed-map collection below.\n// 2. Reference that collection as an optional property on `rootSchema`.\n// No existing entries change. The ResourceKey regex lives on the map key\n// signature so invalid identifiers surface as schema failures pointing at\n// the offending key, not as deferred errors downstream.\nconst gamePassEntry = type({\n\t\"name\": \"string\",\n\t\"description\": \"string\",\n\t\"icon\": iconMap,\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n\t[REDACTED_KEY]: gamePassRedacted,\n});\n\nconst passesCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: gamePassEntry,\n}).onUndeclaredKey(\"reject\");\n\nconst developerProductEntry = type({\n\t\"name\": \"string\",\n\t\"description\": \"string\",\n\t\"icon?\": iconMap,\n\t\"isRegionalPricingEnabled?\": OPTIONAL_BOOLEAN,\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n\t[REDACTED_KEY]: productRedacted,\n\t\"storePageEnabled?\": OPTIONAL_BOOLEAN,\n}).onUndeclaredKey(\"reject\");\n\nconst productsCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: developerProductEntry,\n}).onUndeclaredKey(\"reject\");\n\nconst ROBLOX_ID_DIGITS = \"string.digits\";\n\nconst placeEntry = type({\n\t\"description?\": OPTIONAL_STRING,\n\t\"displayName?\": OPTIONAL_STRING,\n\t\"filePath\": \"string\",\n\t[REDACTED_KEY]: placeRedacted,\n\t\"serverSize?\": OPTIONAL_POSITIVE_INTEGER,\n}).onUndeclaredKey(\"reject\");\n\nconst placesCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: placeEntry,\n}).onUndeclaredKey(\"reject\");\n\nconst socialLink = type({\n\ttitle: \"string\",\n\turi: \"string\",\n}).onUndeclaredKey(\"reject\");\n\nconst socialLinkOrUndefined = socialLink.or(\"undefined\");\n\nconst universeEntry = type({\n\t\"consoleEnabled?\": OPTIONAL_BOOLEAN,\n\t\"desktopEnabled?\": OPTIONAL_BOOLEAN,\n\t\"discordSocialLink?\": socialLinkOrUndefined,\n\t\"displayName?\": OPTIONAL_STRING,\n\t\"facebookSocialLink?\": socialLinkOrUndefined,\n\t\"guildedSocialLink?\": socialLinkOrUndefined,\n\t\"mobileEnabled?\": OPTIONAL_BOOLEAN,\n\t\"privateServerPriceRobux?\": OPTIONAL_ROBUX_PRICE,\n\t\"robloxGroupSocialLink?\": socialLinkOrUndefined,\n\t\"tabletEnabled?\": OPTIONAL_BOOLEAN,\n\t\"twitchSocialLink?\": socialLinkOrUndefined,\n\t\"twitterSocialLink?\": socialLinkOrUndefined,\n\t\"universeId?\": ROBLOX_ID_DIGITS,\n\t\"voiceChatEnabled?\": OPTIONAL_BOOLEAN,\n\t\"vrEnabled?\": OPTIONAL_BOOLEAN,\n\t\"youtubeSocialLink?\": socialLinkOrUndefined,\n}).onUndeclaredKey(\"reject\");\n\nconst stateConfig = type({\n\t\"backend\": \"string\",\n\t\"gistId?\": \"string > 0\",\n}).onUndeclaredKey(\"reject\");\n\n// Overlay schemas mirror the base entry schemas but with every field\n// optional, except the identity-bearing key (`placeId`, `universeId`)\n// which stays required. Game passes have no user-supplied identity, so\n// the overlay is fully partial.\nconst gamePassOverlay = type({\n\t\"description?\": \"string\",\n\t\"icon?\": iconMap,\n\t\"name?\": \"string\",\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n\t[REDACTED_KEY]: gamePassRedacted,\n}).onUndeclaredKey(\"reject\");\n\nconst passesOverlayCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: gamePassOverlay,\n}).onUndeclaredKey(\"reject\");\n\nconst developerProductOverlay = type({\n\t\"description?\": \"string\",\n\t\"icon?\": iconMap,\n\t\"isRegionalPricingEnabled?\": OPTIONAL_BOOLEAN,\n\t\"name?\": \"string\",\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n\t[REDACTED_KEY]: productRedacted,\n\t\"storePageEnabled?\": OPTIONAL_BOOLEAN,\n}).onUndeclaredKey(\"reject\");\n\nconst productsOverlayCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: developerProductOverlay,\n}).onUndeclaredKey(\"reject\");\n\nconst placeOverlay = type({\n\t\"description?\": OPTIONAL_STRING,\n\t\"displayName?\": OPTIONAL_STRING,\n\t\"filePath?\": \"string\",\n\t\"placeId\": ROBLOX_ID_DIGITS,\n\t[REDACTED_KEY]: placeRedacted,\n\t\"serverSize?\": OPTIONAL_POSITIVE_INTEGER,\n}).onUndeclaredKey(\"reject\");\n\nconst placesOverlayCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: placeOverlay,\n}).onUndeclaredKey(\"reject\");\n\n// `Partial<UniverseEntry>` is structurally equal to `UniverseEntry`\n// itself because every field on `UniverseEntry` is optional. Reusing\n// `universeEntry` here keeps the field set in lockstep and avoids a\n// parallel declaration to drift. The XOR rule that ties root and\n// per-environment `universeId` together lives on `rootSchema` below,\n// where both sides of the relationship are in scope.\nconst universeOverlay = universeEntry;\n\nconst environmentEntry: Type<EnvironmentEntry> = type({\n\t\"label?\": OPTIONAL_STRING,\n\t\"passes?\": passesOverlayCollection,\n\t\"places?\": placesOverlayCollection,\n\t\"products?\": productsOverlayCollection,\n\t[REDACTED_KEY]: environmentRedacted,\n\t\"state?\": stateConfig,\n\t\"universe?\": universeOverlay,\n}).onUndeclaredKey(\"reject\");\n\nconst displayNamePrefix: Type<DisplayNamePrefixConfig> = type({\n\t\"enabled?\": OPTIONAL_BOOLEAN,\n\t\"format?\": OPTIONAL_STRING,\n}).onUndeclaredKey(\"reject\");\n\nconst environmentsCollection = type({\n\t[`[/${ENV_NAME_PATTERN_SOURCE}/]`]: environmentEntry,\n})\n\t.onUndeclaredKey(\"reject\")\n\t.narrow((value, ctx) => {\n\t\tif (Object.keys(value).length === 0) {\n\t\t\treturn ctx.mustBe(\"an environments record with at least one declared environment\");\n\t\t}\n\n\t\treturn true;\n\t});\n\n// `rootSchema` is intentionally not annotated `Type<Config>` because\n// `Config` is a discriminated union enforcing the universeId XOR rule\n// at the type level. The arktype schema describes the loose\n// authored-shape that's structurally a supertype of every union arm;\n// the runtime narrow rejects any value that doesn't satisfy one arm so\n// `validateConfig` can cast the result to `Config` safely. Splitting\n// the schema into two `.or()` variants would mirror the type union but\n// duplicate every field declaration without buying additional runtime\n// coverage on top of the narrow.\nconst rootSchema = type({\n\t\"displayNamePrefix?\": displayNamePrefix,\n\t\"environments\": environmentsCollection,\n\t\"extends?\": \"unknown\",\n\t\"passes?\": passesCollection,\n\t\"places?\": placesCollection,\n\t\"products?\": productsCollection,\n\t\"state?\": stateConfig,\n\t\"universe?\": universeEntry,\n})\n\t.onUndeclaredKey(\"reject\")\n\t.narrow((value, ctx) => {\n\t\t// `ctx.reject` returns `false` for every issue and `reduce` walks the\n\t\t// whole list so every offending field gets attributed; the seeded\n\t\t// `true` flips to `false` on the first issue.\n\t\treturn collectUniverseIdIssues(value).reduce<boolean>((_accumulator, issue) => {\n\t\t\treturn ctx.reject({ message: issue.message, path: [...issue.path] });\n\t\t}, true);\n\t});\n\n/**\n * Validate a parsed config value against the runtime schema. Returns the\n * validated `Config` on success or a `validationFailed` `ConfigError` with\n * one issue per problem, each attributed to a field path. `sourceFile`\n * appears in the error so callers can point a human at the offending file.\n *\n * @param input - Parsed value from a config source (object tree from a\n * config loader, or a hand-built literal). Shape is checked, not assumed.\n * @param sourceFile - Path or identifier of the source file, used in the\n * `validationFailed` error.\n * @returns `Ok` with the validated `Config`, or `Err` with a\n * `validationFailed` error carrying each issue's field path.\n * @example\n *\n * ```ts\n * import { validateConfig } from \"@bedrock-rbx/core\";\n *\n * const ok = validateConfig(\n * {\n * environments: { production: {} },\n * passes: {\n * \"vip-pass\": {\n * description: \"VIP perks.\",\n * icon: { \"en-us\": \"assets/vip.png\" },\n * name: \"VIP Pass\",\n * price: 500,\n * },\n * },\n * },\n * \"bedrock.config.ts\",\n * );\n * expect(ok.success).toBeTrue();\n *\n * const err = validateConfig(\n * { environments: { production: {} }, passes: { \"vip-pass\": { name: \"VIP\" } } },\n * \"bedrock.config.ts\",\n * );\n * expect(err.success).toBeFalse();\n * if (!err.success) {\n * expect(err.err.kind).toBe(\"validationFailed\");\n * }\n * ```\n */\nexport function validateConfig(input: unknown, sourceFile: string): Result<Config, ConfigError> {\n\tconst validated = rootSchema(input);\n\tif (validated instanceof ArkErrors) {\n\t\tconst issues = Array.from(validated, (issue) => {\n\t\t\treturn {\n\t\t\t\tmessage: issue.message,\n\t\t\t\tpath: [...issue.path].map((segment) => String(segment)),\n\t\t\t};\n\t\t});\n\n\t\treturn {\n\t\t\terr: { issues, kind: \"validationFailed\", sourceFile },\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\t// Precondition for the cast: the runtime narrow rejects every value\n\t// that violates the universeId XOR rule, so a successful validation\n\t// always lands in one arm of the discriminated `Config` union. The\n\t// schema's inferred type is the structurally loose authored-shape\n\t// (universeId optional in both root and per-env overlays); the cast\n\t// collapses it to the strict union without loss of safety.\n\treturn { data: validated as unknown as Config, success: true };\n}\n","import { createClackPort } from \"../cli/clack-port.ts\";\nimport { type ClackPort, renderDeployError } from \"../cli/render.ts\";\nimport { resolveStateConfig } from \"../core/resolve-state-config.ts\";\nimport {\n\ttype Config,\n\tisGistStateConfig,\n\ttype ResolvedConfig,\n\ttype StateConfig,\n} from \"../core/schema.ts\";\nimport type {\n\tProgressEvent,\n\tProgressPort,\n\tResourceOpSucceededCreateEvent,\n} from \"../ports/progress-port.ts\";\nimport type { ApplyError } from \"../shell/apply-ops.ts\";\n\n/**\n * Configuration for {@link createClackProgressAdapter}.\n */\nexport interface ClackProgressAdapterDeps {\n\t/** Output port the events are rendered through. */\n\treadonly clack: ClackPort;\n\t/**\n\t * Loaded project config (raw `Config` or env-resolved `ResolvedConfig`);\n\t * the `stateWritten` case resolves the per-environment `StateConfig`\n\t * against this to format the backend label. When omitted, `stateWritten`\n\t * renders the generic `\"state\"` placeholder.\n\t */\n\treadonly config?: Config | ResolvedConfig;\n}\n\n/**\n * Build a {@link ProgressPort} that renders events through a `ClackPort`.\n * Pattern-matches on the event `kind`: per-resource events render one line each,\n * the aggregate `applySummary` becomes the deploy footer, and `stateWritten`\n * names the persistence backend resolved from the loaded `Config`.\n *\n * @example\n *\n * ```ts\n * import { createClackProgressAdapter, type ClackPort } from \"@bedrock-rbx/core\";\n *\n * const lines: Array<string> = [];\n * const clack: ClackPort = {\n * cancel: (message) => lines.push(`cancel: ${message}`),\n * intro: (message) => lines.push(`intro: ${message}`),\n * logError: (message) => lines.push(`error: ${message}`),\n * logMessage: (message) => lines.push(`log: ${message}`),\n * logSuccess: (message) => lines.push(`ok: ${message}`),\n * outro: (message) => lines.push(`outro: ${message}`),\n * };\n *\n * const port = createClackProgressAdapter({ clack });\n *\n * port.emit({ environment: \"production\", kind: \"stateWritten\" });\n *\n * expect(lines).toEqual([\"log: State written to state\"]);\n * ```\n *\n * @param deps - The clack port and optional config the adapter renders through.\n * @returns A `ProgressPort` that renders via clack.\n */\nexport function createClackProgressAdapter(deps: ClackProgressAdapterDeps): ProgressPort {\n\treturn {\n\t\temit(event: ProgressEvent): void {\n\t\t\trenderEvent(event, deps);\n\t\t},\n\t};\n}\n\n/**\n * Build a {@link ProgressPort} for the default CLI rendering path: wires a\n * fresh {@link createClackPort} into {@link createClackProgressAdapter}. The\n * `config` argument (raw `Config` or env-resolved `ResolvedConfig`) is\n * forwarded so `stateWritten` events can name the persistence backend; pass\n * `undefined` when the config has not yet loaded.\n *\n * Internal: used by `deploy()`'s default-port resolver when callers omit\n * `progress` and `BEDROCK_CLI` is set.\n *\n * @param config - Pre-loaded or env-resolved config used to format the\n * state-backend label, or `undefined` to render the generic placeholder.\n * @returns A clack-backed `ProgressPort` that writes to `process.stdout`.\n */\nexport function createDefaultProgressAdapter(\n\tconfig: Config | ResolvedConfig | undefined,\n): ProgressPort {\n\tconst clack = createClackPort();\n\treturn config === undefined\n\t\t? createClackProgressAdapter({ clack })\n\t\t: createClackProgressAdapter({ clack, config });\n}\n\nfunction applySummaryLine(event: Extract<ProgressEvent, { kind: \"applySummary\" }>): string {\n\tconst seconds = (event.durationMs / 1000).toFixed(1);\n\tconst parts = [\n\t\t`${event.created} create`,\n\t\t`${event.updated} update`,\n\t\t`${event.noop} noop`,\n\t\t`${event.failed} failed`,\n\t];\n\treturn `Succeeded in ${seconds}s: ${parts.join(\", \")}`;\n}\n\nfunction stateConfigLabel(state: StateConfig): string {\n\tif (isGistStateConfig(state)) {\n\t\treturn `gist:${state.gistId}`;\n\t}\n\n\treturn state.backend;\n}\n\nfunction formatStateLabel(\n\tconfig: Config | ResolvedConfig | undefined,\n\tenvironment: string,\n): string {\n\tif (config === undefined) {\n\t\treturn \"state\";\n\t}\n\n\tconst resolved = resolveStateConfig(config, environment);\n\tif (!resolved.success) {\n\t\treturn \"state\";\n\t}\n\n\treturn stateConfigLabel(resolved.data);\n}\n\nfunction extractResourceId(event: ResourceOpSucceededCreateEvent): string | undefined {\n\tswitch (event.resourceKind) {\n\t\tcase \"developerProduct\": {\n\t\t\treturn event.outputs.productId;\n\t\t}\n\t\tcase \"gamePass\": {\n\t\t\treturn event.outputs.assetId;\n\t\t}\n\t\tcase \"place\": {\n\t\t\treturn undefined;\n\t\t}\n\t\tcase \"universe\": {\n\t\t\treturn event.outputs.rootPlaceId;\n\t\t}\n\t}\n}\n\nfunction renderResourceOpSucceeded(\n\tevent: Extract<ProgressEvent, { kind: \"resourceOpSucceeded\" }>,\n\tclack: ClackPort,\n): void {\n\tif (event.opType === \"create\") {\n\t\tconst id = extractResourceId(event);\n\t\tconst suffix = id === undefined ? \"\" : ` (id ${id})`;\n\t\tclack.logSuccess(`${event.resourceKind}.${event.key} created${suffix}`);\n\t\treturn;\n\t}\n\n\tclack.logSuccess(\n\t\t`${event.resourceKind}.${event.key} ${event.changedFields.join(\", \")} updated`,\n\t);\n}\n\nfunction describeApplyError(error: ApplyError): string {\n\tswitch (error.kind) {\n\t\tcase \"driverFailure\": {\n\t\t\treturn `failed: ${error.cause.message}`;\n\t\t}\n\t\tcase \"unexpectedThrow\": {\n\t\t\treturn \"unexpected error\";\n\t\t}\n\t\tcase \"updateUnsupported\": {\n\t\t\treturn \"update not supported\";\n\t\t}\n\t}\n}\n\n/* eslint-disable-next-line max-lines-per-function -- single exhaustive switch over every ProgressEvent variant is clearer than splitting into deploy-level vs per-resource halves, which would leave both halves non-exhaustive and required a boolean handoff that hides the dispatch surface. */\nfunction renderEvent(event: ProgressEvent, deps: ClackProgressAdapterDeps): void {\n\tconst { clack, config } = deps;\n\tswitch (event.kind) {\n\t\tcase \"applySummary\": {\n\t\t\tclack.logMessage(applySummaryLine(event));\n\t\t\treturn;\n\t\t}\n\t\tcase \"deployFailure\": {\n\t\t\trenderDeployError(event.error, clack);\n\t\t\treturn;\n\t\t}\n\t\tcase \"deploySuccess\": {\n\t\t\tclack.logSuccess(`${event.environment}: ${event.resourceCount} resources reconciled`);\n\t\t\treturn;\n\t\t}\n\t\tcase \"resourceOpFailed\": {\n\t\t\tclack.logError(`${event.resourceKind}.${event.key} ${describeApplyError(event.error)}`);\n\t\t\treturn;\n\t\t}\n\t\tcase \"resourceOpNoop\": {\n\t\t\tclack.logMessage(`${event.resourceKind}.${event.key} unchanged`);\n\t\t\treturn;\n\t\t}\n\t\tcase \"resourceOpStarted\": {\n\t\t\treturn;\n\t\t}\n\t\tcase \"resourceOpSucceeded\": {\n\t\t\trenderResourceOpSucceeded(event, clack);\n\t\t\treturn;\n\t\t}\n\t\tcase \"stateWritten\": {\n\t\t\tclack.logMessage(`State written to ${formatStateLabel(config, event.environment)}`);\n\t\t}\n\t}\n}\n","/**\n * Wire-shape pricing fragment produced by {@link derivePriceFields}: the\n * `isForSale` flag and an optional numeric `price`. Mirrors the multipart\n * fields the Open Cloud `developer-products` create and update endpoints\n * accept for setting Robux pricing.\n */\nexport interface PriceFields {\n\t/** Whether the developer product should be purchasable. */\n\treadonly isForSale: boolean;\n\t/** Default price in Robux; absent when the product is off-sale. */\n\treadonly price?: number;\n}\n\n/**\n * Translate a Mantle-style optional price into the Open Cloud wire shape.\n *\n * `desired.price === undefined` (no price declared) becomes\n * `{ isForSale: false }` and the `price` key is omitted entirely. A defined\n * price (including `0`) becomes `{ isForSale: true, price }`. Both\n * `developerProduct` create and update paths share this helper so the\n * \"absent ⇒ off-sale\" semantics live in exactly one place.\n *\n * @param desired - Object carrying the user-declared `price`.\n * @returns The wire-shape `{ isForSale, price? }` fragment.\n *\n * @example\n *\n * ```ts\n * import { derivePriceFields } from \"@bedrock-rbx/core\";\n *\n * expect(derivePriceFields({ price: undefined })).toStrictEqual({ isForSale: false });\n * expect(derivePriceFields({ price: 250 })).toStrictEqual({ isForSale: true, price: 250 });\n * ```\n */\nexport function derivePriceFields(desired: { readonly price: number | undefined }): PriceFields {\n\tif (desired.price === undefined) {\n\t\treturn { isForSale: false };\n\t}\n\n\treturn { isForSale: true, price: desired.price };\n}\n","import type { DeveloperProduct } from \"@bedrock-rbx/ocale/developer-products\";\n\nimport type { DeveloperProductDesiredState } from \"./resources.ts\";\n\n/**\n * Optional follow-up PATCH body the developer-product driver issues after a\n * successful create POST when `storePageEnabled` was declared on desired\n * state. The Roblox v2 create endpoint does not accept `storePageEnabled`,\n * so the driver applies the flag in a PATCH after the POST returns.\n */\ninterface FollowUpPatchBody {\n\t/** The `storePageEnabled` value the driver should apply via PATCH. */\n\treadonly storePageEnabled: boolean;\n}\n\n/**\n * Plan the optional follow-up PATCH body needed after a developer-product\n * create POST. Returns `undefined` when no PATCH is required: either the\n * user did not declare `storePageEnabled`, or the create response already\n * matches the desired value.\n *\n * @param desired - Desired state for the developer product being created.\n * @param createResponse - The `storePageEnabled` value reported by the create POST response.\n * @returns The PATCH body to issue, or `undefined` when no follow-up is needed.\n */\nexport function planFollowUpPatch(\n\tdesired: DeveloperProductDesiredState,\n\tcreateResponse: Pick<DeveloperProduct, \"storePageEnabled\">,\n): FollowUpPatchBody | undefined {\n\tif (desired.storePageEnabled === undefined) {\n\t\treturn undefined;\n\t}\n\n\tif (desired.storePageEnabled === createResponse.storePageEnabled) {\n\t\treturn undefined;\n\t}\n\n\treturn { storePageEnabled: desired.storePageEnabled };\n}\n","import type { OpenCloudError, Result } from \"@bedrock-rbx/ocale\";\nimport type {\n\tDeveloperProduct,\n\tDeveloperProductsClient,\n} from \"@bedrock-rbx/ocale/developer-products\";\n\nimport { derivePriceFields } from \"../core/derive-price-fields.ts\";\nimport { shouldReuploadIcon } from \"../core/icons.ts\";\nimport { planFollowUpPatch } from \"../core/plan-follow-up-patch.ts\";\nimport { withRedactedIcon } from \"../core/redacted-icon.ts\";\nimport type { DeveloperProductDesiredState, ResourceCurrentState } from \"../core/resources.ts\";\nimport type { ResourceDriver } from \"../ports/resource-driver.ts\";\nimport { asRobloxAssetId, type RobloxAssetId } from \"../types/ids.ts\";\n\n/**\n * Dependencies of `createDeveloperProductDriver`. `universeId` is captured\n * at construction time (matching `GamePassDriverDeps`) so each driver\n * instance is bound to a single universe; multi-universe deploys construct\n * one driver per universe. `readFile` exists on the driver (not upstream\n * in shell) because icon hashes flow through `diff` but bytes do not.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { DeveloperProductsClient } from \"@bedrock-rbx/ocale/developer-products\";\n * import { asRobloxAssetId, type DeveloperProductDriverDeps } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return { data: { body: {}, headers: {}, status: 200 }, success: true };\n * },\n * };\n *\n * const deps: DeveloperProductDriverDeps = {\n * client: new DeveloperProductsClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () => new Uint8Array([0x89, 0x50, 0x4e, 0x47]),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * };\n *\n * expect(deps.universeId).toBe(\"1234567890\");\n * ```\n */\nexport interface DeveloperProductDriverDeps {\n\t/** Configured developer-products client from `@bedrock-rbx/ocale/developer-products`. */\n\treadonly client: DeveloperProductsClient;\n\t/** Reads icon bytes for upload; rejections propagate out of `create` and `update`. */\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\t/** Universe that owns every developer product this driver creates. */\n\treadonly universeId: RobloxAssetId;\n}\n\ninterface UpdateInputs {\n\treadonly current: ResourceCurrentState<\"developerProduct\">;\n\treadonly desired: DeveloperProductDesiredState;\n}\n\ninterface FollowUpPatchInputs {\n\treadonly created: DeveloperProduct;\n\treadonly desired: DeveloperProductDesiredState;\n}\n\n/**\n * Wraps {@link DeveloperProductsClient} as a `ResourceDriver<\"developerProduct\">`\n * that maps a desired-state entry to an ocale create or update call and the\n * response back to a `ResourceCurrentState<\"developerProduct\">`. The\n * `update` path consumes the upstream `204 No Content` response and\n * synthesizes the post-update `ResourceCurrentState` from `desired` plus\n * the existing `current.outputs`, carrying `iconImageAssetId` forward when\n * present.\n *\n * Upstream `OpenCloudError` results pass through as `Result` failures.\n *\n * @param deps - Injected ocale client and owning universe.\n * @returns A driver indexable by `\"developerProduct\"` in a `DriverRegistry`.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { DeveloperProductsClient } from \"@bedrock-rbx/ocale/developer-products\";\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * createDeveloperProductDriver,\n * } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return {\n * data: {\n * body: {\n * createdTimestamp: \"2024-01-15T10:30:00.000Z\",\n * description: \"Stocks the player up with 1,000 premium gems.\",\n * iconImageAssetId: null,\n * isForSale: false,\n * isImmutable: false,\n * name: \"Gem Pack\",\n * priceInformation: null,\n * productId: 9_876_543_210,\n * storePageEnabled: false,\n * universeId: 1_234_567_890,\n * updatedTimestamp: \"2024-01-15T10:30:00.000Z\",\n * },\n * headers: {},\n * status: 200,\n * },\n * success: true,\n * };\n * },\n * };\n *\n * const driver = createDeveloperProductDriver({\n * client: new DeveloperProductsClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () => new Uint8Array([0x89, 0x50, 0x4e, 0x47]),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * });\n *\n * return driver\n * .create({\n * description: \"Stocks the player up with 1,000 premium gems.\",\n * isRegionalPricingEnabled: undefined,\n * key: asResourceKey(\"gem-pack\"),\n * kind: \"developerProduct\",\n * name: \"Gem Pack\",\n * price: undefined,\n * storePageEnabled: undefined,\n * })\n * .then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.outputs.productId).toBe(\"9876543210\");\n * }\n * });\n * ```\n */\nexport function createDeveloperProductDriver(\n\tdeps: DeveloperProductDriverDeps,\n): ResourceDriver<\"developerProduct\"> {\n\tconst effective: DeveloperProductDriverDeps = {\n\t\t...deps,\n\t\treadFile: withRedactedIcon(deps.readFile),\n\t};\n\treturn {\n\t\tasync create(desired) {\n\t\t\treturn createOne(effective, desired);\n\t\t},\n\t\tasync update(current, desired) {\n\t\t\treturn updateOne(effective, { current, desired });\n\t\t},\n\t};\n}\n\nfunction toCurrentState(\n\tdesired: DeveloperProductDesiredState,\n\tdata: DeveloperProduct,\n): Result<ResourceCurrentState<\"developerProduct\">, OpenCloudError> {\n\tconst iconImageAssetId =\n\t\tdata.iconImageAssetId === undefined ? undefined : asRobloxAssetId(data.iconImageAssetId);\n\n\treturn {\n\t\tdata: {\n\t\t\t...desired,\n\t\t\toutputs: {\n\t\t\t\tproductId: asRobloxAssetId(data.id),\n\t\t\t\t...(iconImageAssetId === undefined ? {} : { iconImageAssetId }),\n\t\t\t},\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nasync function applyFollowUpPatch(\n\tdeps: DeveloperProductDriverDeps,\n\t{ created, desired }: FollowUpPatchInputs,\n): Promise<Result<ResourceCurrentState<\"developerProduct\">, OpenCloudError>> {\n\tconst followUp = planFollowUpPatch(desired, created);\n\tif (followUp === undefined) {\n\t\treturn toCurrentState(desired, created);\n\t}\n\n\tconst patched = await deps.client.update({\n\t\tproductId: asRobloxAssetId(created.id),\n\t\tuniverseId: deps.universeId,\n\t\t...followUp,\n\t});\n\tif (patched.success) {\n\t\treturn toCurrentState(desired, created);\n\t}\n\n\t// PATCH failed but the POST persisted the resource. Surface success\n\t// carrying the wire-reported storePageEnabled so state captures what\n\t// actually exists; the next deploy's diff sees the desired/current\n\t// mismatch and retries the PATCH (self-heal).\n\treturn toCurrentState({ ...desired, storePageEnabled: created.storePageEnabled }, created);\n}\n\nasync function createOne(\n\tdeps: DeveloperProductDriverDeps,\n\tdesired: DeveloperProductDesiredState,\n): Promise<Result<ResourceCurrentState<\"developerProduct\">, OpenCloudError>> {\n\tconst imageFile =\n\t\tdesired.icon === undefined ? undefined : await deps.readFile(desired.icon[\"en-us\"]);\n\tconst created = await deps.client.create({\n\t\tname: desired.name,\n\t\tdescription: desired.description,\n\t\tuniverseId: deps.universeId,\n\t\t...(imageFile === undefined ? {} : { imageFile }),\n\t\t...derivePriceFields(desired),\n\t\t...(desired.isRegionalPricingEnabled === undefined\n\t\t\t? {}\n\t\t\t: { isRegionalPricingEnabled: desired.isRegionalPricingEnabled }),\n\t});\n\tif (!created.success) {\n\t\treturn created;\n\t}\n\n\treturn applyFollowUpPatch(deps, { created: created.data, desired });\n}\n\nasync function updateOne(\n\tdeps: DeveloperProductDriverDeps,\n\t{ current, desired }: UpdateInputs,\n): Promise<Result<ResourceCurrentState<\"developerProduct\">, OpenCloudError>> {\n\tconst imageFile =\n\t\tdesired.icon !== undefined &&\n\t\tshouldReuploadIcon(current.iconFileHashes, desired.iconFileHashes)\n\t\t\t? await deps.readFile(desired.icon[\"en-us\"])\n\t\t\t: undefined;\n\tconst result = await deps.client.update({\n\t\tname: desired.name,\n\t\tdescription: desired.description,\n\t\tproductId: current.outputs.productId,\n\t\tuniverseId: deps.universeId,\n\t\t...(imageFile === undefined ? {} : { imageFile }),\n\t\t...derivePriceFields(desired),\n\t\t...(desired.isRegionalPricingEnabled === undefined\n\t\t\t? {}\n\t\t\t: { isRegionalPricingEnabled: desired.isRegionalPricingEnabled }),\n\t\t...(desired.storePageEnabled === undefined\n\t\t\t? {}\n\t\t\t: { storePageEnabled: desired.storePageEnabled }),\n\t});\n\tif (!result.success) {\n\t\treturn result;\n\t}\n\n\t// The PATCH endpoint returns 204; the post-update state is the desired\n\t// entry composed with the existing Roblox-assigned outputs.\n\treturn { data: { ...desired, outputs: current.outputs }, success: true };\n}\n","import { ApiError, type OpenCloudError, type Result } from \"@bedrock-rbx/ocale\";\nimport type { GamePass, GamePassesClient } from \"@bedrock-rbx/ocale/game-passes\";\n\nimport { derivePriceFields } from \"../core/derive-price-fields.ts\";\nimport { shouldReuploadIcon } from \"../core/icons.ts\";\nimport { withRedactedIcon } from \"../core/redacted-icon.ts\";\nimport type { GamePassDesiredState, ResourceCurrentState } from \"../core/resources.ts\";\nimport type { ResourceDriver } from \"../ports/resource-driver.ts\";\nimport { asRobloxAssetId, type RobloxAssetId } from \"../types/ids.ts\";\n\n/**\n * `universeId` is captured at construction time rather than on\n * `GamePassDesiredState` so state files round-trip with Mantle's `PassInputs`\n * shape. `readFile` exists on the driver (not upstream in shell) because icon\n * hashes flow through `diff` but bytes do not.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { GamePassesClient } from \"@bedrock-rbx/ocale/game-passes\";\n * import { asRobloxAssetId, type GamePassDriverDeps } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return { data: { body: {}, headers: {}, status: 200 }, success: true };\n * },\n * };\n *\n * const deps: GamePassDriverDeps = {\n * client: new GamePassesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () => new Uint8Array([0x89, 0x50, 0x4e, 0x47]),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * };\n *\n * expect(deps.universeId).toBe(\"1234567890\");\n * ```\n */\nexport interface GamePassDriverDeps {\n\t/** Configured game-passes client from `@bedrock-rbx/ocale/game-passes`. */\n\treadonly client: GamePassesClient;\n\t/** Reads icon bytes for upload; rejections propagate out of `create`. */\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\t/** Universe that owns every game pass this driver creates. */\n\treadonly universeId: RobloxAssetId;\n}\n\n/**\n * Wraps {@link GamePassesClient} as a `ResourceDriver<\"gamePass\">` that maps\n * a desired-state entry to an ocale create call and the response back to a\n * `ResourceCurrentState<\"gamePass\">`.\n *\n * Upstream `OpenCloudError` results pass through as `Result` failures.\n * Filesystem errors from `deps.readFile` do not fit the `OpenCloudError`\n * shape and propagate as promise rejections; shell callers are expected to\n * translate them if a unified error surface is required.\n *\n * @param deps - Injected ocale client, file reader, and owning universe.\n * @returns A driver indexable by `\"gamePass\"` in a `DriverRegistry`.\n * @throws Whatever `deps.readFile` rejects with.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { GamePassesClient } from \"@bedrock-rbx/ocale/game-passes\";\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * asSha256Hex,\n * createGamePassDriver,\n * } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return {\n * data: {\n * body: {\n * createdTimestamp: \"2024-01-15T10:30:00.000Z\",\n * description: \"Grants VIP perks.\",\n * gamePassId: 9_876_543_210,\n * iconAssetId: 1_122_334_455,\n * isForSale: true,\n * name: \"VIP Pass\",\n * updatedTimestamp: \"2024-01-15T10:30:00.000Z\",\n * },\n * headers: {},\n * status: 200,\n * },\n * success: true,\n * };\n * },\n * };\n *\n * const driver = createGamePassDriver({\n * client: new GamePassesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () => new Uint8Array([0x89, 0x50, 0x4e, 0x47]),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * });\n *\n * return driver\n * .create({\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * iconFileHashes: {\n * \"en-us\": asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * ),\n * },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * price: 500,\n * })\n * .then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.outputs.assetId).toBe(\"9876543210\");\n * }\n * });\n * ```\n */\nexport function createGamePassDriver(deps: GamePassDriverDeps): ResourceDriver<\"gamePass\"> {\n\tconst effective: GamePassDriverDeps = {\n\t\t...deps,\n\t\treadFile: withRedactedIcon(deps.readFile),\n\t};\n\treturn {\n\t\tasync create(desired) {\n\t\t\treturn createGamePass(effective, desired);\n\t\t},\n\t\tasync update(current, desired) {\n\t\t\treturn updateGamePass(effective, { current, desired });\n\t\t},\n\t};\n}\n\nfunction toCurrentState(\n\tdesired: GamePassDesiredState,\n\tdata: GamePass,\n): Result<ResourceCurrentState<\"gamePass\">, OpenCloudError> {\n\tconst { id, iconAssetId } = data;\n\tif (iconAssetId === undefined) {\n\t\treturn {\n\t\t\terr: new ApiError(\n\t\t\t\t\"Malformed game pass response: iconAssetId missing after icon upload\",\n\t\t\t\t{ statusCode: 200 },\n\t\t\t),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\t...desired,\n\t\t\toutputs: {\n\t\t\t\tassetId: asRobloxAssetId(id),\n\t\t\t\ticonAssetIds: { \"en-us\": asRobloxAssetId(iconAssetId) },\n\t\t\t},\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nasync function createGamePass(\n\tdeps: GamePassDriverDeps,\n\tdesired: GamePassDesiredState,\n): Promise<Result<ResourceCurrentState<\"gamePass\">, OpenCloudError>> {\n\tconst imageFile = await deps.readFile(desired.icon[\"en-us\"]);\n\tconst result = await deps.client.create({\n\t\tname: desired.name,\n\t\tdescription: desired.description,\n\t\timageFile,\n\t\tuniverseId: deps.universeId,\n\t\t...(desired.price !== undefined ? { price: desired.price } : {}),\n\t});\n\tif (!result.success) {\n\t\treturn result;\n\t}\n\n\treturn toCurrentState(desired, result.data);\n}\n\nasync function resolveUpdatedState(\n\tdeps: GamePassDriverDeps,\n\tcontext: {\n\t\treadonly current: ResourceCurrentState<\"gamePass\">;\n\t\treadonly desired: GamePassDesiredState;\n\t\treadonly hasIconChanged: boolean;\n\t},\n): Promise<Result<ResourceCurrentState<\"gamePass\">, OpenCloudError>> {\n\tconst { current, desired, hasIconChanged } = context;\n\tif (!hasIconChanged) {\n\t\treturn { data: { ...desired, outputs: current.outputs }, success: true };\n\t}\n\n\tconst fetched = await deps.client.get({\n\t\tgamePassId: current.outputs.assetId,\n\t\tuniverseId: deps.universeId,\n\t});\n\tif (!fetched.success) {\n\t\treturn fetched;\n\t}\n\n\treturn toCurrentState(desired, fetched.data);\n}\n\nasync function updateGamePass(\n\tdeps: GamePassDriverDeps,\n\tstates: {\n\t\treadonly current: ResourceCurrentState<\"gamePass\">;\n\t\treadonly desired: GamePassDesiredState;\n\t},\n): Promise<Result<ResourceCurrentState<\"gamePass\">, OpenCloudError>> {\n\tconst { current, desired } = states;\n\tconst hasIconChanged = shouldReuploadIcon(current.iconFileHashes, desired.iconFileHashes);\n\tconst imageFile = hasIconChanged ? await deps.readFile(desired.icon[\"en-us\"]) : undefined;\n\n\tconst result = await deps.client.update({\n\t\tname: desired.name,\n\t\tdescription: desired.description,\n\t\tgamePassId: current.outputs.assetId,\n\t\tuniverseId: deps.universeId,\n\t\t...derivePriceFields(desired),\n\t\t...(imageFile !== undefined ? { imageFile } : {}),\n\t});\n\tif (!result.success) {\n\t\treturn result;\n\t}\n\n\treturn resolveUpdatedState(deps, { current, desired, hasIconChanged });\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { ArkErrors, type } from \"arktype\";\n\nimport type { ResourceCurrentState } from \"./resources.ts\";\nimport type { BedrockState, StateError } from \"./state.ts\";\n\n// Resource-level validation is intentionally shallow: every resource must be\n// an object with a known `kind` discriminator, but per-kind field validation\n// (game-pass vs place vs universe) is deferred. Bedrock is both the writer\n// and the reader of state files, so tampering is out of scope for v0.1;\n// a follow-up issue widens this to full per-kind schema + brand narrowing.\nconst resourceShape = type({\n\t\"key\": \"string\",\n\t\"[string]\": \"unknown\",\n\t\"kind\": \"'developerProduct' | 'gamePass' | 'place' | 'universe'\",\n\t\"outputs\": \"object\",\n});\n\nconst envelopeSchema = type({\n\t$bedrock: { version: \"1\" },\n\tenvironment: \"string\",\n\tresources: resourceShape.array(),\n});\n\n/**\n * Serialize a {@link BedrockState} to the on-disk JSON representation used by\n * state-port adapters.\n *\n * The on-disk shape wraps the in-memory state with a\n * `$bedrock: { version: N }` envelope so that a future breaking change to the\n * schema can be detected and rejected at parse time rather than silently\n * accepted. The top-level `version` field is not duplicated on disk.\n *\n * @example\n *\n * ```ts\n * import { serializeStateFile, type BedrockState } from \"@bedrock-rbx/core\";\n *\n * const state: BedrockState = {\n * environment: \"production\",\n * resources: [],\n * version: 1,\n * };\n *\n * const wire = serializeStateFile(state);\n * expect(JSON.parse(wire)).toStrictEqual({\n * $bedrock: { version: 1 },\n * environment: \"production\",\n * resources: [],\n * });\n * ```\n *\n * @param state - The in-memory state snapshot to serialize.\n * @returns A pretty-printed JSON string ready to hand to a state adapter's write method.\n */\nexport function serializeStateFile(state: BedrockState): string {\n\tconst envelope = {\n\t\t$bedrock: { version: state.version },\n\t\tenvironment: state.environment,\n\t\tresources: state.resources,\n\t};\n\treturn JSON.stringify(envelope, undefined, 2);\n}\n\n/**\n * Parse a raw on-disk state file into a {@link BedrockState}.\n *\n * A backend that reports \"no state file for this environment yet\" must pass\n * `undefined`: that distinguishes a legitimate first deploy from a file that\n * exists but cannot be trusted.\n *\n * @example\n *\n * ```ts\n * import { parseStateFile } from \"@bedrock-rbx/core\";\n *\n * const freshStart = parseStateFile(undefined, \"gist:abc123/state.production.json\");\n * expect(freshStart.success).toBeTrue();\n * if (freshStart.success) {\n * expect(freshStart.data).toBeUndefined();\n * }\n * ```\n *\n * @param raw - Raw file contents as a string, or `undefined` when the\n * backend reports no file exists yet.\n * @param file - Adapter-specific identifier included in any `StateError`\n * surfaced during parsing.\n * @returns `Ok(undefined)` for a missing file, `Ok(state)` for a parseable\n * file, or `Err(StateError)` for anything that cannot be trusted.\n */\nexport function parseStateFile(\n\traw: string | undefined,\n\tfile: string,\n): Result<BedrockState | undefined, StateError> {\n\tif (raw === undefined) {\n\t\treturn { data: undefined, success: true };\n\t}\n\n\tconst parsed = parseJson(raw, file);\n\tif (!parsed.success) {\n\t\treturn parsed;\n\t}\n\n\tconst validated = envelopeSchema(parsed.data);\n\tif (validated instanceof ArkErrors) {\n\t\treturn errState(file, `invalid state file: ${validated.summary}`);\n\t}\n\n\tconst resources = validated.resources as unknown as ReadonlyArray<ResourceCurrentState>;\n\treturn {\n\t\tdata: { environment: validated.environment, resources, version: 1 },\n\t\tsuccess: true,\n\t};\n}\n\nfunction parseJson(raw: string, file: string): Result<JSONValue, StateError> {\n\ttry {\n\t\treturn { data: JSON.parse(raw), success: true };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn {\n\t\t\terr: { file, kind: \"stateError\", reason: `malformed JSON: ${message}` },\n\t\t\tsuccess: false,\n\t\t};\n\t}\n}\n\nfunction errState(file: string, reason: string): Result<BedrockState | undefined, StateError> {\n\treturn {\n\t\terr: { file, kind: \"stateError\", reason },\n\t\tsuccess: false,\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { validateEnvironmentName } from \"../core/environment.ts\";\nimport { parseStateFile, serializeStateFile } from \"../core/state-file.ts\";\nimport type { BedrockState, StateError } from \"../core/state.ts\";\nimport type { StatePort } from \"../ports/state-port.ts\";\n\nconst GITHUB_API_BASE = \"https://api.github.com\";\nconst GITHUB_API_VERSION = \"2026-03-10\";\nconst USER_AGENT = \"bedrock\";\nconst MAX_INLINE_BYTES = 10_000_000;\nconst MAX_RETRIES = 6;\nconst BASE_BACKOFF_MS = 500;\nconst MAX_BACKOFF_MS = 16_000;\nconst RETRYABLE_STATUSES: ReadonlySet<number> = new Set([409, 502, 503, 504]);\nconst MAX_VISIBILITY_ATTEMPTS = 5;\nconst VISIBILITY_BASE_DELAY_MS = 250;\n\n/**\n * Minimal `fetch`-compatible signature the adapter needs, narrower than\n * `typeof globalThis.fetch` so test fakes do not have to stub runtime\n * extensions such as `fetch.preconnect`.\n */\nexport type GistFetch = (\n\tinput: globalThis.Request | string | URL,\n\tinit?: RequestInit,\n) => Promise<Response>;\n\n/**\n * Configuration for {@link createGistStateAdapter}.\n */\nexport interface GistStateAdapterDeps {\n\t/** Injection seam for tests; defaults to `globalThis.fetch`. */\n\treadonly fetch?: GistFetch | undefined;\n\t/** ID of an existing GitHub Gist that holds this project's state files. */\n\treadonly gistId: string;\n\t/**\n\t * Injection seam for retry jitter; defaults to `Math.random`. Tests pass a\n\t * deterministic source so jittered sleep durations stay stable across runs.\n\t * Jitter prevents concurrent callers (parallel CI jobs writing to the same\n\t * gist) from retrying in lockstep and re-colliding on each backoff.\n\t */\n\treadonly random?: (() => number) | undefined;\n\t/**\n\t * Injection seam for retry backoff timing; defaults to a `setTimeout`-based\n\t * promise. Tests pass a fake to keep retry assertions deterministic.\n\t */\n\treadonly sleep?: ((ms: number) => Promise<void>) | undefined;\n\t/** GitHub token (fine-grained PAT or classic PAT) with gist read/write scope. */\n\treadonly token: string;\n}\n\ninterface AdapterContext {\n\treadonly fetchFn: GistFetch;\n\treadonly gistId: string;\n\treadonly random: () => number;\n\treadonly sleep: (ms: number) => Promise<void>;\n\treadonly token: string;\n}\n\ninterface GistFile {\n\treadonly content: string | undefined;\n\treadonly isTruncated: boolean;\n\treadonly rawUrl: string | undefined;\n\treadonly size: number;\n}\n\ninterface HttpFailure {\n\treadonly file: string;\n\treadonly gistId: string;\n\treadonly response: Response;\n}\n\ninterface RetryDeps {\n\treadonly random: () => number;\n\treadonly sleep: (ms: number) => Promise<void>;\n}\n\ninterface ReadContentParameters {\n\treadonly entry: GistFile;\n\treadonly fetchFn: GistFetch;\n\treadonly file: string;\n\treadonly retry: RetryDeps;\n}\n\n/**\n * Build a `StatePort` that persists Bedrock state in a GitHub Gist.\n *\n * One gist holds one file per environment, named `state.<env>.json`. The\n * adapter authenticates with a user-supplied token and speaks the GitHub\n * REST API directly; no SDK dependency.\n *\n * @example\n *\n * ```ts\n * import { createGistStateAdapter } from \"@bedrock-rbx/core\";\n *\n * const port = createGistStateAdapter({\n * fetch: async () =>\n * new Response(JSON.stringify({ files: {} }), { status: 200 }),\n * gistId: \"abc123def456\",\n * token: \"ghp_example\",\n * });\n *\n * return port.read(\"production\").then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data).toBeUndefined();\n * }\n * });\n * ```\n *\n * @param deps - Gist ID, GitHub token, and optional fetch override.\n * @returns A `StatePort` ready to be passed to `deploy()`.\n */\nexport function createGistStateAdapter(deps: GistStateAdapterDeps): StatePort {\n\tconst ctx: AdapterContext = {\n\t\tfetchFn: deps.fetch ?? globalThis.fetch.bind(globalThis),\n\t\tgistId: deps.gistId,\n\t\trandom: deps.random ?? Math.random,\n\t\tsleep: deps.sleep ?? defaultSleep,\n\t\ttoken: deps.token,\n\t};\n\n\treturn {\n\t\tasync read(environment) {\n\t\t\tconst safe = validateEnvironmentName(environment);\n\t\t\tif (!safe.success) {\n\t\t\t\treturn safe;\n\t\t\t}\n\n\t\t\treturn readPath(ctx, safe.data);\n\t\t},\n\t\tasync write(state) {\n\t\t\tconst safe = validateEnvironmentName(state.environment);\n\t\t\tif (!safe.success) {\n\t\t\t\treturn safe;\n\t\t\t}\n\n\t\t\treturn writePath(ctx, state);\n\t\t},\n\t};\n}\n\nasync function defaultSleep(ms: number): Promise<void> {\n\tawait new Promise<void>((resolve) => {\n\t\tsetTimeout(resolve, ms);\n\t});\n}\n\nfunction fileLabel(gistId: string, environment: string): string {\n\treturn `gist:${gistId}/state.${environment}.json`;\n}\n\nfunction fileName(environment: string): string {\n\treturn `state.${environment}.json`;\n}\n\nfunction toGistFile(entry: unknown): GistFile | undefined {\n\tif (typeof entry !== \"object\" || entry === null) {\n\t\treturn undefined;\n\t}\n\n\tconst record = entry as Record<string, unknown>;\n\tconst content = typeof record[\"content\"] === \"string\" ? record[\"content\"] : undefined;\n\tconst rawUrl = typeof record[\"raw_url\"] === \"string\" ? record[\"raw_url\"] : undefined;\n\tconst size = typeof record[\"size\"] === \"number\" ? record[\"size\"] : 0;\n\tconst isTruncated = record[\"truncated\"] === true;\n\treturn { content, isTruncated, rawUrl, size };\n}\n\nfunction isRateLimited(headers: Headers): boolean {\n\treturn headers.get(\"retry-after\") !== null || headers.get(\"x-ratelimit-remaining\") === \"0\";\n}\n\nfunction rateLimitReason(status: number, headers: Headers): string {\n\tconst retryAfter = headers.get(\"retry-after\");\n\tif (retryAfter !== null) {\n\t\treturn `rate limited (${status}): retry after ${retryAfter}s`;\n\t}\n\n\treturn `rate limited (${status})`;\n}\n\nfunction mapHttpError({ file, gistId, response }: HttpFailure): StateError {\n\tconst { headers, status } = response;\n\tif (status === 404) {\n\t\treturn { file, kind: \"stateError\", reason: `gist ${gistId} not found: check gistId` };\n\t}\n\n\tif (status === 403 && isRateLimited(headers)) {\n\t\treturn { file, kind: \"stateError\", reason: rateLimitReason(status, headers) };\n\t}\n\n\tif (status === 401 || status === 403) {\n\t\treturn { file, kind: \"stateError\", reason: `auth failed (${status}): check token scopes` };\n\t}\n\n\treturn { file, kind: \"stateError\", reason: `github returned ${status}` };\n}\n\nfunction networkError(error: unknown, file: string): StateError {\n\tconst message = error instanceof Error ? error.message : String(error);\n\treturn { file, kind: \"stateError\", reason: `network error: ${message}` };\n}\n\nfunction buildHeaders(token: string): Headers {\n\tconst headers = new Headers();\n\theaders.set(\"Accept\", \"application/vnd.github+json\");\n\theaders.set(\"Authorization\", `Bearer ${token}`);\n\theaders.set(\"User-Agent\", USER_AGENT);\n\theaders.set(\"X-GitHub-Api-Version\", GITHUB_API_VERSION);\n\treturn headers;\n}\n\nasync function sendGet(ctx: AdapterContext): Promise<Response> {\n\treturn ctx.fetchFn(`${GITHUB_API_BASE}/gists/${ctx.gistId}`, {\n\t\theaders: buildHeaders(ctx.token),\n\t\tmethod: \"GET\",\n\t});\n}\n\nfunction isRetryableStatus(status: number): boolean {\n\treturn RETRYABLE_STATUSES.has(status);\n}\n\nfunction backoffMs(attempt: number, random: () => number): number {\n\tconst cap = Math.min(MAX_BACKOFF_MS, BASE_BACKOFF_MS * 2 ** attempt);\n\tconst half = cap / 2;\n\treturn half + random() * half;\n}\n\nasync function withRetry(retry: RetryDeps, operation: () => Promise<Response>): Promise<Response> {\n\tlet response = await operation();\n\tfor (let attempt = 0; attempt < MAX_RETRIES; attempt += 1) {\n\t\tif (response.ok || !isRetryableStatus(response.status)) {\n\t\t\treturn response;\n\t\t}\n\n\t\tawait retry.sleep(backoffMs(attempt, retry.random));\n\t\tresponse = await operation();\n\t}\n\n\treturn response;\n}\n\nasync function fetchGistBody(\n\tctx: AdapterContext,\n\tfile: string,\n): Promise<Result<Record<string, unknown>, StateError>> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await withRetry(ctx, async () => sendGet(ctx));\n\t} catch (err) {\n\t\treturn { err: networkError(err, file), success: false };\n\t}\n\n\tif (!response.ok) {\n\t\treturn {\n\t\t\terr: mapHttpError({ file, gistId: ctx.gistId, response }),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\tconst body = (await response.json()) as Record<string, unknown>;\n\treturn { data: body, success: true };\n}\n\nfunction stateErr<T>(file: string, reason: string): Result<T, StateError> {\n\treturn { err: { file, kind: \"stateError\", reason }, success: false };\n}\n\nasync function readGistContent({\n\tentry,\n\tfetchFn,\n\tfile,\n\tretry,\n}: ReadContentParameters): Promise<Result<BedrockState | undefined, StateError>> {\n\tif (entry.size > MAX_INLINE_BYTES) {\n\t\treturn stateErr(file, `state file too large: ${entry.size} bytes`);\n\t}\n\n\tif (entry.isTruncated) {\n\t\tif (entry.rawUrl === undefined) {\n\t\t\treturn stateErr(file, \"truncated gist file missing raw_url\");\n\t\t}\n\n\t\tconst { rawUrl } = entry;\n\t\tlet rawResponse: Response;\n\t\ttry {\n\t\t\trawResponse = await withRetry(retry, async () => fetchFn(rawUrl));\n\t\t} catch (err) {\n\t\t\treturn { err: networkError(err, file), success: false };\n\t\t}\n\n\t\tif (!rawResponse.ok) {\n\t\t\treturn stateErr(file, `raw_url fetch returned ${rawResponse.status}`);\n\t\t}\n\n\t\tconst raw = await rawResponse.text();\n\t\treturn parseStateFile(raw, file);\n\t}\n\n\treturn parseStateFile(entry.content, file);\n}\n\nasync function readPath(\n\tctx: AdapterContext,\n\tenvironment: string,\n): Promise<Result<BedrockState | undefined, StateError>> {\n\tconst file = fileLabel(ctx.gistId, environment);\n\tconst gist = await fetchGistBody(ctx, file);\n\tif (!gist.success) {\n\t\treturn gist;\n\t}\n\n\tconst files = gist.data[\"files\"] as Record<string, unknown> | undefined;\n\tconst entry = toGistFile(files?.[fileName(environment)]);\n\tif (entry === undefined) {\n\t\treturn { data: undefined, success: true };\n\t}\n\n\treturn readGistContent({ entry, fetchFn: ctx.fetchFn, file, retry: ctx });\n}\n\nasync function sendPatch(ctx: AdapterContext, body: string): Promise<Response> {\n\tconst headers = buildHeaders(ctx.token);\n\theaders.set(\"Content-Type\", \"application/json\");\n\treturn ctx.fetchFn(`${GITHUB_API_BASE}/gists/${ctx.gistId}`, {\n\t\tbody,\n\t\theaders,\n\t\tmethod: \"PATCH\",\n\t});\n}\n\nasync function isFileVisible(ctx: AdapterContext, target: string): Promise<boolean> {\n\ttry {\n\t\tconst response = await sendGet(ctx);\n\t\tconst body = JSON.parse(await response.text());\n\t\tconst files = Reflect.get(body, \"files\");\n\t\treturn typeof files === \"object\" && files !== null && target in files;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Polls the gist until the just-written environment file is visible on a\n * GET, with bounded retries. GitHub's gist API does not guarantee\n * read-your-write across replicas: a GET issued immediately after a\n * successful PATCH can return a body that omits the new file. The poll\n * pre-warms the cache the consumer's next read will hit, so a successful\n * write honours read-after-write at the port boundary.\n *\n * Best-effort: resolves after exhausting the visibility budget regardless\n * of whether the file became visible. The PATCH already committed; the\n * poll only narrows the window in which subsequent reads can lag.\n *\n * @param ctx - Adapter context carrying the injected fetch and sleep seams.\n * @param environment - Environment name whose file is being verified.\n */\nasync function waitForFileVisibility(ctx: AdapterContext, environment: string): Promise<void> {\n\tconst target = fileName(environment);\n\n\tfor (let attempt = 0; attempt < MAX_VISIBILITY_ATTEMPTS; attempt += 1) {\n\t\tif (await isFileVisible(ctx, target)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (attempt < MAX_VISIBILITY_ATTEMPTS - 1) {\n\t\t\tawait ctx.sleep(VISIBILITY_BASE_DELAY_MS * 2 ** attempt);\n\t\t}\n\t}\n}\n\nasync function writePath(\n\tctx: AdapterContext,\n\tstate: BedrockState,\n): Promise<Result<void, StateError>> {\n\tconst file = fileLabel(ctx.gistId, state.environment);\n\tconst body = JSON.stringify({\n\t\tfiles: { [fileName(state.environment)]: { content: serializeStateFile(state) } },\n\t});\n\n\tlet response: Response;\n\ttry {\n\t\tresponse = await withRetry(ctx, async () => sendPatch(ctx, body));\n\t} catch (err) {\n\t\treturn { err: networkError(err, file), success: false };\n\t}\n\n\tif (response.ok) {\n\t\ttry {\n\t\t\tawait waitForFileVisibility(ctx, state.environment);\n\t\t} catch {\n\t\t\t/* visibility poll errors are non-fatal; the PATCH already committed. */\n\t\t}\n\n\t\treturn { data: undefined, success: true };\n\t}\n\n\tif (response.status === 422) {\n\t\treturn stateErr(file, \"invalid PATCH body sent to github\");\n\t}\n\n\treturn {\n\t\terr: mapHttpError({ file, gistId: ctx.gistId, response }),\n\t\tsuccess: false,\n\t};\n}\n","import type { ProgressPort } from \"../ports/progress-port.ts\";\n\n/**\n * Build a {@link ProgressPort} that silently drops every event. Useful for\n * tests and programmatic callers who want to invoke deploy logic without\n * any rendering.\n *\n * @example\n *\n * ```ts\n * import { createNoOpProgressAdapter } from \"@bedrock-rbx/core\";\n *\n * const port = createNoOpProgressAdapter();\n *\n * expect(() =>\n * port.emit({ environment: \"production\", kind: \"deploySuccess\", resourceCount: 3 }),\n * ).not.toThrow();\n * ```\n *\n * @returns A `ProgressPort` whose `emit` method is a no-op.\n */\nexport function createNoOpProgressAdapter(): ProgressPort {\n\treturn {\n\t\temit() {\n\t\t\t/* no-op */\n\t\t},\n\t};\n}\n","import type { SocialLink } from \"@bedrock-rbx/ocale/universes\";\n\nimport {\n\tasResourceKey,\n\ttype ResourceKey,\n\ttype RobloxAssetId,\n\ttype Sha256Hex,\n} from \"../types/ids.ts\";\n\n/**\n * Desired state for a game pass, as declared in user config.\n *\n * Each field is `readonly` because desired state is treated as an immutable\n * snapshot once normalized by `buildDesired`; downstream consumers (`diff`,\n * drivers) must not mutate it.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, asSha256Hex, type GamePassDesiredState } from \"@bedrock-rbx/core\";\n *\n * const pass: GamePassDesiredState = {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * iconFileHashes: {\n * \"en-us\": asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * ),\n * },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * price: undefined,\n * };\n *\n * expect(pass.kind).toBe(\"gamePass\");\n * expect(pass.price).toBeUndefined();\n * ```\n */\nexport interface GamePassDesiredState {\n\t/** User-supplied key; stable across deploys; used to correlate desired with current. */\n\treadonly key: ResourceKey;\n\t/** User-facing game-pass name as shown on the Roblox storefront. */\n\treadonly name: string;\n\t/** User-facing description shown on the game-pass detail page. */\n\treadonly description: string;\n\t/**\n\t * Locale-keyed icon paths declared on the authored config. The Roblox\n\t * game-pass API is monolingual, so only the `\"en-us\"` icon is ever\n\t * uploaded; the map shape mirrors `UniverseDesiredState.icon` for\n\t * cross-kind parity.\n\t */\n\treadonly icon: Record<\"en-us\", string>;\n\t/**\n\t * SHA-256 digests of the local icon files keyed by the same locales as\n\t * the icon map. The diff compares this map against the prior current\n\t * state so the driver re-uploads only when a file's bytes change.\n\t */\n\treadonly iconFileHashes: Record<\"en-us\", Sha256Hex>;\n\t/** Discriminator tag for the `ResourceDesiredState` union. */\n\treadonly kind: \"gamePass\";\n\t/**\n\t * Robux price. `undefined` means off-sale (mirrors Mantle's `Option<u32>`;\n\t * the state parser normalizes JSON `null` to `undefined` at the wire\n\t * boundary per the project type convention).\n\t */\n\treadonly price: number | undefined;\n}\n\n/**\n * Desired state for a place, the `.rbxl` or `.rbxlx` file a universe serves\n * as one of its levels.\n *\n * `placeId` sits on desired state (rather than on outputs like\n * {@link GamePassOutputs.assetId}) because Roblox Open Cloud cannot mint\n * places; the user supplies the existing place ID per entry. `filePath` and\n * `fileHash` describe the local file the driver publishes; `buildDesired`\n * computes `fileHash` from the file bytes so `diff` can detect drift without\n * re-uploading unchanged content. `displayName`, `description`, and\n * `serverSize` are optional metadata fields routed through\n * `PlacesClient.update`; `undefined` leaves the server value untouched.\n *\n * @example\n *\n * ```ts\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * asSha256Hex,\n * type PlaceDesiredState,\n * } from \"@bedrock-rbx/core\";\n *\n * const place: PlaceDesiredState = {\n * description: undefined,\n * displayName: \"Start Place\",\n * fileHash: asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * ),\n * filePath: \"places/start.rbxl\",\n * key: asResourceKey(\"start-place\"),\n * kind: \"place\",\n * placeId: asRobloxAssetId(\"4711\"),\n * serverSize: 50,\n * };\n *\n * expect(place.kind).toBe(\"place\");\n * expect(place.displayName).toBe(\"Start Place\");\n * expect(place.description).toBeUndefined();\n * expect(place.serverSize).toBe(50);\n * ```\n */\nexport interface PlaceDesiredState {\n\t/** User-supplied key; stable across deploys; used to correlate desired with current. */\n\treadonly key: ResourceKey;\n\t/** User-facing place description; `undefined` leaves the server value untouched. */\n\treadonly description: string | undefined;\n\t/** User-facing place name; `undefined` leaves the server value untouched. */\n\treadonly displayName: string | undefined;\n\t/** SHA-256 hex digest of the place file, computed by `buildDesired` in shell. */\n\treadonly fileHash: Sha256Hex;\n\t/** Path to the `.rbxl` or `.rbxlx` file on disk, relative to the config file. */\n\treadonly filePath: string;\n\t/** Discriminator tag for the `ResourceDesiredState` union. */\n\treadonly kind: \"place\";\n\t/** Existing Roblox place ID; Open Cloud cannot create places, so this is an input, not an output. */\n\treadonly placeId: RobloxAssetId;\n\t/** Maximum players per server; positive integer. `undefined` leaves the server value untouched. */\n\treadonly serverSize: number | undefined;\n}\n\n/**\n * Ordered list of optional metadata fields the driver routes through\n * `PlacesClient.update`. Iterated by `placeKind.fieldsEqual` and the place\n * driver's parameter translator so drift detection and the constructed\n * `updateMask` cannot drift apart.\n */\nexport const PLACE_MANAGED_METADATA_FIELDS = [\n\t\"displayName\",\n\t\"description\",\n\t\"serverSize\",\n] as const satisfies ReadonlyArray<keyof PlaceDesiredState>;\n\n/**\n * Roblox-returned value produced by publishing a place version. The publish\n * endpoint does not return an asset ID (the `placeId` is supplied by the\n * caller); `versionNumber` is the only Roblox-assigned field the response\n * carries.\n */\nexport interface PlaceOutputs {\n\t/** Auto-incrementing version number assigned by Roblox on every publish. */\n\treadonly versionNumber: number;\n}\n\n/**\n * Desired state for the singleton universe a config manages.\n *\n * The universe is adopted rather than provisioned: the user supplies an\n * existing `universeId` (Open Cloud cannot mint universes) and bedrock\n * reconciles the declared managed fields against it.\n *\n * Most managed fields use `T | undefined` to mean \"unmanaged\": the diff\n * treats `undefined` as absent and the driver omits the field from the\n * `updateMask`. The clearable fields (`privateServerPriceRobux` and each\n * social link) are additionally key-presence aware: a present key with\n * `undefined` tells the driver to clear the server value (ocale emits\n * JSON `null`), while an absent key leaves the server value untouched.\n *\n * @example\n *\n * ```ts\n * import {\n * asRobloxAssetId,\n * UNIVERSE_SINGLETON_KEY,\n * type UniverseDesiredState,\n * } from \"@bedrock-rbx/core\";\n *\n * const universe: UniverseDesiredState = {\n * consoleEnabled: undefined,\n * desktopEnabled: true,\n * discordSocialLink: { title: \"Join our Discord\", uri: \"https://discord.gg/example\" },\n * displayName: \"Fun Universe\",\n * key: UNIVERSE_SINGLETON_KEY,\n * kind: \"universe\",\n * mobileEnabled: false,\n * privateServerPriceRobux: undefined,\n * tabletEnabled: undefined,\n * twitterSocialLink: undefined,\n * universeId: asRobloxAssetId(\"1234567890\"),\n * voiceChatEnabled: true,\n * vrEnabled: undefined,\n * };\n *\n * expect(universe.kind).toBe(\"universe\");\n * expect(\"privateServerPriceRobux\" in universe).toBeTrue();\n * expect(universe.discordSocialLink?.title).toBe(\"Join our Discord\");\n * expect(universe.twitterSocialLink).toBeUndefined();\n * ```\n */\nexport interface UniverseDesiredState {\n\t/** Fixed singleton key (`\"main\"`); bedrock synthesizes it in `flattenConfig`. */\n\treadonly key: ResourceKey;\n\t/** Whether console players can join; `undefined` leaves the server value untouched. */\n\treadonly consoleEnabled: boolean | undefined;\n\t/** Whether desktop players can join; `undefined` leaves the server value untouched. */\n\treadonly desktopEnabled: boolean | undefined;\n\t/** Discord social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly discordSocialLink?: SocialLink | undefined;\n\t/**\n\t * Display name for the universe. `undefined` leaves the server\n\t * value untouched. The driver routes declared updates through\n\t * `PlacesClient.update` because the universe PATCH endpoint treats\n\t * `displayName` as read-only.\n\t */\n\treadonly displayName: string | undefined;\n\t/** Facebook social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly facebookSocialLink?: SocialLink | undefined;\n\t/** Guilded social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly guildedSocialLink?: SocialLink | undefined;\n\t/** Discriminator tag for the `ResourceDesiredState` union. */\n\treadonly kind: \"universe\";\n\t/** Whether mobile players can join; `undefined` leaves the server value untouched. */\n\treadonly mobileEnabled: boolean | undefined;\n\t/**\n\t * Private-server price in Robux. A present key with `undefined`\n\t * clears the server value on apply; an absent key leaves the server\n\t * value untouched.\n\t */\n\treadonly privateServerPriceRobux?: number | undefined;\n\t/** Roblox Group social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly robloxGroupSocialLink?: SocialLink | undefined;\n\t/** Whether tablet players can join; `undefined` leaves the server value untouched. */\n\treadonly tabletEnabled: boolean | undefined;\n\t/** Twitch social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly twitchSocialLink?: SocialLink | undefined;\n\t/** Twitter social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly twitterSocialLink?: SocialLink | undefined;\n\t/** User-supplied Roblox universe ID; the universe must already exist. */\n\treadonly universeId: RobloxAssetId;\n\t/** Whether voice chat is enabled; `undefined` leaves the server value untouched. */\n\treadonly voiceChatEnabled: boolean | undefined;\n\t/** Whether VR players can join; `undefined` leaves the server value untouched. */\n\treadonly vrEnabled: boolean | undefined;\n\t/** YouTube social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly youtubeSocialLink?: SocialLink | undefined;\n}\n\n/**\n * Ordered list of optional boolean managed fields on {@link UniverseDesiredState}.\n *\n * The driver translator and the diff's per-field equality guard both iterate\n * this list so they cannot drift apart. Order drives `updateMask` sequence in\n * generated requests.\n */\nexport const UNIVERSE_MANAGED_FLAGS = [\n\t\"desktopEnabled\",\n\t\"mobileEnabled\",\n\t\"tabletEnabled\",\n\t\"consoleEnabled\",\n\t\"vrEnabled\",\n\t\"voiceChatEnabled\",\n] as const satisfies ReadonlyArray<keyof UniverseDesiredState>;\n\n/** Key of an optional boolean managed field on {@link UniverseDesiredState}. */\nexport type UniverseManagedFlag = (typeof UNIVERSE_MANAGED_FLAGS)[number];\n\n/**\n * Tuple of every social link field name on {@link UniverseDesiredState}.\n * Iterated by flatten, driver, and diff to handle the tri-state clearable\n * semantics uniformly across all seven fields.\n */\nexport const SOCIAL_LINK_FIELDS = [\n\t\"discordSocialLink\",\n\t\"facebookSocialLink\",\n\t\"guildedSocialLink\",\n\t\"robloxGroupSocialLink\",\n\t\"twitchSocialLink\",\n\t\"twitterSocialLink\",\n\t\"youtubeSocialLink\",\n] as const satisfies ReadonlyArray<keyof UniverseDesiredState>;\n\n/** Union of the seven social link field names on {@link UniverseDesiredState}. */\nexport type SocialLinkField = (typeof SOCIAL_LINK_FIELDS)[number];\n\n/**\n * Desired state for a developer product, the consumable a player can buy via\n * `MarketplaceService:PromptProductPurchase`.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, type DeveloperProductDesiredState } from \"@bedrock-rbx/core\";\n *\n * const product: DeveloperProductDesiredState = {\n * description: \"Stocks the player up with 1,000 premium gems.\",\n * isRegionalPricingEnabled: true,\n * key: asResourceKey(\"gem-pack\"),\n * kind: \"developerProduct\",\n * name: \"Gem Pack\",\n * price: 100,\n * storePageEnabled: true,\n * };\n *\n * expect(product.kind).toBe(\"developerProduct\");\n * expect(product.price).toBe(100);\n * ```\n */\nexport interface DeveloperProductDesiredState {\n\t/** User-supplied key; stable across deploys; used to correlate desired with current. */\n\treadonly key: ResourceKey;\n\t/** User-facing developer product name as shown on the storefront. */\n\treadonly name: string;\n\t/** User-facing description shown on the developer product detail page. */\n\treadonly description: string;\n\t/**\n\t * Locale-keyed icon paths declared on the authored config. Absent when\n\t * the user did not declare an icon block. The Roblox developer-product\n\t * API is monolingual, so only the `\"en-us\"` icon is ever uploaded; the\n\t * map shape mirrors `GamePassDesiredState.icon` for cross-kind parity.\n\t */\n\treadonly icon?: Record<\"en-us\", string>;\n\t/**\n\t * SHA-256 digests of the local icon files keyed by the same locales as\n\t * the icon map. The diff compares this map against the prior current\n\t * state so the driver re-uploads only when a file's bytes change. Absent\n\t * when `icon` is absent.\n\t */\n\treadonly iconFileHashes?: Record<\"en-us\", Sha256Hex>;\n\t/**\n\t * Whether Roblox-managed regional pricing applies to the product.\n\t * Tri-state: `undefined` means the flag is unmanaged (the diff ignores\n\t * it); a defined value is propagated to Roblox on every deploy.\n\t */\n\treadonly isRegionalPricingEnabled: boolean | undefined;\n\t/** Discriminator tag for the `ResourceDesiredState` union. */\n\treadonly kind: \"developerProduct\";\n\t/**\n\t * Robux price. `undefined` means off-sale; removing the field from config\n\t * takes the product off-sale on the next deploy, re-adding puts it back\n\t * on sale.\n\t */\n\treadonly price: number | undefined;\n\t/**\n\t * Whether the product appears on the universe's external store page.\n\t * Tri-state: `undefined` means the flag is unmanaged. A defined value is\n\t * applied via a follow-up PATCH after the create POST because the v2\n\t * create endpoint does not accept this field.\n\t */\n\treadonly storePageEnabled: boolean | undefined;\n}\n\n/**\n * Discriminated union of every desired-state shape Bedrock manages.\n *\n * Extend by adding new members to this union; the mapped\n * `ResourceOutputsByKind` interface then forces a matching outputs entry for\n * the new kind at compile time.\n */\nexport type ResourceDesiredState =\n\t| DeveloperProductDesiredState\n\t| GamePassDesiredState\n\t| PlaceDesiredState\n\t| UniverseDesiredState;\n\n/**\n * Roblox-returned identifiers produced by creating or updating a game pass.\n *\n * Distinct from `GamePassDesiredState.key`: the desired-state key is a\n * user-supplied handle, while these IDs are assigned by Roblox and\n * discovered only after the first successful API call.\n */\nexport interface GamePassOutputs {\n\t/** Primary Roblox asset ID for the game pass itself. */\n\treadonly assetId: RobloxAssetId;\n\t/**\n\t * Locale-keyed Roblox-assigned image IDs for the game-pass icons. The\n\t * Roblox game-pass API is monolingual, so the `\"en-us\"` entry is the\n\t * only one ever populated; the map shape leaves room for translated\n\t * icons should the API ever expose them.\n\t */\n\treadonly iconAssetIds: Record<\"en-us\", RobloxAssetId>;\n}\n\n/**\n * Roblox-returned identifiers produced by creating or updating a developer\n * product. `productId` is what `MarketplaceService:PromptProductPurchase`\n * accepts and is stable across re-deploys. `iconImageAssetId` is optional\n * because the wire returns it as nullable when no icon is uploaded; slice 1\n * never populates it because icon support lands in a later slice.\n */\nexport interface DeveloperProductOutputs {\n\t/** Roblox asset ID of the uploaded icon image; `undefined` when no icon is uploaded. */\n\treadonly iconImageAssetId?: RobloxAssetId | undefined;\n\t/** Roblox-assigned developer product ID; stable across re-deploys. */\n\treadonly productId: RobloxAssetId;\n}\n\n/**\n * Roblox-returned value produced by reconciling a universe. The root place\n * ID is server-authoritative: bedrock cannot set it directly, but records it\n * so a future places slice can cross-validate the declared start place.\n */\nexport interface UniverseOutputs {\n\t/** Server-assigned root place ID for the universe. */\n\treadonly rootPlaceId: RobloxAssetId;\n}\n\n/**\n * String union of every discriminator tag in `ResourceDesiredState`.\n *\n * Derived from the union rather than hand-maintained so adding a new\n * `ResourceDesiredState` member automatically widens this type.\n */\nexport type ResourceKind = ResourceDesiredState[\"kind\"];\n\n/**\n * Per-kind outputs registry. Each `ResourceKind` must have a matching entry\n * or `ResourceOutputs<K>` is a compile error. Modelled as an interface (not a\n * type alias) so downstream packages can use declaration merging to register\n * outputs for new kinds without touching this module.\n *\n * @example\n *\n * ```ts\n * import { asRobloxAssetId, type ResourceOutputsByKind } from \"@bedrock-rbx/core\";\n *\n * const outputs: ResourceOutputsByKind[\"gamePass\"] = {\n * assetId: asRobloxAssetId(\"9876543210\"),\n * iconAssetIds: { \"en-us\": asRobloxAssetId(\"1122334455\") },\n * };\n *\n * expect(outputs.assetId).toBe(\"9876543210\");\n * ```\n */\nexport interface ResourceOutputsByKind {\n\t/** Outputs returned by the Roblox API for a developer-product resource. */\n\tdeveloperProduct: DeveloperProductOutputs;\n\t/** Outputs returned by the Roblox API for a game-pass resource. */\n\tgamePass: GamePassOutputs;\n\t/** Outputs returned by the Roblox API for a place publish. */\n\tplace: PlaceOutputs;\n\t/** Outputs returned by the Roblox API for a universe reconcile. */\n\tuniverse: UniverseOutputs;\n}\n\n/**\n * Resolved outputs for a specific resource kind.\n *\n * @template K - The resource kind discriminator.\n */\nexport type ResourceOutputs<K extends ResourceKind> = ResourceOutputsByKind[K];\n\n/**\n * Current (live) state for a resource kind.\n *\n * Composed from the matching desired-state shape plus a nested `outputs`\n * object carrying Roblox-assigned identifiers. The outer `K extends\n * ResourceKind` conditional distributes `K` across the union so the default\n * `ResourceCurrentState` resolves to a clean per-kind union rather than a\n * cross-product intersection of every kind's fields.\n *\n * The `outputs` sub-object stays nested (rather than flattening into the\n * top level) to mirror Mantle's `{ inputs, outputs }` state layout,\n * keeping migration copy clean.\n *\n * @template K - The resource kind discriminator. Defaults to the full\n * `ResourceKind` union for the broad form used in `ReadonlyArray`\n * collections.\n *\n * @example\n *\n * ```ts\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * asSha256Hex,\n * type ResourceCurrentState,\n * } from \"@bedrock-rbx/core\";\n *\n * const current: ResourceCurrentState<\"gamePass\"> = {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * iconFileHashes: {\n * \"en-us\": asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * ),\n * },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * outputs: {\n * assetId: asRobloxAssetId(\"9876543210\"),\n * iconAssetIds: { \"en-us\": asRobloxAssetId(\"1122334455\") },\n * },\n * price: 500,\n * };\n *\n * expect(current.outputs.assetId).toBe(\"9876543210\");\n * expect(current.kind).toBe(\"gamePass\");\n * ```\n */\nexport type ResourceCurrentState<K extends ResourceKind = ResourceKind> = K extends ResourceKind\n\t? Prettify<\n\t\t\tExtract<ResourceDesiredState, { kind: K }> & { readonly outputs: ResourceOutputs<K> }\n\t\t>\n\t: never;\n\ntype Prettify<T> = { readonly [K in keyof T]: T[K] };\n\ntype WithOptionalSocialLinks = Readonly<Partial<Record<SocialLinkField, SocialLink | undefined>>>;\n\n/**\n * Copy every social link field that is present as a key on `source`,\n * preserving the tri-state distinction between \"key absent\" (unmanaged,\n * omitted from result) and \"key present with `undefined`\" (cleared,\n * forwarded as-is). Shared by flatten, build-desired, and the universe\n * driver so all three layers propagate the same tri-state semantics.\n *\n * @param source - Object whose declared social link keys should be copied.\n * @returns Partial record containing only the social link keys present on\n * `source`; absent keys stay absent.\n */\nexport function copyDeclaredSocialLinks(\n\tsource: WithOptionalSocialLinks,\n): Partial<Record<SocialLinkField, SocialLink | undefined>> {\n\tconst copied: Partial<Record<SocialLinkField, SocialLink | undefined>> = {};\n\tfor (const field of SOCIAL_LINK_FIELDS) {\n\t\tif (field in source) {\n\t\t\tcopied[field] = source[field];\n\t\t}\n\t}\n\n\treturn copied;\n}\n\n/**\n * Fixed stable key for the singleton universe resource. `flattenConfig`\n * stamps this onto the sole `UniverseDesiredInput` it emits; fixtures and\n * state adapters share the constant so the invariant is encoded once.\n *\n * @example\n *\n * ```ts\n * import { UNIVERSE_SINGLETON_KEY } from \"@bedrock-rbx/core\";\n *\n * expect(UNIVERSE_SINGLETON_KEY).toBe(\"main\");\n * ```\n */\nexport const UNIVERSE_SINGLETON_KEY: ResourceKey = asResourceKey(\"main\");\n","import { ApiError, type OpenCloudError, type Result } from \"@bedrock-rbx/ocale\";\nimport type { PlacesClient, UpdatePlaceParameters } from \"@bedrock-rbx/ocale/places\";\n\nimport type { PlaceDesiredState, PlaceOutputs, ResourceCurrentState } from \"../core/resources.ts\";\nimport { PLACE_MANAGED_METADATA_FIELDS } from \"../core/resources.ts\";\nimport type { ResourceDriver } from \"../ports/resource-driver.ts\";\nimport type { RobloxAssetId } from \"../types/ids.ts\";\n\n/**\n * Dependencies of `createPlaceDriver`. `universeId` is captured at\n * construction time (matching `GamePassDriverDeps`) so each driver instance\n * is bound to a single universe; multi-universe deploys construct one driver\n * per universe. `readFile` is injected because `diff` operates on file hashes\n * while the driver is the only place that needs the raw bytes.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { PlacesClient } from \"@bedrock-rbx/ocale/places\";\n * import { asRobloxAssetId, type PlaceDriverDeps } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return { data: { body: {}, headers: {}, status: 200 }, success: true };\n * },\n * };\n *\n * const deps: PlaceDriverDeps = {\n * client: new PlacesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () => new Uint8Array(),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * };\n *\n * expect(deps.universeId).toBe(\"1234567890\");\n * ```\n */\nexport interface PlaceDriverDeps {\n\t/** Configured places client from `@bedrock-rbx/ocale/places`. */\n\treadonly client: PlacesClient;\n\t/** Reads place-file bytes for upload; rejections propagate out of the driver. */\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\t/** Universe that owns every place this driver publishes. */\n\treadonly universeId: RobloxAssetId;\n}\n\n/**\n * Wraps {@link PlacesClient} as a `ResourceDriver<\"place\">`. `create` and\n * `update` are both thin wrappers over a shared publish helper because the\n * upstream Open Cloud call is identical either way: there is no \"create\n * place\" endpoint (the place is user-supplied input), only \"publish version\".\n *\n * Format is detected from the file extension (`.rbxl` → binary,\n * `.rbxlx` → XML); any other extension returns an `ApiError`-backed failure\n * without hitting the network.\n *\n * @param deps - Injected ocale client, file reader, and owning universe.\n * @returns A driver indexable by `\"place\"` in a `DriverRegistry`.\n * @throws Whatever `deps.readFile` rejects with.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { PlacesClient } from \"@bedrock-rbx/ocale/places\";\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * asSha256Hex,\n * createPlaceDriver,\n * } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return {\n * data: { body: { versionNumber: 1 }, headers: {}, status: 200 },\n * success: true,\n * };\n * },\n * };\n *\n * const driver = createPlaceDriver({\n * client: new PlacesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () =>\n * new Uint8Array([\n * 0x3c, 0x72, 0x6f, 0x62, 0x6c, 0x6f, 0x78, 0x21, 0x89, 0xff, 0x0d, 0x0a, 0x1a,\n * 0x0a,\n * ]),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * });\n *\n * return driver\n * .create({\n * description: undefined,\n * displayName: undefined,\n * fileHash: asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * ),\n * filePath: \"places/start.rbxl\",\n * key: asResourceKey(\"start-place\"),\n * kind: \"place\",\n * placeId: asRobloxAssetId(\"4711\"),\n * serverSize: undefined,\n * })\n * .then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.outputs.versionNumber).toBe(1);\n * }\n * });\n * ```\n */\nexport function createPlaceDriver(deps: PlaceDriverDeps): ResourceDriver<\"place\"> {\n\treturn {\n\t\tasync create(desired) {\n\t\t\treturn publishPlace(deps, desired);\n\t\t},\n\t\tasync update(_current, desired) {\n\t\t\treturn publishPlace(deps, desired);\n\t\t},\n\t};\n}\n\nfunction buildMetadataParameters(\n\tuniverseId: RobloxAssetId,\n\tdesired: PlaceDesiredState,\n): undefined | UpdatePlaceParameters {\n\tconst metadata = PLACE_MANAGED_METADATA_FIELDS.reduce<Partial<UpdatePlaceParameters>>(\n\t\t(accumulator, field) => {\n\t\t\tconst value = desired[field];\n\t\t\treturn value === undefined ? accumulator : { ...accumulator, [field]: value };\n\t\t},\n\t\t{},\n\t);\n\n\tif (Object.keys(metadata).length === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn { ...metadata, placeId: desired.placeId, universeId };\n}\n\nfunction detectFormat(filePath: string): \"rbxl\" | \"rbxlx\" | undefined {\n\tif (filePath.endsWith(\".rbxlx\")) {\n\t\treturn \"rbxlx\";\n\t}\n\n\tif (filePath.endsWith(\".rbxl\")) {\n\t\treturn \"rbxl\";\n\t}\n\n\treturn undefined;\n}\n\nasync function publishVersion(\n\tdeps: PlaceDriverDeps,\n\tdesired: PlaceDesiredState,\n): Promise<Result<PlaceOutputs, OpenCloudError>> {\n\tconst format = detectFormat(desired.filePath);\n\tif (format === undefined) {\n\t\treturn {\n\t\t\terr: new ApiError(\n\t\t\t\t`Unsupported place file extension for ${desired.filePath}; expected .rbxl or .rbxlx`,\n\t\t\t\t{ statusCode: 0 },\n\t\t\t),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\tconst body = await deps.readFile(desired.filePath);\n\treturn deps.client.publish({\n\t\t// Narrows `Uint8Array<ArrayBufferLike>` to `Uint8Array<ArrayBuffer>`\n\t\t// so the ocale wire type rejects SharedArrayBuffer at the call site.\n\t\tbody: Uint8Array.from(body),\n\t\tformat,\n\t\tplaceId: desired.placeId,\n\t\tuniverseId: deps.universeId,\n\t});\n}\n\nasync function publishPlace(\n\tdeps: PlaceDriverDeps,\n\tdesired: PlaceDesiredState,\n): Promise<Result<ResourceCurrentState<\"place\">, OpenCloudError>> {\n\tconst publishResult = await publishVersion(deps, desired);\n\tif (!publishResult.success) {\n\t\treturn publishResult;\n\t}\n\n\tconst metadataParameters = buildMetadataParameters(deps.universeId, desired);\n\tif (metadataParameters !== undefined) {\n\t\tconst metadataResult = await deps.client.update(metadataParameters);\n\t\tif (!metadataResult.success) {\n\t\t\treturn metadataResult;\n\t\t}\n\t}\n\n\treturn { data: { ...desired, outputs: publishResult.data }, success: true };\n}\n","import { ApiError, type OpenCloudError, type Result } from \"@bedrock-rbx/ocale\";\nimport type { PlacesClient } from \"@bedrock-rbx/ocale/places\";\nimport type { UniversesClient, UpdateUniverseParameters } from \"@bedrock-rbx/ocale/universes\";\n\nimport {\n\tcopyDeclaredSocialLinks,\n\ttype ResourceCurrentState,\n\tSOCIAL_LINK_FIELDS,\n\tUNIVERSE_MANAGED_FLAGS,\n\ttype UniverseDesiredState,\n} from \"../core/resources.ts\";\nimport type { ResourceDriver } from \"../ports/resource-driver.ts\";\nimport { asRobloxAssetId } from \"../types/ids.ts\";\n\n/**\n * Dependencies of `createUniverseDriver`. The driver reconciles the\n * universe singleton against both the universes endpoint and the root\n * place (for fields Roblox marks read-only on the universe, like\n * `displayName`). There is no `universeId` at construction time because\n * the universe *is* the resource the driver reconciles, so the ID rides\n * along on each `UniverseDesiredState`.\n */\nexport interface UniverseDriverDeps {\n\t/** Configured places client from `@bedrock-rbx/ocale/places`. */\n\treadonly places: PlacesClient;\n\t/** Configured universes client from `@bedrock-rbx/ocale/universes`. */\n\treadonly universes: UniversesClient;\n}\n\ninterface ResolvedUniverse {\n\treadonly rootPlaceId: string;\n}\n\ninterface ReconcileInputs {\n\treadonly deps: UniverseDriverDeps;\n\treadonly desired: UniverseDesiredState;\n}\n\n/**\n * Wraps {@link UniversesClient} as a `ResourceDriver<\"universe\">`. `create`\n * and `update` both delegate to a shared reconcile helper because Open\n * Cloud cannot mint universes; the user supplies an existing `universeId`\n * and bedrock adopts the universe on first apply.\n *\n * A `NotFound` error (HTTP 404) from `UniversesClient.update` is repackaged\n * as an adoption-error `ApiError` whose message names the config key and\n * the `universeId`, so operators can tell adoption failure apart from\n * transient upstream errors. A successful response whose `rootPlaceId` is\n * absent surfaces as an `ApiError` with status 200, mirroring the\n * malformed-response guard in `GamePassDriver`.\n *\n * When `displayName` is declared, the driver routes that field through\n * `PlacesClient.update` on the root place after the universe PATCH\n * succeeds. A subsequent places failure surfaces to the caller as the\n * driver's error result without rolling back the prior universe patch,\n * so callers observing a partial failure should reconcile by\n * reapplying rather than assuming the universe-level fields are\n * unchanged.\n *\n * @param deps - Injected ocale clients (universes plus places for the\n * read-only universe fields Roblox derives from the root place).\n * @returns A driver indexable by `\"universe\"` in a `DriverRegistry`.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { PlacesClient } from \"@bedrock-rbx/ocale/places\";\n * import { UniversesClient } from \"@bedrock-rbx/ocale/universes\";\n * import { validUniverseBody } from \"@bedrock-rbx/ocale/testing\";\n * import {\n * asRobloxAssetId,\n * createUniverseDriver,\n * UNIVERSE_SINGLETON_KEY,\n * } from \"@bedrock-rbx/core\";\n *\n * const universeBodyHttpClient: HttpClient = {\n * async request() {\n * return {\n * data: {\n * body: validUniverseBody({\n * path: \"universes/1234567890\",\n * rootPlace: \"universes/1234567890/places/4711\",\n * }),\n * headers: {},\n * status: 200,\n * },\n * success: true,\n * };\n * },\n * };\n *\n * const driver = createUniverseDriver({\n * places: new PlacesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient: universeBodyHttpClient,\n * sleep: async () => {},\n * }),\n * universes: new UniversesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient: universeBodyHttpClient,\n * sleep: async () => {},\n * }),\n * });\n *\n * return driver\n * .create({\n * consoleEnabled: undefined,\n * desktopEnabled: true,\n * displayName: undefined,\n * key: UNIVERSE_SINGLETON_KEY,\n * kind: \"universe\",\n * mobileEnabled: undefined,\n * privateServerPriceRobux: undefined,\n * tabletEnabled: undefined,\n * universeId: asRobloxAssetId(\"1234567890\"),\n * voiceChatEnabled: true,\n * vrEnabled: undefined,\n * })\n * .then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.outputs.rootPlaceId).toBe(\"4711\");\n * }\n * });\n * ```\n */\nexport function createUniverseDriver(deps: UniverseDriverDeps): ResourceDriver<\"universe\"> {\n\treturn {\n\t\tasync create(desired) {\n\t\t\treturn reconcileUniverse({ deps, desired });\n\t\t},\n\t\tasync update(_current, desired) {\n\t\t\treturn reconcileUniverse({ deps, desired });\n\t\t},\n\t};\n}\n\nfunction toCurrentState(\n\tdesired: UniverseDesiredState,\n\trootPlaceId: string,\n): ResourceCurrentState<\"universe\"> {\n\treturn {\n\t\t...desired,\n\t\toutputs: { rootPlaceId: asRobloxAssetId(rootPlaceId) },\n\t};\n}\n\nfunction buildParameters(desired: UniverseDesiredState): UpdateUniverseParameters {\n\tconst base = UNIVERSE_MANAGED_FLAGS.reduce<UpdateUniverseParameters>(\n\t\t(accumulator, flag) => {\n\t\t\tconst isEnabled = desired[flag];\n\t\t\treturn isEnabled === undefined ? accumulator : { ...accumulator, [flag]: isEnabled };\n\t\t},\n\t\t{ universeId: desired.universeId },\n\t);\n\n\tconst withPrice =\n\t\t\"privateServerPriceRobux\" in desired\n\t\t\t? { ...base, privateServerPriceRobux: desired.privateServerPriceRobux }\n\t\t\t: base;\n\n\treturn { ...withPrice, ...copyDeclaredSocialLinks(desired) };\n}\n\nfunction wrapUpdateError(err: OpenCloudError, desired: UniverseDesiredState): OpenCloudError {\n\tif (err instanceof ApiError && err.statusCode === 404) {\n\t\treturn new ApiError(\n\t\t\t`Universe ${desired.universeId} (key '${desired.key}') was not found; adoption failed`,\n\t\t\t{ statusCode: 404 },\n\t\t);\n\t}\n\n\treturn err;\n}\n\nfunction hasUniverseLevelUpdate(desired: UniverseDesiredState): boolean {\n\tif (UNIVERSE_MANAGED_FLAGS.some((flag) => desired[flag] !== undefined)) {\n\t\treturn true;\n\t}\n\n\tif (\"privateServerPriceRobux\" in desired) {\n\t\treturn true;\n\t}\n\n\treturn SOCIAL_LINK_FIELDS.some((field) => field in desired);\n}\n\nasync function resolveUniverse(\n\tdeps: UniverseDriverDeps,\n\tdesired: UniverseDesiredState,\n): Promise<Result<ResolvedUniverse, OpenCloudError>> {\n\tconst result = hasUniverseLevelUpdate(desired)\n\t\t? await deps.universes.update(buildParameters(desired))\n\t\t: await deps.universes.get({ universeId: desired.universeId });\n\n\tif (!result.success) {\n\t\treturn { err: wrapUpdateError(result.err, desired), success: false };\n\t}\n\n\tconst { rootPlaceId } = result.data;\n\tif (rootPlaceId === undefined) {\n\t\treturn {\n\t\t\terr: new ApiError(\n\t\t\t\t`Malformed universe response for ${desired.universeId}: rootPlaceId missing`,\n\t\t\t\t{ statusCode: 200 },\n\t\t\t),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: { rootPlaceId }, success: true };\n}\n\nasync function reconcileUniverse(\n\tinputs: ReconcileInputs,\n): Promise<Result<ResourceCurrentState<\"universe\">, OpenCloudError>> {\n\tconst { deps, desired } = inputs;\n\tconst universeResult = await resolveUniverse(deps, desired);\n\tif (!universeResult.success) {\n\t\treturn universeResult;\n\t}\n\n\tconst { rootPlaceId } = universeResult.data;\n\tif (desired.displayName !== undefined) {\n\t\tconst placesResult = await deps.places.update({\n\t\t\tdisplayName: desired.displayName,\n\t\t\tplaceId: rootPlaceId,\n\t\t\tuniverseId: desired.universeId,\n\t\t});\n\t\tif (!placesResult.success) {\n\t\t\treturn { err: placesResult.err, success: false };\n\t\t}\n\t}\n\n\treturn { data: toCurrentState(desired, rootPlaceId), success: true };\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { spawn } from \"node:child_process\";\nimport process from \"node:process\";\n\nimport type { Spawner, SpawnInvocation, SpawnLaunchError } from \"./spawner.ts\";\n\n/**\n * Translate a `child.on(\"close\", code, signal)` payload into the\n * {@link Spawner.spawn} return shape. Extracted from the adapter so the\n * signal-terminated branch can be exercised without launching a real\n * process. The caller normalizes node's `null` to `undefined` at the\n * boundary so this helper never sees `null`.\n * @param code - Exit code reported by the child, or `undefined` if the\n * child was terminated by a signal before exiting.\n * @param signal - Signal name reported by the child, or `undefined` when\n * no signal terminated it.\n * @returns `Ok(code)` for a clean exit (including `0`); otherwise\n * `Err(launchFailed)` carrying a synthetic Error whose message names\n * the signal.\n */\nexport function classifySpawnClose(\n\tcode: number | undefined,\n\tsignal: NodeJS.Signals | undefined,\n): Result<number, SpawnLaunchError> {\n\tif (code !== undefined) {\n\t\treturn { data: code, success: true };\n\t}\n\n\tconst cause: NodeJS.ErrnoException = new Error(\n\t\t`spawned process terminated by signal ${signal ?? \"unknown\"}`,\n\t);\n\treturn { err: { cause, kind: \"launchFailed\" }, success: false };\n}\n\n/**\n * Construct a {@link Spawner} backed by `node:child_process.spawn` with\n * `stdio` inherited from the parent process. The child's environment is\n * `process.env` overlaid with {@link SpawnInvocation.envOverrides} (overrides\n * win on key collision).\n *\n * - Exit codes resolve as `Ok(exitCode)` (including `0`).\n * - `ENOENT` and other launch-time errors resolve as `Err(launchFailed)`\n * with the original error in `cause` (its `code` field carries the\n * errno where present).\n * - Children terminated by signal before producing an exit code collapse\n * into `launchFailed` with a synthetic `Error` whose message names the\n * signal; a distinct variant lands the day a caller needs to act on the\n * difference.\n *\n * @returns A `Spawner` whose `spawn` settles once the child closes.\n * @example\n *\n * ```ts\n * import { createDefaultSpawner } from \"@bedrock-rbx/core\";\n * import process from \"node:process\";\n *\n * const spawner = createDefaultSpawner();\n *\n * return spawner\n * .spawn({\n * args: [\"-e\", \"process.exit(0)\"],\n * command: process.execPath,\n * envOverrides: {},\n * })\n * .then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data).toBe(0);\n * }\n * });\n * ```\n */\nexport function createDefaultSpawner(): Spawner {\n\treturn { spawn: spawnViaChildProcess };\n}\n\nasync function spawnViaChildProcess(invocation: SpawnInvocation): ReturnType<Spawner[\"spawn\"]> {\n\treturn new Promise((resolve) => {\n\t\tconst child = spawn(invocation.command, [...invocation.args], {\n\t\t\tenv: { ...process.env, ...invocation.envOverrides },\n\t\t\tstdio: \"inherit\",\n\t\t});\n\n\t\tchild.once(\"error\", (error: NodeJS.ErrnoException) => {\n\t\t\tresolve({ err: { cause: error, kind: \"launchFailed\" }, success: false });\n\t\t});\n\n\t\tchild.once(\"close\", (code, signal) => {\n\t\t\tresolve(classifySpawnClose(code ?? undefined, signal ?? undefined));\n\t\t});\n\t});\n}\n","/** Credential flags that may be supplied on the CLI and translated to env-var overrides. */\ninterface CredentialFlags {\n\t/** Roblox Open Cloud API key override; translates to BEDROCK_API_KEY when defined. */\n\treadonly apiKey?: string;\n\t/** GitHub token override; translates to BEDROCK_GITHUB_TOKEN when defined. */\n\treadonly githubToken?: string;\n}\n\n/**\n * Map CLI credential flags to their corresponding env-var names, omitting\n * entries whose flag is `undefined`.\n * @param flags - CLI credential flag values to translate.\n * @returns An immutable record of env-var names to their override values.\n */\nexport function buildCredentialOverrides(flags: CredentialFlags): Readonly<Record<string, string>> {\n\tconst overrides: Record<string, string> = {};\n\tif (flags.apiKey !== undefined) {\n\t\toverrides[\"BEDROCK_API_KEY\"] = flags.apiKey;\n\t}\n\n\tif (flags.githubToken !== undefined) {\n\t\toverrides[\"BEDROCK_GITHUB_TOKEN\"] = flags.githubToken;\n\t}\n\n\treturn overrides;\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { buildCredentialOverrides } from \"./credential-environment-overrides.ts\";\nimport type { Spawner, SpawnInvocation, SpawnLaunchCause } from \"./spawner.ts\";\n\n/**\n * Parsed deploy arguments forwarded to a `.bedrock/<command>.ts` override\n * script. Credential flags are translated into env-var overrides by the\n * dispatcher so secrets never reach the child's argv.\n */\nexport interface OverrideInvocation {\n\t/** Optional `--api-key` value; translated to `BEDROCK_API_KEY` in env. */\n\treadonly apiKey?: string;\n\t/** Optional `--config <path>` value; forwarded unchanged in argv when present. */\n\treadonly configFile?: string;\n\t/** Target environment for this single override invocation. */\n\treadonly environment: string;\n\t/** Optional `--github-token` value; translated to `BEDROCK_GITHUB_TOKEN` in env. */\n\treadonly githubToken?: string;\n\t/** Path to the override script file to invoke. */\n\treadonly overridePath: string;\n}\n\n/**\n * Failure modes returned by {@link dispatchOverride}.\n *\n * - `launchFailed` — the child process could not be started (e.g. `bun`\n * missing, permission denied). Wraps the {@link SpawnLaunchCause} the\n * underlying spawner surfaced so callers can render a precise diagnostic.\n * - `nonZeroExit` — the child started, ran, and exited with a non-zero\n * exit code. Callers should propagate `exitCode` into the CLI's own\n * process exit code so CI failure modes mirror the override's outcome.\n */\nexport type SpawnOverrideError =\n\t| { readonly cause: SpawnLaunchCause; readonly kind: \"launchFailed\" }\n\t| { readonly exitCode: number; readonly kind: \"nonZeroExit\" };\n\n/**\n * Dispatch a single `.bedrock/<command>.ts` override invocation through the\n * supplied {@link Spawner}. Encapsulates the spawn protocol:\n *\n * - argv = `[overridePath, \"--env\", environment]`, with `\"--config\", configFile`\n * appended when supplied.\n * - `apiKey` becomes the `BEDROCK_API_KEY` env-var override; `githubToken`\n * becomes `BEDROCK_GITHUB_TOKEN`. Neither value appears in argv.\n * - `BEDROCK_CLI=1` is always set in the env. The override's `deploy()`\n * reads this on the `getEnv` seam to default to the clack progress\n * adapter; absent that downstream wiring, the variable is a forward-\n * compatible signal a future caller can act on.\n *\n * The dispatcher itself reads no ambient state: every input arrives via the\n * `invocation` argument and the `Spawner` port is the only side-effect seam.\n *\n * @param invocation - Path, environment, and parsed deploy-flag inputs.\n * @param spawner - Port the dispatcher hands the resolved\n * {@link SpawnInvocation} to.\n * @returns `Ok(undefined)` when the child exited zero; otherwise an\n * {@link SpawnOverrideError} discriminating launch vs non-zero exit.\n *\n * @example\n *\n * ```ts\n * import { dispatchOverride, type Spawner } from \"@bedrock-rbx/core\";\n *\n * const spawner: Spawner = {\n * async spawn() {\n * return { data: 0, success: true };\n * },\n * };\n *\n * return dispatchOverride(\n * {\n * environment: \"production\",\n * overridePath: \"/abs/.bedrock/deploy.ts\",\n * },\n * spawner,\n * ).then((result) => {\n * expect(result.success).toBeTrue();\n * });\n * ```\n */\nexport async function dispatchOverride(\n\tinvocation: OverrideInvocation,\n\tspawner: Spawner,\n): Promise<Result<void, SpawnOverrideError>> {\n\tconst args = [invocation.overridePath, \"--env\", invocation.environment];\n\tif (invocation.configFile !== undefined) {\n\t\targs.push(\"--config\", invocation.configFile);\n\t}\n\n\tconst credentialOverrides = buildCredentialOverrides(invocation);\n\n\tconst launched = await spawner.spawn({\n\t\targs,\n\t\tcommand: \"bun\",\n\t\tenvOverrides: { ...credentialOverrides, BEDROCK_CLI: \"1\" },\n\t});\n\tif (!launched.success) {\n\t\treturn { err: { cause: launched.err.cause, kind: \"launchFailed\" }, success: false };\n\t}\n\n\tconst exitCode = launched.data;\n\tif (exitCode !== 0) {\n\t\treturn { err: { exitCode, kind: \"nonZeroExit\" }, success: false };\n\t}\n\n\treturn { data: undefined, success: true };\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { type } from \"arktype\";\n\nimport { asResourceKey } from \"../../types/ids.ts\";\nimport type { DeveloperProductDesiredInput } from \"../flatten.ts\";\nimport { hashIconLocales, iconHashesEqual, iconMap } from \"../icons.ts\";\nimport type { DeveloperProductDesiredState, ResourceCurrentState } from \"../resources.ts\";\nimport { OPTIONAL_ROBUX_PRICE, type ResolvedConfig } from \"../schema.ts\";\nimport type { BuildDesiredError, KindIo, ResourceKindModule } from \"./module.ts\";\n\nconst OPTIONAL_BOOLEAN = \"boolean | undefined\";\n\nconst entrySchema = type({\n\t\"name\": \"string\",\n\t\"description\": \"string\",\n\t\"icon?\": iconMap,\n\t\"isRegionalPricingEnabled?\": OPTIONAL_BOOLEAN,\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n\t\"redacted?\": \"boolean | undefined\",\n\t\"storePageEnabled?\": OPTIONAL_BOOLEAN,\n});\n\nfunction flatten(config: ResolvedConfig): ReadonlyArray<DeveloperProductDesiredInput> {\n\treturn Object.entries(config.products ?? {}).map<DeveloperProductDesiredInput>(\n\t\t([key, entry]) => {\n\t\t\tconst base: DeveloperProductDesiredInput = {\n\t\t\t\tkey: asResourceKey(key),\n\t\t\t\tname: entry.name,\n\t\t\t\tdescription: entry.description,\n\t\t\t\tisRegionalPricingEnabled: entry.isRegionalPricingEnabled,\n\t\t\t\tkind: \"developerProduct\",\n\t\t\t\tprice: entry.price,\n\t\t\t\tstorePageEnabled: entry.storePageEnabled,\n\t\t\t};\n\t\t\treturn entry.icon === undefined ? base : { ...base, icon: entry.icon };\n\t\t},\n\t);\n}\n\nasync function normalize(\n\tinput: DeveloperProductDesiredInput,\n\tio: KindIo,\n): Promise<Result<DeveloperProductDesiredState, BuildDesiredError>> {\n\tconst base: DeveloperProductDesiredState = {\n\t\tkey: input.key,\n\t\tname: input.name,\n\t\tdescription: input.description,\n\t\tisRegionalPricingEnabled: input.isRegionalPricingEnabled,\n\t\tkind: \"developerProduct\",\n\t\tprice: input.price,\n\t\tstorePageEnabled: input.storePageEnabled,\n\t};\n\n\tif (input.icon === undefined) {\n\t\treturn { data: base, success: true };\n\t}\n\n\tconst hashes = await hashIconLocales({ key: input.key, icon: input.icon }, io);\n\tif (!hashes.success) {\n\t\treturn hashes;\n\t}\n\n\treturn {\n\t\tdata: { ...base, icon: input.icon, iconFileHashes: hashes.data },\n\t\tsuccess: true,\n\t};\n}\n\nfunction changedFieldsBetween(\n\tdesired: DeveloperProductDesiredState,\n\tcurrent: ResourceCurrentState<\"developerProduct\">,\n): ReadonlyArray<string> {\n\treturn [\n\t\t...(desired.description === current.description ? [] : [\"description\"]),\n\t\t...(desired.icon?.[\"en-us\"] === current.icon?.[\"en-us\"] ? [] : [\"icon\"]),\n\t\t...(iconHashesEqual(current.iconFileHashes, desired.iconFileHashes)\n\t\t\t? []\n\t\t\t: [\"iconFileHashes\"]),\n\t\t...(desired.name === current.name ? [] : [\"name\"]),\n\t\t...(desired.price === current.price ? [] : [\"price\"]),\n\t\t...(desired.isRegionalPricingEnabled === undefined ||\n\t\tdesired.isRegionalPricingEnabled === current.isRegionalPricingEnabled\n\t\t\t? []\n\t\t\t: [\"isRegionalPricingEnabled\"]),\n\t\t...(desired.storePageEnabled === undefined ||\n\t\tdesired.storePageEnabled === current.storePageEnabled\n\t\t\t? []\n\t\t\t: [\"storePageEnabled\"]),\n\t];\n}\n\nfunction fieldsEqual(\n\tdesired: DeveloperProductDesiredState,\n\tcurrent: ResourceCurrentState<\"developerProduct\">,\n): boolean {\n\treturn changedFieldsBetween(desired, current).length === 0;\n}\n\nfunction assertReconcilable(\n\tcurrent: ResourceCurrentState<\"developerProduct\">,\n\tdesired: DeveloperProductDesiredState,\n): Result<undefined, BuildDesiredError> {\n\tif (current.iconFileHashes !== undefined && desired.iconFileHashes === undefined) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tkey: desired.key,\n\t\t\t\tkind: \"iconRemovalRejected\",\n\t\t\t\tmessage: `developer product '${desired.key}' had an icon recorded in state, but the desired entry no longer declares one. The Roblox developer-product API has no documented way to unset an icon; remove the resource entry to delete the product, or restore the icon path to keep the existing image.`,\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: undefined, success: true };\n}\n\n/**\n * Resource-kind module for Roblox developer products. Owns the entry\n * schema, flattening, icon-hash normalization, drift-equality, and the\n * pre-reconcile icon-removal rejection for the `developerProduct` kind.\n */\nexport const developerProductKind: ResourceKindModule<\"developerProduct\"> = {\n\tassertReconcilable,\n\tchangedFieldsBetween,\n\tentrySchema,\n\tfieldsEqual,\n\tflatten,\n\tkind: \"developerProduct\",\n\tnormalize,\n};\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { type } from \"arktype\";\n\nimport { asResourceKey } from \"../../types/ids.ts\";\nimport type { GamePassDesiredInput } from \"../flatten.ts\";\nimport { hashIconLocales, iconHashesEqual, iconMap } from \"../icons.ts\";\nimport type { GamePassDesiredState, ResourceCurrentState } from \"../resources.ts\";\nimport { OPTIONAL_ROBUX_PRICE, type ResolvedConfig } from \"../schema.ts\";\nimport type { BuildDesiredError, KindIo, ResourceKindModule } from \"./module.ts\";\n\nconst entrySchema = type({\n\t\"name\": \"string\",\n\t\"description\": \"string\",\n\t\"icon\": iconMap,\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n\t\"redacted?\": \"boolean | undefined\",\n});\n\nfunction flatten(config: ResolvedConfig): ReadonlyArray<GamePassDesiredInput> {\n\treturn Object.entries(config.passes ?? {}).map<GamePassDesiredInput>(([key, entry]) => {\n\t\treturn {\n\t\t\tkey: asResourceKey(key),\n\t\t\tname: entry.name,\n\t\t\tdescription: entry.description,\n\t\t\ticon: entry.icon,\n\t\t\tkind: \"gamePass\",\n\t\t\tprice: entry.price,\n\t\t};\n\t});\n}\n\nasync function normalize(\n\tinput: GamePassDesiredInput,\n\tio: KindIo,\n): Promise<Result<GamePassDesiredState, BuildDesiredError>> {\n\tconst hashes = await hashIconLocales({ key: input.key, icon: input.icon }, io);\n\tif (!hashes.success) {\n\t\treturn hashes;\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\tkey: input.key,\n\t\t\tname: input.name,\n\t\t\tdescription: input.description,\n\t\t\ticon: input.icon,\n\t\t\ticonFileHashes: hashes.data,\n\t\t\tkind: \"gamePass\",\n\t\t\tprice: input.price,\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction changedFieldsBetween(\n\tdesired: GamePassDesiredState,\n\tcurrent: ResourceCurrentState<\"gamePass\">,\n): ReadonlyArray<string> {\n\treturn [\n\t\t...(desired.description === current.description ? [] : [\"description\"]),\n\t\t...(desired.icon[\"en-us\"] === current.icon[\"en-us\"] ? [] : [\"icon\"]),\n\t\t...(iconHashesEqual(current.iconFileHashes, desired.iconFileHashes)\n\t\t\t? []\n\t\t\t: [\"iconFileHashes\"]),\n\t\t...(desired.name === current.name ? [] : [\"name\"]),\n\t\t...(desired.price === current.price ? [] : [\"price\"]),\n\t];\n}\n\nfunction fieldsEqual(\n\tdesired: GamePassDesiredState,\n\tcurrent: ResourceCurrentState<\"gamePass\">,\n): boolean {\n\treturn changedFieldsBetween(desired, current).length === 0;\n}\n\n/**\n * Resource-kind module for Roblox game passes. Owns the entry schema,\n * flattening, icon-hash normalization, and drift-equality for the\n * `gamePass` kind.\n */\nexport const gamePassKind: ResourceKindModule<\"gamePass\"> = {\n\tchangedFieldsBetween,\n\tentrySchema,\n\tfieldsEqual,\n\tflatten,\n\tkind: \"gamePass\",\n\tnormalize,\n};\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { type } from \"arktype\";\n\nimport { asResourceKey, asRobloxAssetId, asSha256Hex } from \"../../types/ids.ts\";\nimport type { PlaceDesiredInput } from \"../flatten.ts\";\nimport { PLACE_MANAGED_METADATA_FIELDS } from \"../resources.ts\";\nimport type { PlaceDesiredState, ResourceCurrentState } from \"../resources.ts\";\nimport { OPTIONAL_POSITIVE_INTEGER } from \"../schema.ts\";\nimport type { ResolvedConfig } from \"../schema.ts\";\nimport { sha256Hex } from \"./hash.ts\";\nimport type { BuildDesiredError, KindIo, ResourceKindModule } from \"./module.ts\";\nimport { readBytes } from \"./read-bytes.ts\";\n\nconst entrySchema = type({\n\t\"description?\": \"string | undefined\",\n\t\"displayName?\": \"string | undefined\",\n\t\"filePath\": \"string\",\n\t\"placeId\": \"string.digits\",\n\t\"serverSize?\": OPTIONAL_POSITIVE_INTEGER,\n}).onUndeclaredKey(\"reject\");\n\nfunction flatten(config: ResolvedConfig): ReadonlyArray<PlaceDesiredInput> {\n\treturn Object.entries(config.places ?? {}).map<PlaceDesiredInput>(([key, entry]) => {\n\t\treturn {\n\t\t\tkey: asResourceKey(key),\n\t\t\tdescription: entry.description,\n\t\t\tdisplayName: entry.displayName,\n\t\t\tfilePath: entry.filePath,\n\t\t\tkind: \"place\",\n\t\t\tplaceId: asRobloxAssetId(entry.placeId),\n\t\t\tserverSize: entry.serverSize,\n\t\t};\n\t});\n}\n\nasync function normalize(\n\tinput: PlaceDesiredInput,\n\tio: KindIo,\n): Promise<Result<PlaceDesiredState, BuildDesiredError>> {\n\tconst read = await readBytes({ key: input.key, filePath: input.filePath }, io);\n\tif (!read.success) {\n\t\treturn read;\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\tkey: input.key,\n\t\t\tdescription: input.description,\n\t\t\tdisplayName: input.displayName,\n\t\t\tfileHash: asSha256Hex(await sha256Hex(read.data)),\n\t\t\tfilePath: input.filePath,\n\t\t\tkind: \"place\",\n\t\t\tplaceId: input.placeId,\n\t\t\tserverSize: input.serverSize,\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction changedFieldsBetween(\n\tdesired: PlaceDesiredState,\n\tcurrent: ResourceCurrentState<\"place\">,\n): ReadonlyArray<string> {\n\treturn [\n\t\t...(desired.fileHash === current.fileHash ? [] : [\"fileHash\"]),\n\t\t...(desired.filePath === current.filePath ? [] : [\"filePath\"]),\n\t\t...(desired.placeId === current.placeId ? [] : [\"placeId\"]),\n\t\t...PLACE_MANAGED_METADATA_FIELDS.filter((field) => {\n\t\t\tconst desiredValue = desired[field];\n\t\t\treturn desiredValue !== undefined && desiredValue !== current[field];\n\t\t}),\n\t];\n}\n\nfunction fieldsEqual(desired: PlaceDesiredState, current: ResourceCurrentState<\"place\">): boolean {\n\treturn changedFieldsBetween(desired, current).length === 0;\n}\n\n/**\n * Resource-kind module for Roblox places. Owns the entry schema,\n * flattening, file-hash normalization, and drift-equality for the `place`\n * kind.\n */\nexport const placeKind: ResourceKindModule<\"place\"> = {\n\tchangedFieldsBetween,\n\tentrySchema,\n\tfieldsEqual,\n\tflatten,\n\tkind: \"place\",\n\tnormalize,\n};\n","import type { Result } from \"@bedrock-rbx/ocale\";\nimport type { SocialLink } from \"@bedrock-rbx/ocale/universes\";\n\nimport { type } from \"arktype\";\n\nimport { asRobloxAssetId } from \"../../types/ids.ts\";\nimport type { UniverseDesiredInput } from \"../flatten.ts\";\nimport {\n\tcopyDeclaredSocialLinks,\n\ttype ResourceCurrentState,\n\tSOCIAL_LINK_FIELDS,\n\tUNIVERSE_MANAGED_FLAGS,\n\tUNIVERSE_SINGLETON_KEY,\n\ttype UniverseDesiredState,\n} from \"../resources.ts\";\nimport type { ResolvedConfig } from \"../schema.ts\";\nimport type { BuildDesiredError, KindIo, ResourceKindModule } from \"./module.ts\";\n\nconst OPTIONAL_BOOLEAN = \"boolean | undefined\";\n\nconst socialLink = type({\n\ttitle: \"string\",\n\turi: \"string\",\n}).onUndeclaredKey(\"reject\");\n\nconst socialLinkOrUndefined = socialLink.or(\"undefined\");\n\nconst entrySchema = type({\n\t\"consoleEnabled?\": OPTIONAL_BOOLEAN,\n\t\"desktopEnabled?\": OPTIONAL_BOOLEAN,\n\t\"discordSocialLink?\": socialLinkOrUndefined,\n\t\"displayName?\": \"string | undefined\",\n\t\"facebookSocialLink?\": socialLinkOrUndefined,\n\t\"guildedSocialLink?\": socialLinkOrUndefined,\n\t\"mobileEnabled?\": OPTIONAL_BOOLEAN,\n\t\"privateServerPriceRobux?\": \"number.integer >= 0 | undefined\",\n\t\"robloxGroupSocialLink?\": socialLinkOrUndefined,\n\t\"tabletEnabled?\": OPTIONAL_BOOLEAN,\n\t\"twitchSocialLink?\": socialLinkOrUndefined,\n\t\"twitterSocialLink?\": socialLinkOrUndefined,\n\t\"universeId\": \"string.digits\",\n\t\"voiceChatEnabled?\": OPTIONAL_BOOLEAN,\n\t\"vrEnabled?\": OPTIONAL_BOOLEAN,\n\t\"youtubeSocialLink?\": socialLinkOrUndefined,\n}).onUndeclaredKey(\"reject\");\n\nfunction flatten(config: ResolvedConfig): ReadonlyArray<UniverseDesiredInput> {\n\tconst entry = config.universe;\n\tif (entry === undefined) {\n\t\treturn [];\n\t}\n\n\tconst base: UniverseDesiredInput = {\n\t\tkey: UNIVERSE_SINGLETON_KEY,\n\t\tconsoleEnabled: entry.consoleEnabled,\n\t\tdesktopEnabled: entry.desktopEnabled,\n\t\tdisplayName: entry.displayName,\n\t\tkind: \"universe\",\n\t\tmobileEnabled: entry.mobileEnabled,\n\t\ttabletEnabled: entry.tabletEnabled,\n\t\tuniverseId: asRobloxAssetId(entry.universeId),\n\t\tvoiceChatEnabled: entry.voiceChatEnabled,\n\t\tvrEnabled: entry.vrEnabled,\n\t\t...copyDeclaredSocialLinks(entry),\n\t};\n\n\treturn [\n\t\t\"privateServerPriceRobux\" in entry\n\t\t\t? { ...base, privateServerPriceRobux: entry.privateServerPriceRobux }\n\t\t\t: base,\n\t];\n}\n\nfunction buildBaseDesired(input: UniverseDesiredInput): UniverseDesiredState {\n\tconst base: UniverseDesiredState = {\n\t\tkey: input.key,\n\t\tconsoleEnabled: input.consoleEnabled,\n\t\tdesktopEnabled: input.desktopEnabled,\n\t\tdisplayName: input.displayName,\n\t\tkind: \"universe\",\n\t\tmobileEnabled: input.mobileEnabled,\n\t\ttabletEnabled: input.tabletEnabled,\n\t\tuniverseId: input.universeId,\n\t\tvoiceChatEnabled: input.voiceChatEnabled,\n\t\tvrEnabled: input.vrEnabled,\n\t\t...copyDeclaredSocialLinks(input),\n\t};\n\n\treturn \"privateServerPriceRobux\" in input\n\t\t? { ...base, privateServerPriceRobux: input.privateServerPriceRobux }\n\t\t: base;\n}\n\nasync function normalize(\n\tinput: UniverseDesiredInput,\n\t_io: KindIo,\n): Promise<Result<UniverseDesiredState, BuildDesiredError>> {\n\treturn { data: buildBaseDesired(input), success: true };\n}\n\nfunction socialLinkEqual(a: SocialLink | undefined, b: SocialLink | undefined): boolean {\n\tif (a === undefined) {\n\t\treturn b === undefined;\n\t}\n\n\tif (b === undefined) {\n\t\treturn false;\n\t}\n\n\treturn a.title === b.title && a.uri === b.uri;\n}\n\nfunction changedFieldsBetween(\n\tdesired: UniverseDesiredState,\n\tcurrent: ResourceCurrentState<\"universe\">,\n): ReadonlyArray<string> {\n\treturn [\n\t\t...(desired.universeId === current.universeId ? [] : [\"universeId\"]),\n\t\t...UNIVERSE_MANAGED_FLAGS.filter((flag) => {\n\t\t\tconst isDesiredEnabled = desired[flag];\n\t\t\treturn isDesiredEnabled !== undefined && isDesiredEnabled !== current[flag];\n\t\t}),\n\t\t...(desired.displayName === undefined || desired.displayName === current.displayName\n\t\t\t? []\n\t\t\t: [\"displayName\"]),\n\t\t...(\"privateServerPriceRobux\" in desired &&\n\t\tdesired.privateServerPriceRobux !== current.privateServerPriceRobux\n\t\t\t? [\"privateServerPriceRobux\"]\n\t\t\t: []),\n\t\t...SOCIAL_LINK_FIELDS.filter(\n\t\t\t(field) => field in desired && !socialLinkEqual(desired[field], current[field]),\n\t\t),\n\t];\n}\n\nfunction fieldsEqual(\n\tdesired: UniverseDesiredState,\n\tcurrent: ResourceCurrentState<\"universe\">,\n): boolean {\n\treturn changedFieldsBetween(desired, current).length === 0;\n}\n\n/**\n * Resource-kind module for the singleton Roblox universe. Owns the entry\n * schema, flattening, pass-through normalize (no file I/O), and\n * drift-equality for the `universe` kind.\n */\nexport const universeKind: ResourceKindModule<\"universe\"> = {\n\tchangedFieldsBetween,\n\tentrySchema,\n\tfieldsEqual,\n\tflatten,\n\tkind: \"universe\",\n\tnormalize,\n};\n","import { developerProductKind } from \"./developer-product.ts\";\nimport { gamePassKind } from \"./game-pass.ts\";\nimport type { KindRegistry } from \"./module.ts\";\nimport { placeKind } from \"./place.ts\";\nimport { universeKind } from \"./universe.ts\";\n\n/**\n * Default {@link KindRegistry} composing every resource kind bedrock ships\n * out of the box. Iteration order (`gamePass`, `place`, `universe`,\n * `developerProduct`) matches the order `flattenConfig` emits entries\n * today, preserving the observable order of generated operations.\n *\n * @example\n *\n * ```ts\n * import { defaultKindRegistry } from \"@bedrock-rbx/core\";\n *\n * expect(defaultKindRegistry.gamePass.kind).toBe(\"gamePass\");\n * expect(defaultKindRegistry.place.kind).toBe(\"place\");\n * expect(defaultKindRegistry.universe.kind).toBe(\"universe\");\n * expect(defaultKindRegistry.developerProduct.kind).toBe(\"developerProduct\");\n * ```\n */\nexport const defaultKindRegistry: KindRegistry = {\n\tdeveloperProduct: developerProductKind,\n\tgamePass: gamePassKind,\n\tplace: placeKind,\n\tuniverse: universeKind,\n};\n","import { defaultKindRegistry } from \"./kinds/index.ts\";\nimport type { ResourceKindModule } from \"./kinds/module.ts\";\nimport type { Operation } from \"./operations.ts\";\nimport type { ResourceCurrentState, ResourceDesiredState, ResourceKind } from \"./resources.ts\";\n\n/**\n * Computes the operations required to reconcile `current` state with `desired`\n * state. Pure and synchronous: no I/O, no side effects, no `Result` wrapper.\n *\n * Each entry in `desired` is matched to `current` by `(kind, key)`: resources\n * are uniquely identified by that pair, so a `place` and a `universe` keyed\n * `\"main\"` are independent slots. A `(kind, key)` pair present only in\n * `desired` produces a `create` op; a pair present in both produces an\n * `update` op if any declared field differs or a `noop` op if every field\n * matches.\n *\n * Ops appear in the order their desired entries appear in the input array.\n * `applyOps` regroups them into Phase 1 (universe) and Phase 2 (everything\n * else) when dispatching; the execution order within Phase 2 is not\n * guaranteed because Phase 2 dispatches concurrently. Persisted state-file\n * order is determined by the merge in `deploy.runReconcile` (which retains\n * prior-snapshot positions for unchanged keys), not by this diff output.\n *\n * @param desired - Declared desired state from user config, already normalized\n * (file hashes computed, nullable wire values mapped to `undefined`).\n * @param current - Last-known current state from the state file.\n * @returns Operations to reconcile the two snapshots.\n *\n * @example\n *\n * ```ts\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * asSha256Hex,\n * diff,\n * type GamePassDesiredState,\n * type ResourceCurrentState,\n * } from \"@bedrock-rbx/core\";\n *\n * const hash = asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * );\n *\n * const unchanged: GamePassDesiredState = {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * iconFileHashes: { \"en-us\": hash },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * price: 500,\n * };\n * const drifted: GamePassDesiredState = {\n * ...unchanged,\n * key: asResourceKey(\"legend-pass\"),\n * name: \"Legend Pass (renamed)\",\n * };\n * const fresh: GamePassDesiredState = {\n * ...unchanged,\n * key: asResourceKey(\"rookie-pass\"),\n * name: \"Rookie Pass\",\n * };\n *\n * const current: ReadonlyArray<ResourceCurrentState> = [\n * {\n * ...unchanged,\n * outputs: {\n * assetId: asRobloxAssetId(\"111\"),\n * iconAssetIds: { \"en-us\": asRobloxAssetId(\"222\") },\n * },\n * },\n * {\n * ...drifted,\n * name: \"Legend Pass\",\n * outputs: {\n * assetId: asRobloxAssetId(\"333\"),\n * iconAssetIds: { \"en-us\": asRobloxAssetId(\"444\") },\n * },\n * },\n * ];\n *\n * const ops = diff([unchanged, drifted, fresh], current);\n *\n * expect(ops.map((op) => op.type)).toEqual([\"noop\", \"update\", \"create\"]);\n *\n * const updateOp = ops[1]!;\n * if (updateOp.type === \"update\") {\n * expect(updateOp.changedFields).toStrictEqual([\"name\"]);\n * }\n * ```\n */\nexport function diff(\n\tdesired: ReadonlyArray<ResourceDesiredState>,\n\tcurrent: ReadonlyArray<ResourceCurrentState>,\n): ReadonlyArray<Operation> {\n\tconst currentByKey = new Map(current.map((entry) => [compositeKey(entry), entry]));\n\treturn desired.map((entry) => operationFor(entry, currentByKey.get(compositeKey(entry))));\n}\n\nfunction compositeKey(resource: { readonly key: string; readonly kind: ResourceKind }): string {\n\treturn `${resource.kind}:${resource.key}`;\n}\n\nfunction operationFor(\n\tdesired: ResourceDesiredState,\n\tcurrent: ResourceCurrentState | undefined,\n): Operation {\n\tif (current === undefined) {\n\t\treturn { key: desired.key, desired, type: \"create\" };\n\t}\n\n\tconst module = defaultKindRegistry[desired.kind] as ResourceKindModule<ResourceKind>;\n\tconst changedFields = module.changedFieldsBetween(desired, current);\n\tif (changedFields.length === 0) {\n\t\treturn { key: desired.key, kind: desired.kind, type: \"noop\" };\n\t}\n\n\treturn { key: desired.key, changedFields, current, desired, type: \"update\" };\n}\n","/**\n * Default template applied when a project enables display-name prefixing\n * without supplying its own `displayNamePrefix.format`. Yields outputs\n * like `[STAGING] ` for an environment whose `label` is `\"staging\"`.\n */\nexport const DEFAULT_PREFIX_FORMAT = \"[{LABEL}] \";\n\nconst PLACEHOLDER_PATTERN = /\\{(LABEL|Label|label)\\}/g;\n\n/**\n * Render the prefix that selectEnvironment prepends to declared display\n * names when a project enables `displayNamePrefix`. The template\n * recognizes three placeholders:\n *\n * - `{label}`: label as written.\n * - `{LABEL}`: upper-cased label.\n * - `{Label}`: capitalized label (first character upper, rest as written).\n *\n * Other characters in the template flow through verbatim.\n *\n * @param label - Environment label declared on `EnvironmentEntry.label`.\n * @param format - Template string. Falls back to\n * {@link DEFAULT_PREFIX_FORMAT} when omitted.\n * @returns The rendered prefix string.\n *\n * @example\n *\n * ```ts\n * import { renderDisplayNamePrefix } from \"@bedrock-rbx/core\";\n *\n * expect(renderDisplayNamePrefix(\"staging\")).toBe(\"[STAGING] \");\n * expect(renderDisplayNamePrefix(\"staging\", \"{Label}: \")).toBe(\"Staging: \");\n * expect(renderDisplayNamePrefix(\"dev\", \"{LABEL}-{label}\")).toBe(\"DEV-dev\");\n * ```\n */\nexport function renderDisplayNamePrefix(label: string, format?: string): string {\n\tconst template = format ?? DEFAULT_PREFIX_FORMAT;\n\treturn template.replace(PLACEHOLDER_PATTERN, (_match, kind) => {\n\t\tif (kind === \"LABEL\") {\n\t\t\treturn label.toUpperCase();\n\t\t}\n\n\t\tif (kind === \"Label\") {\n\t\t\treturn capitalize(label);\n\t\t}\n\n\t\treturn label;\n\t});\n}\n\nfunction capitalize(value: string): string {\n\treturn value.charAt(0).toUpperCase() + value.slice(1);\n}\n","import type { SocialLink } from \"@bedrock-rbx/ocale/universes\";\n\nimport type { ResourceKey, RobloxAssetId } from \"../types/ids.ts\";\nimport { defaultKindRegistry } from \"./kinds/index.ts\";\nimport type { ResourceKindModule } from \"./kinds/module.ts\";\nimport type { ResourceKind } from \"./resources.ts\";\nimport type { DeveloperProductEntry, GamePassEntry, ResolvedConfig } from \"./schema.ts\";\n\n/**\n * Pre-I/O game-pass input the flattener emits. Extends the authored\n * `GamePassEntry` with the tag discriminator and the `ResourceKey`-branded\n * key so `buildDesired` can consume a flat tagged list and layer on the\n * SHA-256 icon digest.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, type GamePassDesiredInput } from \"@bedrock-rbx/core\";\n *\n * const input: GamePassDesiredInput = {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * price: 500,\n * };\n *\n * expect(input.kind).toBe(\"gamePass\");\n * ```\n */\nexport interface GamePassDesiredInput extends Readonly<GamePassEntry> {\n\t/** User-supplied handle, already validated against the `ResourceKey` brand. */\n\treadonly key: ResourceKey;\n\t/** Discriminator tag for the `ResourceDesiredInput` union. */\n\treadonly kind: \"gamePass\";\n}\n\n/**\n * Pre-I/O place input the flattener emits. Carries the resolved place\n * fields (`filePath` from the root, `placeId` from the per-environment\n * overlay) plus the optional metadata fields and the tag discriminator and\n * the `ResourceKey`-branded key, so `buildDesired` can consume a flat\n * tagged list and layer on the SHA-256 file digest.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, asRobloxAssetId, type PlaceDesiredInput } from \"@bedrock-rbx/core\";\n *\n * const input: PlaceDesiredInput = {\n * description: undefined,\n * displayName: \"Start Place\",\n * filePath: \"places/start.rbxl\",\n * key: asResourceKey(\"start-place\"),\n * kind: \"place\",\n * placeId: asRobloxAssetId(\"4711\"),\n * serverSize: 50,\n * };\n *\n * expect(input.kind).toBe(\"place\");\n * expect(input.displayName).toBe(\"Start Place\");\n * ```\n */\nexport interface PlaceDesiredInput {\n\t/** User-supplied handle, already validated against the `ResourceKey` brand. */\n\treadonly key: ResourceKey;\n\t/** User-facing place description; `undefined` leaves the server value untouched. */\n\treadonly description: string | undefined;\n\t/** User-facing place name; `undefined` leaves the server value untouched. */\n\treadonly displayName: string | undefined;\n\t/** Path to the `.rbxl` or `.rbxlx` file; read by `buildDesired`. */\n\treadonly filePath: string;\n\t/** Discriminator tag for the `ResourceDesiredInput` union. */\n\treadonly kind: \"place\";\n\t/** Existing Roblox place ID, validated and branded at flatten time. */\n\treadonly placeId: RobloxAssetId;\n\t/** Maximum players per server; `undefined` leaves the server value untouched. */\n\treadonly serverSize: number | undefined;\n}\n\n/**\n * Pre-I/O universe input the flattener emits. Carries the fixed singleton\n * key (`\"main\"`) and the branded `universeId` so `buildDesired` can hand a\n * single tagged record downstream without a shape divergence for the\n * singleton kind.\n *\n * @example\n *\n * ```ts\n * import { asRobloxAssetId, UNIVERSE_SINGLETON_KEY, type UniverseDesiredInput } from \"@bedrock-rbx/core\";\n *\n * const input: UniverseDesiredInput = {\n * consoleEnabled: undefined,\n * desktopEnabled: true,\n * displayName: undefined,\n * key: UNIVERSE_SINGLETON_KEY,\n * kind: \"universe\",\n * mobileEnabled: undefined,\n * tabletEnabled: undefined,\n * universeId: asRobloxAssetId(\"1234567890\"),\n * voiceChatEnabled: true,\n * vrEnabled: undefined,\n * };\n *\n * expect(input.kind).toBe(\"universe\");\n * expect(input.key).toBe(\"main\");\n * expect(input.desktopEnabled).toBeTrue();\n * ```\n */\nexport interface UniverseDesiredInput {\n\t/** Synthesized singleton key (`\"main\"`), already validated against the `ResourceKey` brand. */\n\treadonly key: ResourceKey;\n\t/** Whether console players can join; `undefined` leaves the server value untouched. */\n\treadonly consoleEnabled: boolean | undefined;\n\t/** Whether desktop players can join; `undefined` leaves the server value untouched. */\n\treadonly desktopEnabled: boolean | undefined;\n\t/** Discord social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly discordSocialLink?: SocialLink | undefined;\n\t/**\n\t * Display name for the universe. `undefined` leaves the server value\n\t * untouched. The driver routes declared updates through\n\t * `PlacesClient.update` because the universe PATCH endpoint treats\n\t * `displayName` as read-only.\n\t */\n\treadonly displayName: string | undefined;\n\t/** Facebook social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly facebookSocialLink?: SocialLink | undefined;\n\t/** Guilded social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly guildedSocialLink?: SocialLink | undefined;\n\t/**\n\t * Locale-keyed experience-icon paths copied from the user-supplied\n\t * `UniverseEntry`. Absent when the user did not declare an icon block.\n\t */\n\treadonly icon?: Record<\"en-us\", string>;\n\t/** Discriminator tag for the `ResourceDesiredInput` union. */\n\treadonly kind: \"universe\";\n\t/** Whether mobile players can join; `undefined` leaves the server value untouched. */\n\treadonly mobileEnabled: boolean | undefined;\n\t/**\n\t * Private-server price in Robux. A present key with `undefined`\n\t * clears the server value (ocale emits JSON `null`); an absent key\n\t * leaves the server value untouched.\n\t */\n\treadonly privateServerPriceRobux?: number | undefined;\n\t/** Roblox Group social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly robloxGroupSocialLink?: SocialLink | undefined;\n\t/** Whether tablet players can join; `undefined` leaves the server value untouched. */\n\treadonly tabletEnabled: boolean | undefined;\n\t/** Twitch social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly twitchSocialLink?: SocialLink | undefined;\n\t/** Twitter social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly twitterSocialLink?: SocialLink | undefined;\n\t/** Existing Roblox universe ID, validated and branded at flatten time. */\n\treadonly universeId: RobloxAssetId;\n\t/** Whether voice chat is enabled; `undefined` leaves the server value untouched. */\n\treadonly voiceChatEnabled: boolean | undefined;\n\t/** Whether VR players can join; `undefined` leaves the server value untouched. */\n\treadonly vrEnabled: boolean | undefined;\n\t/** YouTube social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly youtubeSocialLink?: SocialLink | undefined;\n}\n\n/**\n * Pre-I/O developer-product input the flattener emits. Extends the authored\n * `DeveloperProductEntry` with the tag discriminator and the\n * `ResourceKey`-branded key so `buildDesired` can consume a flat tagged\n * list.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, type DeveloperProductDesiredInput } from \"@bedrock-rbx/core\";\n *\n * const input: DeveloperProductDesiredInput = {\n * description: \"Stocks the player up with 1,000 premium gems.\",\n * key: asResourceKey(\"gem-pack\"),\n * kind: \"developerProduct\",\n * name: \"Gem Pack\",\n * };\n *\n * expect(input.kind).toBe(\"developerProduct\");\n * ```\n */\nexport interface DeveloperProductDesiredInput extends Readonly<DeveloperProductEntry> {\n\t/** User-supplied handle, already validated against the `ResourceKey` brand. */\n\treadonly key: ResourceKey;\n\t/** Discriminator tag for the `ResourceDesiredInput` union. */\n\treadonly kind: \"developerProduct\";\n}\n\n/**\n * Flat tagged input for `buildDesired`. One member per resource kind; future\n * kinds widen this union as they land.\n */\nexport type ResourceDesiredInput =\n\t| DeveloperProductDesiredInput\n\t| GamePassDesiredInput\n\t| PlaceDesiredInput\n\t| UniverseDesiredInput;\n\n/**\n * Turn a resolved `Config` into a flat, tagged list of resource inputs.\n *\n * Pure and infallible: validation and per-environment overlay merging\n * have already happened upstream (typically via `selectEnvironment`), so\n * every invariant this function relies on is guaranteed by the input\n * shape. Entries appear in the insertion order of each collection;\n * `passes` are emitted before `places`.\n *\n * @param config - Resolved config returned by `selectEnvironment`.\n * @returns Flat tagged list ready for `buildDesired`.\n * @example\n *\n * ```ts\n * import { flattenConfig, selectEnvironment, type Config } from \"@bedrock-rbx/core\";\n *\n * const config: Config = {\n * environments: {\n * production: { places: { \"start-place\": { placeId: \"4711\" } } },\n * },\n * passes: {\n * \"vip-pass\": {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * name: \"VIP Pass\",\n * price: 500,\n * },\n * },\n * places: { \"start-place\": { filePath: \"places/start.rbxl\" } },\n * };\n *\n * const resolved = selectEnvironment(config, \"production\");\n * expect(resolved.success).toBeTrue();\n * if (resolved.success) {\n * const inputs = flattenConfig(resolved.data);\n * expect(inputs.map((input) => input.kind)).toEqual([\"gamePass\", \"place\"]);\n * expect(inputs.map((input) => input.key)).toEqual([\"vip-pass\", \"start-place\"]);\n * }\n * ```\n */\nexport function flattenConfig(config: ResolvedConfig): ReadonlyArray<ResourceDesiredInput> {\n\tconst modules = Object.values(defaultKindRegistry) as ReadonlyArray<\n\t\tResourceKindModule<ResourceKind>\n\t>;\n\treturn modules.flatMap((module) => module.flatten(config));\n}\n","import { createHash } from \"node:crypto\";\n\nimport { asResourceKey, type ResourceKey } from \"../types/ids.ts\";\nimport { REDACTED_ICON_PATH } from \"./redacted-icon.ts\";\nimport type { ResourceKind } from \"./resources.ts\";\nimport type {\n\tDeveloperProductEntry,\n\tGamePassEntry,\n\tRedactedDeveloperProductOverride,\n\tRedactedEnvironmentOverride,\n\tRedactedGamePassOverride,\n\tRedactedPlaceOverride,\n\tResolvedConfig,\n\tResolvedPlaceEntry,\n} from \"./schema.ts\";\n\n/** Default placeholder name pushed for a redacted game-pass. */\nexport const REDACTED_PASS_NAME = \"Redacted Pass\";\n\n/**\n * Common prefix used to build the default name pushed for a redacted\n * developer-product. The full default produced by {@link defaultRedactedProductName}\n * is `${REDACTED_PRODUCT_NAME} ${suffix}`, where `suffix` is a 6-hex-char\n * digest of the resource key (see {@link redactedNameSuffix}). The suffix is\n * required because Roblox enforces per-universe uniqueness on\n * developer-product names, so a shared bare placeholder would collide across\n * multiple redacted entries. The prefix avoids the word `Redacted` and the\n * `#` separator because Roblox's text-moderation filter has been observed\n * silently replacing names matching `Redacted Product #<hex>` with\n * `########################`, which then causes downstream `DuplicateProductName`\n * errors when other redacted entries are moderated to the same string.\n */\nexport const REDACTED_PRODUCT_NAME = \"Hidden Product\";\n\n/** Default placeholder description pushed for any redacted resource. */\nexport const REDACTED_DESCRIPTION = \"\";\n\n/**\n * Default placeholder Robux price pushed for a redacted game-pass or\n * developer-product whose config price is defined. Off-sale resources\n * (`price === undefined`) keep their off-sale state through redaction so a\n * hidden product is never accidentally listed for sale.\n */\nexport const REDACTED_PRICE = 99_999;\n\n/**\n * Per-resource annotation surfaced in preview output for entries that are\n * redacted in the active environment. `hasRealValueEdits` is true when the\n * pre-redaction merged config carries real display values that diverge from\n * the placeholders bedrock pushes, so the renderer can warn the author that\n * their config edits are intentionally not flowing through to Open Cloud.\n */\nexport interface RedactionAnnotation {\n\t/** Resource key the annotation describes. */\n\treadonly key: ResourceKey;\n\t/** True when any real display field differs from the kind's placeholder default. */\n\treadonly hasRealValueEdits: boolean;\n\t/** Resource kind, so the renderer can format `kind:key` consistently with op output. */\n\treadonly kind: ResourceKind;\n}\n\n/**\n * Per-resource env-overlay redaction layers, keyed by kind. Each entry maps\n * a resource key to its env-overlay `redacted` value (boolean or per-field\n * override). `selectEnvironment` extracts these from env-overlay entries\n * before the rest of the overlay is defu-merged onto the root, so the\n * env-resource layer can compose field-by-field with the root layer in\n * {@link applyRedaction}.\n */\nexport interface EnvironmentResourceRedaction {\n\t/** Per-pass env-overlay redaction values keyed by resource key. */\n\treadonly passes?: EnvironmentResourceLayer<RedactedGamePassOverride>;\n\t/** Per-place env-overlay redaction values keyed by resource key. */\n\treadonly places?: EnvironmentResourceLayer<RedactedPlaceOverride>;\n\t/** Per-product env-overlay redaction values keyed by resource key. */\n\treadonly products?: EnvironmentResourceLayer<RedactedDeveloperProductOverride>;\n}\n\ntype RedactionLayer<Override> = boolean | Override | undefined;\n\ntype EnvironmentResourceLayer<Override> = Readonly<Record<string, RedactionLayer<Override>>>;\n\ntype EnvironmentLevel = boolean | RedactedEnvironmentOverride | undefined;\n\n/**\n * Aggregated redaction layers consumed by {@link applyRedaction}. The\n * `envLevel` layer applies to every redactable resource in the env;\n * `envResource` carries per-resource env-overlay overrides keyed by kind.\n */\ninterface RedactionInputs {\n\t/** Env-level redaction layer. Boolean toggle or cross-kind override object. */\n\treadonly envLevel?: EnvironmentLevel;\n\t/** Per-resource env-overlay redaction layers, keyed by kind. */\n\treadonly envResource?: EnvironmentResourceRedaction;\n}\n\ninterface ProductRedactionInputs {\n\treadonly key: string;\n\treadonly entry: DeveloperProductEntry;\n\treadonly override: RedactedDeveloperProductOverride;\n}\n\ninterface ResolvedEntry<Entry, Override> {\n\treadonly key: string;\n\treadonly entry: Entry;\n\treadonly override: Override | undefined;\n}\n\nconst PASS_PRODUCT_ENV_FIELDS = [\n\t\"description\",\n\t\"icon\",\n\t\"name\",\n\t\"price\",\n] as const satisfies ReadonlyArray<keyof RedactedEnvironmentOverride>;\n\nconst PLACE_ENV_FIELDS = [\"description\", \"displayName\"] as const satisfies ReadonlyArray<\n\tkeyof RedactedEnvironmentOverride\n>;\n\ninterface RedactCollectionInputs<Entry, Override> {\n\treadonly collection: Readonly<Record<string, Entry>> | undefined;\n\treadonly environmentForKind: RedactionLayer<Override>;\n\treadonly envResource: EnvironmentResourceLayer<Override> | undefined;\n\treadonly redact: (item: { entry: Entry; key: string; override: Override }) => Entry;\n}\n\ninterface RedactKindInputs<Entry, Override> {\n\treadonly collection: Readonly<Record<string, Entry>> | undefined;\n\treadonly envLevel: EnvironmentLevel;\n\treadonly envResource: EnvironmentResourceLayer<Override> | undefined;\n}\n\n/**\n * Six-character lowercase hex digest of `SHA-256(key)`, used as the\n * disambiguating suffix on a redacted developer-product's default `name`.\n * Stable across config edits (driven only by the bedrock resource key, not\n * declaration order) and opaque to a Roblox player browsing the marketplace.\n * A natural collision is caught before any apply-side driver I/O by `assertAllReconcilable`.\n *\n * @param key - Bedrock resource key for the developer product being redacted.\n * @returns The first six lowercase hex characters of the SHA-256 digest of `key`.\n */\nexport function redactedNameSuffix(key: string): string {\n\treturn createHash(\"sha256\").update(key).digest(\"hex\").slice(0, 6);\n}\n\n/**\n * Default redacted name for a developer product with the given resource key.\n * Combines {@link REDACTED_PRODUCT_NAME} with {@link redactedNameSuffix} so\n * each redacted entry resolves to a unique value the upstream API will accept.\n *\n * @param key - Bedrock resource key for the developer product being redacted.\n * @returns The placeholder name pushed to Roblox for this product.\n */\nexport function defaultRedactedProductName(key: string): string {\n\treturn `${REDACTED_PRODUCT_NAME} ${redactedNameSuffix(key)}`;\n}\n\n/**\n * Pure transform that substitutes bedrock-supplied placeholder content for\n * every resource whose effective redaction state is truthy. Three layers\n * compose field-by-field per resource: env-resource (most-specific, from\n * `inputs.envResource`), root-resource (the `redacted` field on the\n * passed-in entry), and env-level (least-specific, `inputs.envLevel`).\n * The first non-undefined value sets state (`false` carves out); object\n * layers then contribute fields with the most-specific layer winning per\n * field, and bedrock defaults fill any field nobody set. Runs between\n * env-overlay merge and display-name prefix render so the rest of the\n * pipeline (flatten, normalize, diff, apply) operates on already-redacted\n * values and needs no special-case redaction logic.\n *\n * @param config - Post-merge `ResolvedConfig` produced by `selectEnvironment`.\n * @param inputs - Aggregated redaction layers. Omit to skip redaction\n * entirely. See {@link RedactionInputs} for the shape.\n * @returns A `ResolvedConfig` whose redacted entries carry placeholder\n * values; non-redacted entries pass through verbatim, and the input is\n * not mutated.\n */\nexport function applyRedaction(config: ResolvedConfig, inputs?: RedactionInputs): ResolvedConfig {\n\tconst environmentLevel = inputs?.envLevel;\n\tconst environmentResource = inputs?.envResource;\n\tconst passes = redactPasses({\n\t\tcollection: config.passes,\n\t\tenvLevel: environmentLevel,\n\t\tenvResource: environmentResource?.passes,\n\t});\n\tconst places = redactPlaces({\n\t\tcollection: config.places,\n\t\tenvLevel: environmentLevel,\n\t\tenvResource: environmentResource?.places,\n\t});\n\tconst products = redactProducts({\n\t\tcollection: config.products,\n\t\tenvLevel: environmentLevel,\n\t\tenvResource: environmentResource?.products,\n\t});\n\n\tif (passes === config.passes && places === config.places && products === config.products) {\n\t\treturn config;\n\t}\n\n\treturn {\n\t\t...config,\n\t\t...(passes === undefined ? {} : { passes }),\n\t\t...(places === undefined ? {} : { places }),\n\t\t...(products === undefined ? {} : { products }),\n\t};\n}\n\n/**\n * Inspect the pre-redaction merged config and produce one annotation per\n * resource flagged `redacted: true` at either the root entry or its\n * env-overlay counterpart. Callers thread the result into plan output so\n * authors can see which resources are redacted in the active environment\n * and whether their real-value edits are being suppressed.\n *\n * Operates on the pre-redaction view because the post-redaction config no\n * longer carries the real `name`/`description`/`icon` values needed to\n * detect divergence from the placeholder defaults.\n *\n * @param merged - `ResolvedConfig` produced by environment overlay merge,\n * before `applyRedaction` has substituted placeholders.\n * @param environmentResource - Per-kind env-overlay redaction layers\n * extracted from the active env entry. Omit when the caller has no\n * env-overlay layer.\n * @returns Zero or more annotations, one per redacted resource. Empty when\n * the config declares no redacted resources.\n */\nexport function collectRedactionAnnotations(\n\tmerged: ResolvedConfig,\n\tenvironmentResource?: EnvironmentResourceRedaction,\n): ReadonlyArray<RedactionAnnotation> {\n\tconst passes = Object.entries(merged.passes ?? {})\n\t\t.filter(\n\t\t\t([key, entry]) =>\n\t\t\t\tentry.redacted === true || environmentResource?.passes?.[key] === true,\n\t\t)\n\t\t.map(([key, entry]): RedactionAnnotation => {\n\t\t\treturn {\n\t\t\t\tkey: asResourceKey(key),\n\t\t\t\thasRealValueEdits: passHasRealValueEdits(entry),\n\t\t\t\tkind: \"gamePass\",\n\t\t\t};\n\t\t});\n\tconst products = Object.entries(merged.products ?? {})\n\t\t.filter(\n\t\t\t([key, entry]) =>\n\t\t\t\tentry.redacted === true || environmentResource?.products?.[key] === true,\n\t\t)\n\t\t.map(([key, entry]): RedactionAnnotation => {\n\t\t\treturn {\n\t\t\t\tkey: asResourceKey(key),\n\t\t\t\thasRealValueEdits: productHasRealValueEdits(key, entry),\n\t\t\t\tkind: \"developerProduct\",\n\t\t\t};\n\t\t});\n\n\treturn [...passes, ...products];\n}\n\nfunction pickEnvironmentFields<Field extends keyof RedactedEnvironmentOverride>(\n\tenvironmentLevel: EnvironmentLevel,\n\tfields: ReadonlyArray<Field>,\n): RedactionLayer<Pick<RedactedEnvironmentOverride, Field>> {\n\tif (environmentLevel === undefined || typeof environmentLevel === \"boolean\") {\n\t\treturn environmentLevel;\n\t}\n\n\treturn Object.fromEntries(fields.map((field) => [field, environmentLevel[field]])) as Pick<\n\t\tRedactedEnvironmentOverride,\n\t\tField\n\t>;\n}\n\n/**\n * Walk redaction layers most-specific to least-specific and produce the\n * effective per-field override for one resource. Returns `undefined` when the\n * resource is not redacted; returns a (possibly empty) object when it is.\n * State step: the first non-undefined layer sets state -- `false` carves out,\n * `true` or object enables. Fields step: walk every object layer in the same\n * order, taking the first value per field. A field's value may itself be\n * `undefined` (the env-level projection produced by {@link pickEnvironmentFields}\n * includes every projected key, even when the env override left it absent);\n * downstream per-kind redact functions collapse those back to bedrock\n * placeholder defaults via `??`.\n *\n * @template Override - Per-kind override type the resource accepts.\n * @param layers - Layers ordered most-specific (index 0) to least-specific.\n * @returns The effective override, or `undefined` when not redacted.\n */\nfunction resolveEffectiveOverride<Override extends object>(\n\tlayers: ReadonlyArray<RedactionLayer<Override>>,\n): Override | undefined {\n\tconst firstNonUndefined = layers.find((layer) => layer !== undefined);\n\tif (firstNonUndefined === undefined || firstNonUndefined === false) {\n\t\treturn undefined;\n\t}\n\n\tconst effective: Record<string, unknown> = {};\n\tfor (const layer of layers) {\n\t\tif (typeof layer !== \"object\") {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (const [field, value] of Object.entries(layer)) {\n\t\t\tif (!(field in effective)) {\n\t\t\t\teffective[field] = value;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn effective as Override;\n}\n\nfunction resolveEntries<\n\tEntry extends { readonly redacted?: RedactionLayer<Override> },\n\tOverride extends object,\n>(inputs: {\n\treadonly collection: Readonly<Record<string, Entry>>;\n\treadonly environmentForKind: RedactionLayer<Override>;\n\treadonly envResource: EnvironmentResourceLayer<Override> | undefined;\n}): ReadonlyArray<ResolvedEntry<Entry, Override>> {\n\tconst { collection, environmentForKind, envResource } = inputs;\n\treturn Object.entries(collection).map(([key, entry]) => {\n\t\treturn {\n\t\t\tkey,\n\t\t\tentry,\n\t\t\toverride: resolveEffectiveOverride<Override>([\n\t\t\t\tenvResource?.[key],\n\t\t\t\tentry.redacted,\n\t\t\t\tenvironmentForKind,\n\t\t\t]),\n\t\t};\n\t});\n}\n\nfunction redactCollection<\n\tEntry extends { readonly redacted?: RedactionLayer<Override> },\n\tOverride extends object,\n>(inputs: RedactCollectionInputs<Entry, Override>): Readonly<Record<string, Entry>> | undefined {\n\tconst { collection, environmentForKind, envResource, redact } = inputs;\n\tif (collection === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst resolved = resolveEntries<Entry, Override>({\n\t\tcollection,\n\t\tenvironmentForKind,\n\t\tenvResource,\n\t});\n\n\tif (resolved.every((item) => item.override === undefined)) {\n\t\treturn collection;\n\t}\n\n\treturn Object.fromEntries(\n\t\tresolved.map((item) => {\n\t\t\treturn item.override === undefined\n\t\t\t\t? ([item.key, item.entry] as const)\n\t\t\t\t: ([\n\t\t\t\t\t\titem.key,\n\t\t\t\t\t\tredact({ key: item.key, entry: item.entry, override: item.override }),\n\t\t\t\t\t] as const);\n\t\t}),\n\t);\n}\n\nfunction redactPass(entry: GamePassEntry, override: RedactedGamePassOverride): GamePassEntry {\n\treturn {\n\t\t...entry,\n\t\tname: override.name ?? REDACTED_PASS_NAME,\n\t\tdescription: override.description ?? REDACTED_DESCRIPTION,\n\t\ticon: override.icon ?? { \"en-us\": REDACTED_ICON_PATH },\n\t\t...(entry.price === undefined ? {} : { price: override.price ?? REDACTED_PRICE }),\n\t};\n}\n\nfunction redactPasses(\n\tinputs: RedactKindInputs<GamePassEntry, RedactedGamePassOverride>,\n): ResolvedConfig[\"passes\"] {\n\tconst { collection, envLevel, envResource } = inputs;\n\treturn redactCollection<GamePassEntry, RedactedGamePassOverride>({\n\t\tcollection,\n\t\tenvironmentForKind: pickEnvironmentFields(envLevel, PASS_PRODUCT_ENV_FIELDS),\n\t\tenvResource,\n\t\tredact: (item) => redactPass(item.entry, item.override),\n\t});\n}\n\nfunction redactPlace(\n\tentry: ResolvedPlaceEntry,\n\toverride: RedactedPlaceOverride,\n): ResolvedPlaceEntry {\n\treturn {\n\t\t...entry,\n\t\tdescription: override.description ?? REDACTED_DESCRIPTION,\n\t\tdisplayName: override.displayName ?? entry.displayName,\n\t};\n}\n\nfunction redactPlaces(\n\tinputs: RedactKindInputs<ResolvedPlaceEntry, RedactedPlaceOverride>,\n): ResolvedConfig[\"places\"] {\n\tconst { collection, envLevel, envResource } = inputs;\n\treturn redactCollection<ResolvedPlaceEntry, RedactedPlaceOverride>({\n\t\tcollection,\n\t\tenvironmentForKind: pickEnvironmentFields(envLevel, PLACE_ENV_FIELDS),\n\t\tenvResource,\n\t\tredact: (item) => redactPlace(item.entry, item.override),\n\t});\n}\n\nfunction redactProduct(inputs: ProductRedactionInputs): DeveloperProductEntry {\n\tconst { key, entry, override } = inputs;\n\treturn {\n\t\t...entry,\n\t\tname: override.name ?? defaultRedactedProductName(key),\n\t\tdescription: override.description ?? REDACTED_DESCRIPTION,\n\t\ticon: override.icon ?? { \"en-us\": REDACTED_ICON_PATH },\n\t\t...(entry.price === undefined ? {} : { price: override.price ?? REDACTED_PRICE }),\n\t};\n}\n\nfunction redactProducts(\n\tinputs: RedactKindInputs<DeveloperProductEntry, RedactedDeveloperProductOverride>,\n): ResolvedConfig[\"products\"] {\n\tconst { collection, envLevel, envResource } = inputs;\n\treturn redactCollection<DeveloperProductEntry, RedactedDeveloperProductOverride>({\n\t\tcollection,\n\t\tenvironmentForKind: pickEnvironmentFields(envLevel, PASS_PRODUCT_ENV_FIELDS),\n\t\tenvResource,\n\t\tredact: redactProduct,\n\t});\n}\n\nfunction passHasRealValueEdits(entry: GamePassEntry): boolean {\n\treturn (\n\t\tentry.name !== REDACTED_PASS_NAME ||\n\t\tentry.description !== REDACTED_DESCRIPTION ||\n\t\tentry.icon[\"en-us\"] !== REDACTED_ICON_PATH ||\n\t\t(entry.price !== undefined && entry.price !== REDACTED_PRICE)\n\t);\n}\n\nfunction productHasRealValueEdits(key: string, entry: DeveloperProductEntry): boolean {\n\t// A redacted product's `name` is a placeholder when it equals either the\n\t// suffixed default for this key (what `applyRedaction` synthesizes) or\n\t// the bare `REDACTED_PRODUCT_NAME` constant (what an author may have\n\t// hand-typed). Any other value, including `Hidden Product Deluxe` or a\n\t// suffix that doesn't match this key's hash, is treated as a real edit.\n\tconst isPlaceholderName =\n\t\tentry.name === defaultRedactedProductName(key) || entry.name === REDACTED_PRODUCT_NAME;\n\treturn (\n\t\t!isPlaceholderName ||\n\t\tentry.description !== REDACTED_DESCRIPTION ||\n\t\t(entry.icon !== undefined && entry.icon[\"en-us\"] !== REDACTED_ICON_PATH) ||\n\t\t(entry.price !== undefined && entry.price !== REDACTED_PRICE)\n\t);\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { defu } from \"defu\";\n\nimport { renderDisplayNamePrefix } from \"./display-name-prefix.ts\";\nimport { applyRedaction, type EnvironmentResourceRedaction } from \"./redact-resources.ts\";\nimport type {\n\tConfig,\n\tDeveloperProductEntry,\n\tEnvironmentEntry,\n\tGamePassEntry,\n\tResolvedConfig,\n\tResolvedPlaceEntry,\n\tResolvedUniverseEntry,\n\tUniverseEntry,\n\tWithoutKey,\n} from \"./schema.ts\";\n\n/**\n * Failure surfaced when `selectEnvironment` is asked for an environment\n * name that is not a key of `config.environments`. Carries the list of\n * declared names so callers can render a \"did you mean?\" hint or a\n * close-match suggestion.\n */\nexport interface UnknownEnvironmentError {\n\t/** Environment names that the config actually declared. */\n\treadonly declared: ReadonlyArray<string>;\n\t/** Environment name the caller asked for. */\n\treadonly environment: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"unknownEnvironment\";\n}\n\n/**\n * Failure surfaced when a merged place entry is missing a required field.\n * Two paths reach this error: a root place declared without a matching\n * per-environment overlay supplying `placeId`, and an overlay-only place\n * declared under `environments.X.places` with no matching root entry to\n * supply `filePath`. Surfacing both at the resolution boundary attributes\n * the missing field to the offending entry's key instead of letting\n * `buildDesired` crash with a generic `fileReadFailed` later on.\n */\nexport interface IncompletePlaceEntryError {\n\t/** ResourceKey of the place entry that is missing a required field. */\n\treadonly key: string;\n\t/** Environment whose overlay was projected onto the config. */\n\treadonly environment: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"incompletePlaceEntry\";\n\t/** Field that the merged entry lacks. */\n\treadonly missingField: \"filePath\" | \"placeId\";\n}\n\n/**\n * Failure surfaced when a merged `universe` block lacks `universeId`.\n * The schema-level XOR rule normally prevents this by requiring\n * `universeId` either at the root or on every per-environment overlay;\n * this error remains as a typed safety net for callers that bypass\n * `validateConfig` and hand a `Config` to `selectEnvironment` directly.\n */\nexport interface IncompleteUniverseEntryError {\n\t/** Environment whose overlay was projected onto the config. */\n\treadonly environment: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"incompleteUniverseEntry\";\n\t/** Field that the merged entry lacks. V1 only surfaces `\"universeId\"`. */\n\treadonly missingField: \"universeId\";\n}\n\n/**\n * Failure surfaced when a merged `passes` entry is missing a required\n * field. The most common path here is an overlay-only pass declared\n * under `environments.X.passes` with no matching root entry: the overlay\n * shape is `Partial<GamePassEntry>`, so a typo on the ResourceKey\n * silently produces an incomplete entry that would otherwise be filled\n * in by `applyRedaction` (when `redacted: true` is set) and pushed as a\n * phantom placeholder pass. Surfacing the missing field at the\n * resolution boundary keeps that case attributable instead of letting\n * normalize fail later with a less specific error.\n */\nexport interface IncompletePassEntryError {\n\t/** ResourceKey of the pass entry that is missing a required field. */\n\treadonly key: string;\n\t/** Environment whose overlay was projected onto the config. */\n\treadonly environment: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"incompletePassEntry\";\n\t/** Field that the merged entry lacks. */\n\treadonly missingField: \"description\" | \"icon\" | \"name\";\n}\n\n/** Failure modes returned by {@link selectEnvironment}. */\nexport type SelectEnvironmentError =\n\t| IncompletePassEntryError\n\t| IncompletePlaceEntryError\n\t| IncompleteUniverseEntryError\n\t| UnknownEnvironmentError;\n\n/** Successful return shape for {@link selectMergedEnvironment}. */\ninterface MergedEnvironment {\n\t/** Per-environment entry that the merge projected onto the root config. */\n\treadonly entry: EnvironmentEntry;\n\t/**\n\t * Post-merge, pre-redaction `ResolvedConfig`. Resources that opt into\n\t * redaction still carry their real `name`, `description`, and `icon`\n\t * values, so callers can inspect divergence from placeholder defaults\n\t * before {@link selectEnvironment} substitutes them.\n\t */\n\treadonly merged: ResolvedConfig;\n}\n\n/**\n * Project a `Config` onto a single environment up to the pre-redaction\n * merge boundary. Looks up the env entry, deep-merges its resource overlay\n * over the root config, and runs the same pass, place, and universe\n * completeness checks {@link selectEnvironment} runs, so the returned\n * `merged` config honours the full `ResolvedConfig` contract. Real\n * `name`, `description`, and `icon` values on redacted resources stay\n * intact, letting callers inspect divergence from placeholder defaults\n * before {@link selectEnvironment} substitutes them.\n *\n * @param config - Validated project config.\n * @param environment - Environment name to project onto.\n * @returns The matched env entry plus the merged config, or any of the\n * `SelectEnvironmentError` failure modes.\n */\nexport function selectMergedEnvironment(\n\tconfig: Config,\n\tenvironment: string,\n): Result<MergedEnvironment, SelectEnvironmentError> {\n\tconst entry = config.environments[environment];\n\tif (entry === undefined) {\n\t\treturn { err: unknownEnvironment(config, environment), success: false };\n\t}\n\n\tconst merged = mergeOverlays(config, entry);\n\tconst incompletePass = findIncompletePass(merged, environment);\n\tif (incompletePass !== undefined) {\n\t\treturn { err: incompletePass, success: false };\n\t}\n\n\tconst incompletePlace = findIncompletePlace(merged, environment);\n\tif (incompletePlace !== undefined) {\n\t\treturn { err: incompletePlace, success: false };\n\t}\n\n\tconst incompleteUniverse = findIncompleteUniverse(merged, environment);\n\tif (incompleteUniverse !== undefined) {\n\t\treturn { err: incompleteUniverse, success: false };\n\t}\n\n\treturn { data: { entry, merged }, success: true };\n}\n\n/**\n * Build the per-resource env-overlay redaction layer that `applyRedaction`\n * and `collectRedactionAnnotations` consume. Reads each redactable kind off\n * the environment entry and projects every entry's `redacted` field into\n * the layer; omits kinds the env entry does not declare.\n *\n * @param entry - Environment entry whose overlay redaction values to extract.\n * @returns A `EnvironmentResourceRedaction` ready to pass downstream.\n */\nexport function extractResourceRedaction(entry: EnvironmentEntry): EnvironmentResourceRedaction {\n\treturn {\n\t\t...(entry.passes ? { passes: extractRedactionLayer(entry.passes) } : {}),\n\t\t...(entry.places ? { places: extractRedactionLayer(entry.places) } : {}),\n\t\t...(entry.products ? { products: extractRedactionLayer(entry.products) } : {}),\n\t};\n}\n\n/**\n * Project a validated `Config` onto a single environment. Looks up the\n * matching `environments[environment]` entry, deep-merges its resource\n * overlay (`passes`, `places`, `universe`) over the root config via defu,\n * and applies the env-level state override when present (the env entry's\n * `state` field wins; otherwise the root `state` flows through).\n *\n * Pure: no I/O. Returns a `ResolvedConfig` ready to feed into downstream\n * functions (`flattenConfig`, `buildDefaultRegistry`, `resolveStateConfig`).\n * The post-merge view promotes `places` from `Record<string, PlaceEntry>`\n * (root: file-paths only) to `Record<string, ResolvedPlaceEntry>` (root +\n * overlay merged). `environments` and `extends` are passed through\n * unchanged because they preserve the shape relationship to `Config`;\n * downstream consumers do not read them post-merge.\n *\n * Defu's merge semantics are deliberate: keyed-map collections merge by\n * key (so a place declared in both root and overlay produces a single\n * entry whose overlay-supplied fields win), and `null` / `undefined` in\n * the overlay are skipped (so the overlay never deletes a root field).\n * State has its own resolution path (a single replacement, not a\n * deep-merge) because it is a tagged union: a deep-merge of\n * `{ backend: \"s3\" }` over `{ backend: \"gist\", gistId }` would produce\n * a malformed `{ backend: \"s3\", gistId }`.\n *\n * State is left absent when neither the env override nor the root block\n * provides one. Callers that require a resolved `StateConfig` should\n * route through `resolveStateConfig` or `buildStatePort`; the absent\n * case surfaces as a typed `stateNotConfigured` there.\n *\n * Limitation in v1: a per-environment universe overlay that introduces a\n * brand-new universe block may still have optional fields missing, since\n * the overlay type only requires the identity-bearing key. The resolver\n * surfaces the entry as-is; the universe driver reports the missing\n * field when it tries to consume the entry. Universe is a singleton with\n * 20+ optional fields, so the same `incompletePlaceEntry`-style validation\n * is deferred to a separate follow-up.\n *\n * When the project sets a `displayNamePrefix` (or omits it, in which case\n * prefixing defaults to enabled) and the chosen environment declares a\n * non-empty `label`, the resolver renders the configured template via\n * `renderDisplayNamePrefix` and prepends the result to `universe.displayName`\n * and every declared place `displayName`. An undeclared `displayName`, an\n * empty/absent label, or an explicit `displayNamePrefix.enabled: false` all\n * skip prefixing for the affected fields.\n *\n * @example\n *\n * ```ts\n * import { selectEnvironment } from \"@bedrock-rbx/core\";\n * import type { Config } from \"@bedrock-rbx/core/config\";\n *\n * const config: Config = {\n * environments: {\n * production: { universe: { universeId: \"999\" } },\n * },\n * state: { backend: \"gist\", gistId: \"abc123\" },\n * universe: { voiceChatEnabled: true },\n * };\n *\n * const result = selectEnvironment(config, \"production\");\n *\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.universe?.universeId).toBe(\"999\");\n * expect(result.data.universe?.voiceChatEnabled).toBeTrue();\n * expect(result.data.state?.backend).toBe(\"gist\");\n * }\n * ```\n *\n * @param config - Validated project config carrying at least one\n * environment under `environments`.\n * @param environment - Environment name to project onto. Must be a key\n * of `config.environments`.\n * @returns `Ok(ResolvedConfig)` with the merged resource fields and the\n * resolved state, or `Err(SelectEnvironmentError)` describing why the\n * projection failed.\n */\nexport function selectEnvironment(\n\tconfig: Config,\n\tenvironment: string,\n): Result<ResolvedConfig, SelectEnvironmentError> {\n\tconst mergedResult = selectMergedEnvironment(config, environment);\n\tif (!mergedResult.success) {\n\t\treturn mergedResult;\n\t}\n\n\tconst { entry, merged } = mergedResult.data;\n\treturn { data: redactAndPrefix({ config, entry, merged }), success: true };\n}\n\nfunction findIncompletePass(\n\tmerged: ResolvedConfig,\n\tenvironment: string,\n): IncompletePassEntryError | undefined {\n\tconst { passes } = merged;\n\tif (passes === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst candidates: Record<string, Partial<GamePassEntry>> = passes;\n\tfor (const [key, entry] of Object.entries(candidates)) {\n\t\tif (entry.name === undefined) {\n\t\t\treturn { key, environment, kind: \"incompletePassEntry\", missingField: \"name\" };\n\t\t}\n\n\t\tif (entry.description === undefined) {\n\t\t\treturn {\n\t\t\t\tkey,\n\t\t\t\tenvironment,\n\t\t\t\tkind: \"incompletePassEntry\",\n\t\t\t\tmissingField: \"description\",\n\t\t\t};\n\t\t}\n\n\t\tif (entry.icon === undefined) {\n\t\t\treturn { key, environment, kind: \"incompletePassEntry\", missingField: \"icon\" };\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction mergeEntry<Resolved extends object>(\n\toverlay: Partial<Resolved>,\n\tbase: Partial<Resolved> | undefined,\n): Resolved {\n\t// Precondition for the cast: every public success path of\n\t// `selectEnvironment` MUST run completeness validation (today only\n\t// `findIncompletePlace`) before exposing the merged record to a caller.\n\t// The cast trades compile-time soundness for the freedom to surface\n\t// partial entries to a validator that can attribute the missing field to\n\t// a typed error. New resource kinds that adopt this merge pattern owe\n\t// their own `findIncomplete<Kind>Entry` validator before returning.\n\t//\n\t// defu treats `undefined` as the empty object, so an overlay-only entry\n\t// (no matching root) flows through unchanged. defu's return type is\n\t// `MergeObjects<Partial<Resolved>, Partial<Resolved>>` which the compiler\n\t// cannot prove equals `Resolved`.\n\treturn defu(overlay, base ?? {}) as Resolved;\n}\n\nfunction mergeKeyedRecord<Resolved extends object>(\n\toverlay: Record<string, Partial<Resolved>> | undefined,\n\tbase: Record<string, Partial<Resolved>> | undefined,\n): Record<string, Resolved> | undefined {\n\tif (overlay === undefined) {\n\t\t// Same precondition as `mergeEntry`: passing the base record straight\n\t\t// through is sound only when the caller validates completeness on the\n\t\t// returned record before publishing it.\n\t\treturn base as Record<string, Resolved> | undefined;\n\t}\n\n\treturn {\n\t\t...((base ?? {}) as Record<string, Resolved>),\n\t\t...Object.fromEntries(\n\t\t\tObject.entries(overlay).map(([key, partial]) => {\n\t\t\t\treturn [key, mergeEntry<Resolved>(partial, base?.[key])];\n\t\t\t}),\n\t\t),\n\t};\n}\n\nfunction mergeUniverse(\n\toverlay: Partial<UniverseEntry> | undefined,\n\tbase: undefined | UniverseEntry,\n): ResolvedUniverseEntry | undefined {\n\tif (overlay === undefined && base === undefined) {\n\t\treturn undefined;\n\t}\n\n\t// Precondition for the cast: see `mergeEntry`. The schema-level XOR rule\n\t// guarantees a present `universeId` post-merge whenever the result is\n\t// non-empty, and `findIncompleteUniverse` re-verifies the invariant on\n\t// the success path. The `defu` call type-resolves to a wider partial\n\t// because both sides declare `universeId` as optional; the cast collapses\n\t// it to the resolved shape.\n\treturn defu(overlay ?? {}, base ?? {}) as ResolvedUniverseEntry;\n}\n\nfunction stripRedacted<T extends { readonly redacted?: unknown }>(\n\toverlay: Readonly<Record<string, T>> | undefined,\n): Record<string, WithoutKey<T, \"redacted\">> | undefined {\n\tif (overlay === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn Object.fromEntries(\n\t\tObject.entries(overlay).map(([key, entryValue]) => {\n\t\t\tconst { redacted: _redacted, ...rest } = entryValue;\n\t\t\treturn [key, rest];\n\t\t}),\n\t);\n}\n\nfunction mergeOverlays(config: Config, entry: EnvironmentEntry): ResolvedConfig {\n\tconst passes = mergeKeyedRecord<GamePassEntry>(stripRedacted(entry.passes), config.passes);\n\tconst places = mergeKeyedRecord<ResolvedPlaceEntry>(stripRedacted(entry.places), config.places);\n\tconst products = mergeKeyedRecord<DeveloperProductEntry>(\n\t\tstripRedacted(entry.products),\n\t\tconfig.products,\n\t);\n\tconst universe = mergeUniverse(entry.universe, config.universe);\n\tconst state = entry.state ?? config.state;\n\n\tconst {\n\t\tplaces: _placesRoot,\n\t\tproducts: _productsRoot,\n\t\tuniverse: _universeRoot,\n\t\t...rest\n\t} = config;\n\n\treturn {\n\t\t...rest,\n\t\t...(passes === undefined ? {} : { passes }),\n\t\t...(places === undefined ? {} : { places }),\n\t\t...(products === undefined ? {} : { products }),\n\t\t...(state === undefined ? {} : { state }),\n\t\t...(universe === undefined ? {} : { universe }),\n\t};\n}\n\nfunction unknownEnvironment(config: Config, environment: string): UnknownEnvironmentError {\n\treturn {\n\t\tdeclared: Object.keys(config.environments),\n\t\tenvironment,\n\t\tkind: \"unknownEnvironment\",\n\t};\n}\n\nfunction findIncompleteUniverse(\n\tprojected: ResolvedConfig,\n\tenvironment: string,\n): IncompleteUniverseEntryError | undefined {\n\tconst { universe } = projected;\n\tif (universe === undefined) {\n\t\treturn undefined;\n\t}\n\n\t// `universe` is typed as `ResolvedUniverseEntry` (universeId required)\n\t// because the merge boundary already promised completeness; this routine\n\t// exists to honour that promise at runtime, so it widens the view back to\n\t// `Partial<ResolvedUniverseEntry>` for the duration of the check.\n\tconst candidate: Partial<ResolvedUniverseEntry> = universe;\n\tif (candidate.universeId === undefined) {\n\t\treturn { environment, kind: \"incompleteUniverseEntry\", missingField: \"universeId\" };\n\t}\n\n\treturn undefined;\n}\n\nfunction findIncompletePlace(\n\tprojected: ResolvedConfig,\n\tenvironment: string,\n): IncompletePlaceEntryError | undefined {\n\tconst { places } = projected;\n\tif (places === undefined) {\n\t\treturn undefined;\n\t}\n\n\t// `places` is typed as `Record<string, ResolvedPlaceEntry>` because the\n\t// merge boundary already promised completeness; this routine exists to\n\t// honour that promise at runtime, so it widens the view back to\n\t// `Partial<ResolvedPlaceEntry>` for the duration of the check.\n\tconst candidates: Record<string, Partial<ResolvedPlaceEntry>> = places;\n\tfor (const [key, entry] of Object.entries(candidates)) {\n\t\tif (entry.placeId === undefined) {\n\t\t\treturn {\n\t\t\t\tkey,\n\t\t\t\tenvironment,\n\t\t\t\tkind: \"incompletePlaceEntry\",\n\t\t\t\tmissingField: \"placeId\",\n\t\t\t};\n\t\t}\n\n\t\tif (entry.filePath === undefined) {\n\t\t\treturn {\n\t\t\t\tkey,\n\t\t\t\tenvironment,\n\t\t\t\tkind: \"incompletePlaceEntry\",\n\t\t\t\tmissingField: \"filePath\",\n\t\t\t};\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction extractRedactionLayer<Value>(\n\toverlay: Readonly<Record<string, { readonly redacted?: Value }>>,\n): Readonly<Record<string, Value>> {\n\tconst layer: Record<string, Value> = {};\n\tfor (const [key, entryValue] of Object.entries(overlay)) {\n\t\tif (entryValue.redacted !== undefined) {\n\t\t\tlayer[key] = entryValue.redacted;\n\t\t}\n\t}\n\n\treturn layer;\n}\n\nfunction resolvePrefix(config: Config, entry: EnvironmentEntry): string | undefined {\n\tif (config.displayNamePrefix?.enabled === false) {\n\t\treturn undefined;\n\t}\n\n\tconst { label } = entry;\n\tif (label === undefined || label === \"\") {\n\t\treturn undefined;\n\t}\n\n\treturn renderDisplayNamePrefix(label, config.displayNamePrefix?.format);\n}\n\nfunction applyUniversePrefix(\n\tuniverse: ResolvedUniverseEntry | undefined,\n\tprefix: string | undefined,\n): ResolvedUniverseEntry | undefined {\n\tif (universe === undefined || prefix === undefined || universe.displayName === undefined) {\n\t\treturn universe;\n\t}\n\n\treturn { ...universe, displayName: prefix + universe.displayName };\n}\n\nfunction applyPlacesPrefix(\n\tplaces: Record<string, ResolvedPlaceEntry> | undefined,\n\tprefix: string | undefined,\n): Record<string, ResolvedPlaceEntry> | undefined {\n\tif (places === undefined || prefix === undefined) {\n\t\treturn places;\n\t}\n\n\treturn Object.fromEntries(\n\t\tObject.entries(places).map(([key, place]) => {\n\t\t\tif (place.displayName === undefined) {\n\t\t\t\treturn [key, place];\n\t\t\t}\n\n\t\t\treturn [key, { ...place, displayName: prefix + place.displayName }];\n\t\t}),\n\t);\n}\n\nfunction redactAndPrefix(inputs: {\n\treadonly config: Config;\n\treadonly entry: EnvironmentEntry;\n\treadonly merged: ResolvedConfig;\n}): ResolvedConfig {\n\tconst { config, entry, merged } = inputs;\n\tconst redacted = applyRedaction(merged, {\n\t\tenvLevel: entry.redacted,\n\t\tenvResource: extractResourceRedaction(entry),\n\t});\n\tconst prefix = resolvePrefix(config, entry);\n\tconst places = applyPlacesPrefix(redacted.places, prefix);\n\tconst universe = applyUniversePrefix(redacted.universe, prefix);\n\n\treturn {\n\t\t...redacted,\n\t\t...(places === undefined ? {} : { places }),\n\t\t...(universe === undefined ? {} : { universe }),\n\t};\n}\n","/* eslint-disable max-lines -- exhaustive per-ResourceKind dispatch (applyOne, dispatchByKind, createSucceededEvent) plus progress emission helpers keep the apply pipeline cohesive in one module; splitting would scatter related code without improving navigation. */\nimport { ApiError, type OpenCloudError, type Result } from \"@bedrock-rbx/ocale\";\n\nimport type { Operation } from \"../core/operations.ts\";\nimport type {\n\tDeveloperProductDesiredState,\n\tGamePassDesiredState,\n\tPlaceDesiredState,\n\tResourceCurrentState,\n\tResourceDesiredState,\n\tResourceKind,\n\tUniverseDesiredState,\n} from \"../core/resources.ts\";\nimport type { ProgressEvent, ProgressPort } from \"../ports/progress-port.ts\";\nimport type { DriverRegistry, ResourceDriver } from \"../ports/resource-driver.ts\";\nimport type { ResourceKey } from \"../types/ids.ts\";\n\n/**\n * Optional wiring `applyOps` uses to emit per-resource and aggregate progress\n * events. When omitted, `applyOps` runs silently (backward-compatible with\n * pre-progress callers).\n */\nexport interface ApplyOpsReporting {\n\t/** Environment name stamped on every emitted event. */\n\treadonly environment: string;\n\t/** Sink the apply pipeline pushes events into. */\n\treadonly progress: ProgressPort;\n}\n\n/**\n * Failure surfaced by `applyOps` when an operation cannot be applied.\n * Plain-data discriminated union; narrow on `kind`, do not `instanceof` it.\n * One `ApplyError` describes one failing op; the surrounding\n * `AggregateApplyError` carries the full batch outcome (every survivor and\n * every failure).\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, type ApplyError } from \"@bedrock-rbx/core\";\n *\n * function describe(err: ApplyError): string {\n * switch (err.kind) {\n * case \"driverFailure\": {\n * return `driver failed for ${err.key}: ${err.cause.message}`;\n * }\n * case \"unexpectedThrow\": {\n * return `unexpected error for ${err.key}`;\n * }\n * case \"updateUnsupported\": {\n * return `update not supported for ${err.key}`;\n * }\n * }\n * }\n *\n * const err: ApplyError = {\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"updateUnsupported\",\n * };\n *\n * expect(describe(err)).toBe(\"update not supported for vip-pass\");\n * ```\n */\nexport type ApplyError =\n\t| {\n\t\t\treadonly cause: OpenCloudError;\n\t\t\treadonly key: ResourceKey;\n\t\t\treadonly kind: \"driverFailure\";\n\t }\n\t| {\n\t\t\treadonly cause: unknown;\n\t\t\treadonly key: ResourceKey;\n\t\t\treadonly kind: \"unexpectedThrow\";\n\t }\n\t| {\n\t\t\treadonly key: ResourceKey;\n\t\t\treadonly kind: \"updateUnsupported\";\n\t };\n\n/**\n * Aggregate outcome returned by `applyOps` when one or more ops fail.\n * `applied` is the survivor set in Phase 1 then Phase 2 input order.\n * `failures` is the non-empty list of `ApplyError`s, one per failing op,\n * grouped the same way.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, type AggregateApplyError } from \"@bedrock-rbx/core\";\n *\n * function summarize(err: AggregateApplyError): string {\n * return `${err.applied.length} survived, ${err.failures.length} failed`;\n * }\n *\n * const err: AggregateApplyError = {\n * applied: [],\n * failures: [{ key: asResourceKey(\"vip-pass\"), kind: \"updateUnsupported\" }],\n * };\n *\n * expect(summarize(err)).toBe(\"0 survived, 1 failed\");\n * ```\n */\nexport interface AggregateApplyError {\n\t/** Survivors persisted to state, in Phase 1 then Phase 2 input order. */\n\treadonly applied: ReadonlyArray<ResourceCurrentState>;\n\t/** Per-op failures, at least one, in Phase 1 then Phase 2 input order. */\n\treadonly failures: readonly [ApplyError, ...ReadonlyArray<ApplyError>];\n}\n\ntype NonNoopOp = Exclude<Operation, { readonly type: \"noop\" }>;\ntype DeveloperProductOp = NonNoopOp & { readonly desired: DeveloperProductDesiredState };\ntype GamePassOp = NonNoopOp & { readonly desired: GamePassDesiredState };\ntype PlaceOp = NonNoopOp & { readonly desired: PlaceDesiredState };\ntype UniverseOp = NonNoopOp & { readonly desired: UniverseDesiredState };\n\ninterface OutcomePair {\n\treadonly op: NonNoopOp;\n\treadonly outcome: Result<ResourceCurrentState, ApplyError>;\n}\n\ninterface DispatchInPhasesInput {\n\treadonly phase1: ReadonlyArray<NonNoopOp>;\n\treadonly phase2: ReadonlyArray<NonNoopOp>;\n\treadonly registry: DriverRegistry;\n\treadonly reporting: ApplyOpsReporting | undefined;\n}\n\ninterface ApplySummaryInput {\n\treadonly end: number;\n\treadonly failures: ReadonlyArray<ApplyError>;\n\treadonly noopCount: number;\n\treadonly pairs: ReadonlyArray<OutcomePair>;\n\treadonly reporting: ApplyOpsReporting | undefined;\n\treadonly start: number;\n}\n\ninterface CreateSucceededInput {\n\treadonly key: ResourceKey;\n\treadonly environment: string;\n\treadonly state: ResourceCurrentState;\n}\n\ninterface TerminalEventInput {\n\treadonly environment: string;\n\treadonly op: NonNoopOp;\n\treadonly outcome: Result<ResourceCurrentState, ApplyError>;\n}\n\ninterface ReportAndDispatchInput {\n\treadonly op: NonNoopOp;\n\treadonly registry: DriverRegistry;\n\treadonly reporting: ApplyOpsReporting | undefined;\n}\n\n/**\n * Dispatch reconciliation operations to their matching drivers in two phases\n * with continue-on-failure semantics. Phase 1 runs universe ops sequentially\n * (singleton per environment; sequencing it before everything else avoids the\n * `displayName` race against the root `Place`). Phase 2 dispatches every\n * remaining non-noop op concurrently via `Promise.all`; every op is\n * attempted regardless of earlier failures.\n *\n * Behaviour:\n * - `create` operations route to `registry[op.desired.kind].create`.\n * - `update` operations route to `registry[op.desired.kind].update` when the\n * driver exposes it; otherwise they yield an `updateUnsupported`\n * `ApplyError` without invoking the driver.\n * - `noop` operations are skipped entirely (no I/O, no dispatch).\n * - A driver that throws outside its `Result` contract is caught at the\n * dispatch boundary and translated to an `unexpectedThrow` `ApplyError`\n * scoped to that op alone; the rest of the batch keeps running.\n *\n * On Ok the returned array carries driver outputs for every non-noop op\n * in phase order: Phase 1 universe entries first, then Phase 2 entries in\n * their input order. Noops are not represented; callers needing a full\n * post-apply snapshot merge with the pre-apply current state keyed by\n * `ResourceKey`.\n *\n * On Err the aggregate carries every survivor in `applied` (Phase 1 first,\n * then Phase 2 input order) and every failure in `failures` with the same\n * grouping. Neither array reflects completion order.\n *\n * @param ops - Reconciliation operations produced by `diff`, applied in\n * declaration order.\n * @param registry - Per-kind driver table; dispatch uses `op.desired.kind`\n * as the index.\n * @param reporting - Optional progress wiring. When supplied, `applyOps`\n * emits one `resourceOpStarted` and one terminal event per non-noop op,\n * one `resourceOpNoop` per noop op, and a final `applySummary` carrying\n * the per-type counts and the wall-clock apply duration. When omitted,\n * no events fire.\n * @returns `Ok(state)` when every op succeeded; otherwise\n * `Err(AggregateApplyError)` with the survivors and the non-empty\n * failures tuple.\n * @example\n *\n * ```ts\n * import { applyOps, type DriverRegistry } from \"@bedrock-rbx/core\";\n *\n * const noopRegistry: DriverRegistry = {\n * developerProduct: { create: async () => ({ err: new Error(\"stub\") as never, success: false }) },\n * gamePass: { create: async () => ({ err: new Error(\"stub\") as never, success: false }) },\n * place: { create: async () => ({ err: new Error(\"stub\") as never, success: false }) },\n * universe: { create: async () => ({ err: new Error(\"stub\") as never, success: false }) },\n * };\n *\n * return applyOps([], noopRegistry).then((result) => {\n * expect(result).toStrictEqual({ data: [], success: true });\n * });\n * ```\n */\n// eslint-disable-next-line better-max-params/better-max-params -- additive optional progress hook on the established two-positional-deps shape; folding into an options bag would break every caller for no semantic gain.\nexport async function applyOps(\n\tops: ReadonlyArray<Operation>,\n\tregistry: DriverRegistry,\n\treporting?: ApplyOpsReporting,\n): Promise<Result<ReadonlyArray<ResourceCurrentState>, AggregateApplyError>> {\n\tconst start = Date.now();\n\tconst { noopCount, phase1, phase2 } = partitionAndEmitNoops(ops, reporting);\n\tconst pairs = await dispatchInPhases({ phase1, phase2, registry, reporting });\n\tconst end = Date.now();\n\n\tconst { applied, failures } = partitionOutcomes(pairs.map((pair) => pair.outcome));\n\temitApplySummary({ end, failures, noopCount, pairs, reporting, start });\n\n\tconst [head, ...tail] = failures;\n\tif (head === undefined) {\n\t\treturn { data: applied, success: true };\n\t}\n\n\treturn { err: { applied, failures: [head, ...tail] }, success: false };\n}\n\nfunction driverFailure(\n\tkey: ResourceKey,\n\tcause: OpenCloudError,\n): Result<ResourceCurrentState, ApplyError> {\n\treturn { err: { key, cause, kind: \"driverFailure\" }, success: false };\n}\n\nfunction kindMismatch(key: ResourceKey, mismatch: { actual: string; expected: string }): ApiError {\n\treturn new ApiError(\n\t\t`internal: operation kind mismatch for ${key}: expected ${mismatch.expected}, got ${mismatch.actual}`,\n\t\t{ statusCode: 0 },\n\t);\n}\n\nasync function applyOne<K extends ResourceKind>(\n\top: NonNoopOp & { readonly desired: Extract<ResourceDesiredState, { kind: K }> },\n\tdriver: ResourceDriver<K>,\n): Promise<Result<ResourceCurrentState, ApplyError>> {\n\tif (op.type === \"create\") {\n\t\tconst created = await driver.create(op.desired);\n\t\treturn created.success ? created : driverFailure(op.key, created.err);\n\t}\n\n\tif (driver.update === undefined) {\n\t\treturn { err: { key: op.key, kind: \"updateUnsupported\" }, success: false };\n\t}\n\n\tif (op.current.kind !== op.desired.kind) {\n\t\treturn driverFailure(\n\t\t\top.key,\n\t\t\tkindMismatch(op.key, { actual: op.current.kind, expected: op.desired.kind }),\n\t\t);\n\t}\n\n\tconst updated = await driver.update(op.current as ResourceCurrentState<K>, op.desired);\n\treturn updated.success ? updated : driverFailure(op.key, updated.err);\n}\n\nasync function dispatchByKind(\n\top: NonNoopOp,\n\tregistry: DriverRegistry,\n): Promise<Result<ResourceCurrentState, ApplyError>> {\n\t// Exhaustive switch: adding a new ResourceKind is a compile error here\n\t// until an arm lands. Each arm casts because custom type narrowing does\n\t// not propagate through a non-distributive union.\n\tswitch (op.desired.kind) {\n\t\tcase \"developerProduct\": {\n\t\t\treturn applyOne(op as DeveloperProductOp, registry.developerProduct);\n\t\t}\n\t\tcase \"gamePass\": {\n\t\t\treturn applyOne(op as GamePassOp, registry.gamePass);\n\t\t}\n\t\tcase \"place\": {\n\t\t\treturn applyOne(op as PlaceOp, registry.place);\n\t\t}\n\t\tcase \"universe\": {\n\t\t\treturn applyOne(op as UniverseOp, registry.universe);\n\t\t}\n\t}\n}\n\nasync function dispatchOp(\n\top: NonNoopOp,\n\tregistry: DriverRegistry,\n): Promise<Result<ResourceCurrentState, ApplyError>> {\n\ttry {\n\t\treturn await dispatchByKind(op, registry);\n\t} catch (err) {\n\t\treturn { err: { key: op.key, cause: err, kind: \"unexpectedThrow\" }, success: false };\n\t}\n}\n\n/* eslint-disable-next-line max-lines-per-function -- exhaustive per-ResourceKind switch with literal returns required for per-kind narrowing of `outputs`; consolidating would either reintroduce casts or hide the discriminator. */\nfunction createSucceededEvent(input: CreateSucceededInput): ProgressEvent {\n\tconst { key, environment, state } = input;\n\tswitch (state.kind) {\n\t\tcase \"developerProduct\": {\n\t\t\treturn {\n\t\t\t\tkey,\n\t\t\t\tenvironment,\n\t\t\t\tkind: \"resourceOpSucceeded\",\n\t\t\t\topType: \"create\",\n\t\t\t\toutputs: state.outputs,\n\t\t\t\tresourceKind: \"developerProduct\",\n\t\t\t};\n\t\t}\n\t\tcase \"gamePass\": {\n\t\t\treturn {\n\t\t\t\tkey,\n\t\t\t\tenvironment,\n\t\t\t\tkind: \"resourceOpSucceeded\",\n\t\t\t\topType: \"create\",\n\t\t\t\toutputs: state.outputs,\n\t\t\t\tresourceKind: \"gamePass\",\n\t\t\t};\n\t\t}\n\t\tcase \"place\": {\n\t\t\treturn {\n\t\t\t\tkey,\n\t\t\t\tenvironment,\n\t\t\t\tkind: \"resourceOpSucceeded\",\n\t\t\t\topType: \"create\",\n\t\t\t\toutputs: state.outputs,\n\t\t\t\tresourceKind: \"place\",\n\t\t\t};\n\t\t}\n\t\tcase \"universe\": {\n\t\t\treturn {\n\t\t\t\tkey,\n\t\t\t\tenvironment,\n\t\t\t\tkind: \"resourceOpSucceeded\",\n\t\t\t\topType: \"create\",\n\t\t\t\toutputs: state.outputs,\n\t\t\t\tresourceKind: \"universe\",\n\t\t\t};\n\t\t}\n\t}\n}\n\nfunction toTerminalEvent(input: TerminalEventInput): ProgressEvent {\n\tconst { environment, op, outcome } = input;\n\tif (!outcome.success) {\n\t\treturn {\n\t\t\tkey: op.key,\n\t\t\tenvironment,\n\t\t\terror: outcome.err,\n\t\t\tkind: \"resourceOpFailed\",\n\t\t\topType: op.type,\n\t\t\tresourceKind: op.desired.kind,\n\t\t};\n\t}\n\n\tif (op.type === \"update\") {\n\t\treturn {\n\t\t\tkey: op.key,\n\t\t\tchangedFields: op.changedFields,\n\t\t\tenvironment,\n\t\t\tkind: \"resourceOpSucceeded\",\n\t\t\topType: \"update\",\n\t\t\tresourceKind: op.desired.kind,\n\t\t};\n\t}\n\n\treturn createSucceededEvent({ key: op.key, environment, state: outcome.data });\n}\n\nasync function reportAndDispatch(input: ReportAndDispatchInput): Promise<OutcomePair> {\n\tconst { op, registry, reporting } = input;\n\tif (reporting !== undefined) {\n\t\treporting.progress.emit({\n\t\t\tkey: op.key,\n\t\t\tenvironment: reporting.environment,\n\t\t\tkind: \"resourceOpStarted\",\n\t\t\topType: op.type,\n\t\t\tresourceKind: op.desired.kind,\n\t\t});\n\t}\n\n\tconst outcome = await dispatchOp(op, registry);\n\tif (reporting !== undefined) {\n\t\treporting.progress.emit(\n\t\t\ttoTerminalEvent({ environment: reporting.environment, op, outcome }),\n\t\t);\n\t}\n\n\treturn { op, outcome };\n}\n\nasync function dispatchInPhases(input: DispatchInPhasesInput): Promise<ReadonlyArray<OutcomePair>> {\n\tconst phase1Pairs: Array<OutcomePair> = [];\n\tfor (const op of input.phase1) {\n\t\tphase1Pairs.push(\n\t\t\tawait reportAndDispatch({ op, registry: input.registry, reporting: input.reporting }),\n\t\t);\n\t}\n\n\tconst phase2Pairs = await Promise.all(\n\t\tinput.phase2.map(async (op) => {\n\t\t\treturn reportAndDispatch({ op, registry: input.registry, reporting: input.reporting });\n\t\t}),\n\t);\n\treturn [...phase1Pairs, ...phase2Pairs];\n}\n\nfunction emitApplySummary(input: ApplySummaryInput): void {\n\tif (input.reporting === undefined) {\n\t\treturn;\n\t}\n\n\tconst created = input.pairs.filter(\n\t\t(pair) => pair.outcome.success && pair.op.type === \"create\",\n\t).length;\n\tconst updated = input.pairs.filter(\n\t\t(pair) => pair.outcome.success && pair.op.type === \"update\",\n\t).length;\n\tinput.reporting.progress.emit({\n\t\tcreated,\n\t\tdurationMs: input.end - input.start,\n\t\tenvironment: input.reporting.environment,\n\t\tfailed: input.failures.length,\n\t\tkind: \"applySummary\",\n\t\tnoop: input.noopCount,\n\t\tupdated,\n\t});\n}\n\nfunction partitionOutcomes(outcomes: ReadonlyArray<Result<ResourceCurrentState, ApplyError>>): {\n\treadonly applied: ReadonlyArray<ResourceCurrentState>;\n\treadonly failures: ReadonlyArray<ApplyError>;\n} {\n\tconst applied = outcomes.flatMap((outcome) => (outcome.success ? [outcome.data] : []));\n\tconst failures = outcomes.flatMap((outcome) => (outcome.success ? [] : [outcome.err]));\n\treturn { applied, failures };\n}\n\nfunction emitNoop(\n\top: Extract<Operation, { readonly type: \"noop\" }>,\n\treporting: ApplyOpsReporting | undefined,\n): void {\n\tif (reporting === undefined) {\n\t\treturn;\n\t}\n\n\treporting.progress.emit({\n\t\tkey: op.key,\n\t\tenvironment: reporting.environment,\n\t\tkind: \"resourceOpNoop\",\n\t\tresourceKind: op.kind,\n\t});\n}\n\nfunction partitionAndEmitNoops(\n\tops: ReadonlyArray<Operation>,\n\treporting: ApplyOpsReporting | undefined,\n): {\n\treadonly noopCount: number;\n\treadonly phase1: ReadonlyArray<NonNoopOp>;\n\treadonly phase2: ReadonlyArray<NonNoopOp>;\n} {\n\tconst phase1: Array<NonNoopOp> = [];\n\tconst phase2: Array<NonNoopOp> = [];\n\tlet noopCount = 0;\n\tfor (const op of ops) {\n\t\tif (op.type === \"noop\") {\n\t\t\tnoopCount += 1;\n\t\t\temitNoop(op, reporting);\n\t\t} else if (op.desired.kind === \"universe\") {\n\t\t\tphase1.push(op);\n\t\t} else {\n\t\t\tphase2.push(op);\n\t\t}\n\t}\n\n\treturn { noopCount, phase1, phase2 };\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\nimport { DeveloperProductsClient } from \"@bedrock-rbx/ocale/developer-products\";\nimport { GamePassesClient } from \"@bedrock-rbx/ocale/game-passes\";\nimport { PlacesClient } from \"@bedrock-rbx/ocale/places\";\nimport { UniversesClient } from \"@bedrock-rbx/ocale/universes\";\n\nimport { createDeveloperProductDriver } from \"../adapters/developer-product-driver.ts\";\nimport { createGamePassDriver } from \"../adapters/game-pass-driver.ts\";\nimport { createPlaceDriver } from \"../adapters/place-driver.ts\";\nimport { createUniverseDriver } from \"../adapters/universe-driver.ts\";\nimport type { ResolvedConfig } from \"../core/schema.ts\";\nimport type { DriverRegistry } from \"../ports/resource-driver.ts\";\nimport { asRobloxAssetId } from \"../types/ids.ts\";\nimport type { MissingCredentialError } from \"./build-state-port.ts\";\n\n/**\n * Failure surfaced when default-constructing a registry needs a config\n * field that is not present. The deploy boundary wraps this in a\n * `DeployError` so the caller sees a typed Result instead of a downstream\n * driver error.\n */\nexport interface RegistryConfigError {\n\t/** Suggested fix routed back to the caller. */\n\treadonly hint: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"registryConfigMissing\";\n\t/** Which config field was missing. */\n\treadonly missing: \"universeId\";\n}\n\n/** Inputs for {@link buildDefaultRegistry}. */\ninterface BuildDefaultRegistryDeps {\n\t/** Resolved project config; supplies `universe.universeId` and is read for nothing else. */\n\treadonly config: ResolvedConfig;\n\t/** Reads an environment variable; injected so tests stay free of `process.env`. */\n\treadonly getEnv: (name: string) => string | undefined;\n\t/** Reader plumbed into kind-specific drivers that ingest file bytes. */\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n}\n\ninterface AssembleRegistryInputs {\n\treadonly apiKey: string;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\treadonly universeId: ReturnType<typeof asRobloxAssetId>;\n}\n\n/**\n * Construct the default `DriverRegistry` from `config.universe.universeId`\n * and `BEDROCK_API_KEY`. Reads the API key via the injected `getEnv` seam\n * and surfaces `missingCredential` or `registryConfigMissing` as typed\n * Results instead of throwing.\n *\n * @example\n *\n * ```ts\n * import { buildDefaultRegistry } from \"@bedrock-rbx/core\";\n *\n * const registry = buildDefaultRegistry({\n * config: {\n * environments: { production: {} },\n * state: { backend: \"gist\", gistId: \"abc\" },\n * universe: { universeId: \"1234567890\" },\n * },\n * getEnv: () => \"rbx-test\",\n * readFile: async () => new Uint8Array(),\n * });\n *\n * expect(registry.success).toBeTrue();\n * ```\n *\n * @param deps - Validated config plus credential and file-reader seams.\n * @returns A `DriverRegistry` on success, or a typed Err describing the\n * missing API key or the missing universe declaration.\n */\nexport function buildDefaultRegistry(\n\tdeps: BuildDefaultRegistryDeps,\n): Result<DriverRegistry, MissingCredentialError | RegistryConfigError> {\n\tconst apiKey = deps.getEnv(\"BEDROCK_API_KEY\");\n\tif (apiKey === undefined) {\n\t\treturn missingApiKey();\n\t}\n\n\tconst rawUniverseId = deps.config.universe?.universeId;\n\tif (rawUniverseId === undefined) {\n\t\treturn missingUniverseId();\n\t}\n\n\treturn {\n\t\tdata: assembleRegistry({\n\t\t\tapiKey,\n\t\t\treadFile: deps.readFile,\n\t\t\tuniverseId: asRobloxAssetId(rawUniverseId),\n\t\t}),\n\t\tsuccess: true,\n\t};\n}\n\nfunction missingApiKey(): Result<DriverRegistry, MissingCredentialError> {\n\treturn {\n\t\terr: {\n\t\t\tkind: \"missingCredential\",\n\t\t\tpurpose: \"registry\",\n\t\t\tvariable: \"BEDROCK_API_KEY\",\n\t\t},\n\t\tsuccess: false,\n\t};\n}\n\nfunction missingUniverseId(): Result<DriverRegistry, RegistryConfigError> {\n\treturn {\n\t\terr: {\n\t\t\thint: \"declare universe.universeId in bedrock.config.ts\",\n\t\t\tkind: \"registryConfigMissing\",\n\t\t\tmissing: \"universeId\",\n\t\t},\n\t\tsuccess: false,\n\t};\n}\n\nfunction assembleRegistry(inputs: AssembleRegistryInputs): DriverRegistry {\n\tconst { apiKey, readFile, universeId } = inputs;\n\tconst developerProducts = new DeveloperProductsClient({ apiKey });\n\tconst gamePasses = new GamePassesClient({ apiKey });\n\tconst places = new PlacesClient({ apiKey });\n\tconst universes = new UniversesClient({ apiKey });\n\n\treturn {\n\t\tdeveloperProduct: createDeveloperProductDriver({\n\t\t\tclient: developerProducts,\n\t\t\treadFile,\n\t\t\tuniverseId,\n\t\t}),\n\t\tgamePass: createGamePassDriver({ client: gamePasses, readFile, universeId }),\n\t\tplace: createPlaceDriver({ client: places, readFile, universeId }),\n\t\tuniverse: createUniverseDriver({ places, universes }),\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport type { ResourceDesiredInput } from \"../core/flatten.ts\";\nimport { defaultKindRegistry } from \"../core/kinds/index.ts\";\nimport type { BuildDesiredError, ResourceKindModule } from \"../core/kinds/module.ts\";\nimport type { ResourceDesiredState, ResourceKind } from \"../core/resources.ts\";\n\nexport type { BuildDesiredError } from \"../core/kinds/module.ts\";\n\n/**\n * Layer file I/O onto a flat tagged list of resource inputs to produce\n * `ResourceDesiredState`.\n *\n * For each input, reads the file bytes via the injected `readFile`, computes\n * the SHA-256 hex digest, and assembles the branded desired-state record\n * that `diff` consumes. Entries are processed sequentially so the first\n * failure's attribution is deterministic.\n *\n * @param inputs - Flat tagged resource inputs from `flattenConfig`.\n * @param readFile - Reads file bytes for a given path; rejection becomes a\n * `fileReadFailed` Err.\n * @returns `Ok` with the desired-state array (same length and order as\n * `inputs`), or `Err` with the first I/O failure.\n * @example\n *\n * ```ts\n * import { asResourceKey, buildDesired } from \"@bedrock-rbx/core\";\n *\n * async function readFile(): Promise<Uint8Array> {\n * return new Uint8Array([1, 2, 3]);\n * }\n *\n * return buildDesired(\n * [\n * {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * price: 500,\n * },\n * ],\n * readFile,\n * ).then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data).toHaveLength(1);\n * expect(result.data[0]!.kind).toBe(\"gamePass\");\n * }\n * });\n * ```\n */\nexport async function buildDesired(\n\tinputs: ReadonlyArray<ResourceDesiredInput>,\n\treadFile: (path: string) => Promise<Uint8Array>,\n): Promise<Result<ReadonlyArray<ResourceDesiredState>, BuildDesiredError>> {\n\tconst desired: Array<ResourceDesiredState> = [];\n\tconst io = { readFile };\n\tfor (const input of inputs) {\n\t\t// Registry index returns a union of per-kind modules; widening its\n\t\t// type parameter lets us call normalize without per-kind\n\t\t// discriminator narrowing. Safe because input.kind pins which\n\t\t// module is selected.\n\t\tconst module = defaultKindRegistry[input.kind] as ResourceKindModule<ResourceKind>;\n\t\tconst normalized = await module.normalize(input, io);\n\t\tif (!normalized.success) {\n\t\t\treturn normalized;\n\t\t}\n\n\t\tdesired.push(normalized.data);\n\t}\n\n\treturn { data: desired, success: true };\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { createGistStateAdapter, type GistFetch } from \"../adapters/gist-state-adapter.ts\";\nimport { type GistStateConfig, isGistStateConfig, type StateConfig } from \"../core/schema.ts\";\nimport type { StatePort } from \"../ports/state-port.ts\";\n\n/**\n * Failure surfaced when a default-constructed adapter cannot find a\n * required environment variable. The deploy boundary wraps this in a\n * `DeployError` so the caller sees a typed Result instead of an\n * exception or a confusing downstream HTTP error.\n */\nexport interface MissingCredentialError {\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"missingCredential\";\n\t/** Whether the credential was needed for the state backend or the driver registry. */\n\treadonly purpose: \"registry\" | \"stateBackend\";\n\t/** Environment variable name the default-construction path tried to read. */\n\treadonly variable: string;\n}\n\n/**\n * Failure surfaced when the dispatch helper sees a `state.backend` value\n * it does not recognize. The hint points at `opts.statePort` so the\n * caller can pass a custom adapter as an escape hatch.\n */\nexport interface UnsupportedBackendError {\n\t/** Backend name read from `state.backend`. */\n\treadonly backend: string;\n\t/** Suggested escape hatch routed back to the caller. */\n\treadonly hint: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"unsupportedBackend\";\n}\n\n/** Inputs for {@link buildStatePort}. */\ninterface BuildStatePortDeps {\n\t/** Optional `fetch` seam plumbed through to the gist adapter for tests. */\n\treadonly fetch?: GistFetch | undefined;\n\t/** Reads an environment variable; injected so tests stay free of `process.env`. */\n\treadonly getEnv: (name: string) => string | undefined;\n\t/** Resolved state configuration for the target environment. */\n\treadonly stateConfig: StateConfig;\n}\n\nconst STATE_PORT_HINT = \"pass a custom statePort via opts.statePort\";\n\n/**\n * Construct a `StatePort` from a resolved `StateConfig`. Dispatches on\n * `stateConfig.backend` to the matching builtin adapter; reads the\n * required credential from `getEnv` and surfaces `missingCredential` or\n * `unsupportedBackend` as typed Results.\n *\n * @example\n *\n * ```ts\n * import { buildStatePort } from \"@bedrock-rbx/core\";\n *\n * const port = buildStatePort({\n * fetch: async () =>\n * new Response(JSON.stringify({ files: {} }), { status: 200 }),\n * getEnv: (name) => (name === \"BEDROCK_GITHUB_TOKEN\" ? \"ghp_example\" : undefined),\n * stateConfig: { backend: \"gist\", gistId: \"abc123\" },\n * });\n *\n * expect(port.success).toBeTrue();\n * ```\n *\n * @param deps - Resolved state config plus credential-injection seams.\n * @returns A `StatePort` on success, or a typed Err describing the\n * missing credential or the unsupported backend.\n */\nexport function buildStatePort(\n\tdeps: BuildStatePortDeps,\n): Result<StatePort, MissingCredentialError | UnsupportedBackendError> {\n\tif (isGistStateConfig(deps.stateConfig)) {\n\t\treturn buildGistStatePort(deps.stateConfig, deps);\n\t}\n\n\treturn {\n\t\terr: {\n\t\t\tbackend: deps.stateConfig.backend,\n\t\t\thint: STATE_PORT_HINT,\n\t\t\tkind: \"unsupportedBackend\",\n\t\t},\n\t\tsuccess: false,\n\t};\n}\n\nfunction buildGistStatePort(\n\tstateConfig: GistStateConfig,\n\tdeps: BuildStatePortDeps,\n): Result<StatePort, MissingCredentialError> {\n\tconst token = deps.getEnv(\"BEDROCK_GITHUB_TOKEN\") ?? deps.getEnv(\"GITHUB_TOKEN\");\n\tif (token === undefined) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tkind: \"missingCredential\",\n\t\t\t\tpurpose: \"stateBackend\",\n\t\t\t\tvariable: \"BEDROCK_GITHUB_TOKEN\",\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn {\n\t\tdata: createGistStateAdapter({\n\t\t\tfetch: deps.fetch,\n\t\t\tgistId: stateConfig.gistId,\n\t\t\ttoken,\n\t\t}),\n\t\tsuccess: true,\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport type { ResourceKey } from \"../types/ids.ts\";\nimport { defaultKindRegistry } from \"./kinds/index.ts\";\nimport type { BuildDesiredError, ResourceKindModule } from \"./kinds/module.ts\";\nimport type { ResourceCurrentState, ResourceDesiredState, ResourceKind } from \"./resources.ts\";\n\n/**\n * Batch reconcilability check that runs after `buildDesired` and before\n * `diff`. Walks paired `(kind, key)` entries and dispatches to each\n * kind module's optional `assertReconcilable` hook so kind-specific\n * rejections (e.g. Removing a developer-product icon, which the upstream\n * API has no documented unset path for) surface as typed errors before\n * `diff` runs and before any apply-side driver I/O is attempted.\n *\n * Pure and synchronous. Current-only entries (no matching desired) are\n * ignored: their reconciliation is `diff`'s concern, not this seam's.\n *\n * @param desired - Desired state from `buildDesired`.\n * @param current - Prior current state from the state port.\n * @returns `Ok(undefined)` when every paired entry passes its kind-level\n * reconcilability check, or the first `Err` returned by a hook.\n */\nexport function assertAllReconcilable(\n\tdesired: ReadonlyArray<ResourceDesiredState>,\n\tcurrent: ReadonlyArray<ResourceCurrentState>,\n): Result<undefined, BuildDesiredError> {\n\tconst collision = detectProductNameCollision(desired);\n\tif (collision !== undefined) {\n\t\treturn collision;\n\t}\n\n\tconst currentByKey = new Map(current.map((entry) => [compositeKey(entry), entry]));\n\tfor (const entry of desired) {\n\t\tconst matched = currentByKey.get(compositeKey(entry));\n\t\tif (matched === undefined) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst module = defaultKindRegistry[entry.kind] as ResourceKindModule<ResourceKind>;\n\t\tconst check = module.assertReconcilable?.(matched, entry);\n\t\tif (check !== undefined && !check.success) {\n\t\t\treturn check;\n\t\t}\n\t}\n\n\treturn { data: undefined, success: true };\n}\n\nfunction compositeKey(resource: { readonly key: string; readonly kind: ResourceKind }): string {\n\treturn `${resource.kind}:${resource.key}`;\n}\n\nfunction detectProductNameCollision(\n\tdesired: ReadonlyArray<ResourceDesiredState>,\n): Result<undefined, BuildDesiredError> | undefined {\n\tconst seenByName = new Map<string, ResourceKey>();\n\tfor (const entry of desired) {\n\t\tif (entry.kind !== \"developerProduct\") {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst prior = seenByName.get(entry.name);\n\t\tif (prior === undefined) {\n\t\t\tseenByName.set(entry.name, entry.key);\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tkeys: [prior, entry.key],\n\t\t\t\tkind: \"redactedNameCollision\",\n\t\t\t\tmessage: `developer products '${prior}' and '${entry.key}' both resolve to the wire name '${entry.name}'. Roblox enforces per-universe uniqueness on developer-product names, so the second update would be rejected as DuplicateProductName. Set 'redacted: { name: \"<unique>\" }' on one of them to disambiguate.`,\n\t\t\t\tresolvedName: entry.name,\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn undefined;\n}\n","const LUAU_BOOTSTRAP_TEMP_PREFIX = \"bedrock-luau-bootstrap-\";\n\n/**\n * Build the `mkdtempSync` prefix used for Luau bootstrap directories.\n *\n * Embedding `process.pid` scopes the directory to its creator. Concurrent\n * processes share `tmpdir()`, so without a pid component they would observe\n * each other's in-flight bootstrap dirs whenever any caller scans the temp\n * directory.\n * @param pid - Process id to embed in the prefix; pass `process.pid` from\n * production callers.\n * @returns The full prefix to pass to `mkdtempSync`, including the trailing\n * separator that `mkdtempSync` appends its random suffix to.\n */\nexport function bootstrapDirectoryPrefix(pid: number): string {\n\treturn `${LUAU_BOOTSTRAP_TEMP_PREFIX}${pid}-`;\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { execFile } from \"node:child_process\";\nimport { mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { basename, dirname, join } from \"node:path\";\nimport process from \"node:process\";\n\nimport type { LuauEvaluationError, LuauEvaluator } from \"../ports/luau-evaluator.ts\";\nimport { bootstrapDirectoryPrefix } from \"../shell/load-config-internal.ts\";\n\nconst SENTINEL_BASE = \"__BEDROCK_LUAU_\";\nconst OK_PREFIX = `${SENTINEL_BASE}OK__`;\nconst ERR_PREFIX = `${SENTINEL_BASE}ERR__`;\n\nconst LUTE_BOOTSTRAP_LUAU = `--!strict\nlocal json = require(\"@std/json\")\nlocal process = require(\"@std/process\")\nlocal io = require(\"@std/io\")\n\nlocal function emit(kind, payload)\n io.write(\"${SENTINEL_BASE}\" .. kind .. \"__\")\n io.write(json.serialize(payload))\nend\n\nlocal userBasename = process.args[2]\n-- The user file lives in a different directory from this bootstrap, so we\n-- require it via the @user alias defined in the .luaurc written alongside.\nlocal req = \"@user/\" .. string.gsub(userBasename, \"%.luau$\", \"\")\n\nlocal loadOk, modOrErr = pcall(require, req)\nif not loadOk then\n emit(\"ERR\", { kind = \"loadFailed\", message = tostring(modOrErr) })\n return\nend\n\nlocal value = if type(modOrErr) == \"function\" then modOrErr() else modOrErr\n\nlocal encOk, encoded = pcall(json.serialize, value)\nif not encOk then\n emit(\"ERR\", { kind = \"serializeFailed\", message = tostring(encoded) })\n return\nend\n\nio.write(\"${OK_PREFIX}\")\nio.write(encoded)\n`;\n\nconst LUAU_RUNTIME_HINT =\n\t\"install lute (e.g. `mise install` with `github:luau-lang/lute`) or set BEDROCK_LUTE_PATH to the binary.\";\n\ninterface LuteRunOptions {\n\treadonly bin: string;\n\treadonly bootstrapPath: string;\n\treadonly userBasename: string;\n}\n\nfunction isEnoentError(error: unknown): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === \"ENOENT\";\n}\n\n// 5s is generous for evaluating a config file: real configs run in\n// milliseconds, and a value this high is meant to catch infinite loops in\n// user code (or a hung lute) without surprising slow-startup environments.\nconst LUTE_BOOTSTRAP_TIMEOUT_MS = 5_000;\n\n/**\n * Build the default `LuauEvaluator` adapter that shells out to the `lute`\n * runtime. Reads `BEDROCK_LUTE_PATH` from `process.env` once per call to pick\n * the binary, so tests can override it via env var without rebuilding the\n * adapter.\n * @returns A `LuauEvaluator` that spawns `lute run` per call.\n */\nexport function createLuteLuauEvaluator(): LuauEvaluator {\n\treturn evaluateLuauWithLute;\n}\n\nfunction setupBootstrapDirectory(userCwd: string): string {\n\tconst bootstrapDirectory = mkdtempSync(join(tmpdir(), bootstrapDirectoryPrefix(process.pid)));\n\twriteFileSync(join(bootstrapDirectory, \"bootstrap.luau\"), LUTE_BOOTSTRAP_LUAU);\n\t// Lute resolves `require` relative to the calling script's directory, not\n\t// the process cwd. The bootstrap lives in a temp dir, so we expose the\n\t// user's directory via a `.luaurc` alias that the bootstrap requires by\n\t// name.\n\twriteFileSync(\n\t\tjoin(bootstrapDirectory, \".luaurc\"),\n\t\tJSON.stringify({ aliases: { user: userCwd } }),\n\t);\n\treturn bootstrapDirectory;\n}\n\nasync function runLuteBootstrap(runOptions: LuteRunOptions): Promise<string> {\n\tconst { bin, bootstrapPath, userBasename } = runOptions;\n\treturn new Promise((resolve, reject) => {\n\t\texecFile(\n\t\t\tbin,\n\t\t\t[\"run\", bootstrapPath, userBasename],\n\t\t\t{ encoding: \"utf8\", timeout: LUTE_BOOTSTRAP_TIMEOUT_MS },\n\t\t\t(error, stdout) => {\n\t\t\t\tif (error instanceof Error) {\n\t\t\t\t\treject(error);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve(stdout);\n\t\t\t},\n\t\t);\n\t});\n}\n\nfunction parseBootstrapOutput(stdout: string): Record<string, unknown> {\n\tif (stdout.startsWith(ERR_PREFIX)) {\n\t\t// The bootstrap contract pairs ERR_PREFIX with `{ kind, message }`. Pass\n\t\t// the raw envelope through as the error text rather than reach into the\n\t\t// JSON: any unwrapping logic we add here is paranoid with no test\n\t\t// surface (the bootstrap is the only producer and always conforms).\n\t\tthrow new Error(stdout.slice(ERR_PREFIX.length));\n\t}\n\n\t// Stdout that doesn't carry the OK prefix is unsupported; let JSON.parse\n\t// surface a SyntaxError rather than guard a defensive fallback that has\n\t// no observable behaviour.\n\tconst parsed = JSON.parse(stdout.slice(OK_PREFIX.length));\n\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\tthrow new TypeError(\"Luau config must return a table at the root\");\n\t}\n\n\treturn parsed;\n}\n\nasync function evaluateLuauWithLute(\n\tabsPath: string,\n): Promise<Result<Record<string, unknown>, LuauEvaluationError>> {\n\tconst overridePath = process.env[\"BEDROCK_LUTE_PATH\"];\n\tconst lute = overridePath !== undefined && overridePath.length > 0 ? overridePath : \"lute\";\n\tconst bootstrapDirectory = setupBootstrapDirectory(dirname(absPath));\n\ttry {\n\t\tconst stdout = await runLuteBootstrap({\n\t\t\tbin: lute,\n\t\t\tbootstrapPath: join(bootstrapDirectory, \"bootstrap.luau\"),\n\t\t\tuserBasename: basename(absPath),\n\t\t});\n\t\treturn { data: parseBootstrapOutput(stdout), success: true };\n\t} catch (err) {\n\t\tif (isEnoentError(err)) {\n\t\t\treturn {\n\t\t\t\terr: { hint: LUAU_RUNTIME_HINT, kind: \"missingRuntime\" },\n\t\t\t\tsuccess: false,\n\t\t\t};\n\t\t}\n\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn { err: { kind: \"evaluationFailed\", message }, success: false };\n\t} finally {\n\t\trmSync(bootstrapDirectory, { recursive: true });\n\t}\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { loadConfig as c12LoadConfig } from \"c12\";\nimport { existsSync, readdirSync, statSync } from \"node:fs\";\nimport { isAbsolute, join, resolve as resolvePath } from \"node:path\";\nimport process from \"node:process\";\n\nimport { createLuteLuauEvaluator } from \"../adapters/lute-luau-evaluator.ts\";\nimport type { ConfigError } from \"../core/config-error.ts\";\nimport { type Config, validateConfig } from \"../core/schema.ts\";\nimport type { LuauEvaluationError, LuauEvaluator } from \"../ports/luau-evaluator.ts\";\n\n/**\n * Options for {@link loadConfig}. Matches a subset of c12's loader options;\n * additional fields land with the issues that introduce each flow.\n */\nexport interface LoadConfigOptions {\n\t/**\n\t * Path to a specific config file to load, including its extension.\n\t * Resolved relative to `cwd` when not absolute. Loaded as-is with no\n\t * extension search; if the file does not exist at the given path,\n\t * `loadConfig` returns `fileNotFound`. When omitted, `loadConfig`\n\t * discovers `bedrock.config.{ts,js,...}` from `cwd`.\n\t */\n\treadonly configFile?: string;\n\t/**\n\t * Directory to search from. Defaults to `process.cwd()` at call time, so\n\t * each invocation sees the current working directory.\n\t */\n\treadonly cwd?: string;\n}\n\ninterface LoadConfigDeps {\n\treadonly evaluator: LuauEvaluator;\n}\n\ninterface LuauResolveResult {\n\t/* eslint-disable-next-line flawless/naming-convention -- c12 reads `_configFile` to detect that a config file was found. */\n\treadonly _configFile: string;\n\treadonly config: Record<string, unknown>;\n\treadonly configFile: string;\n\treadonly cwd: string;\n}\n\n/**\n * Internal entrypoint that lets tests inject a fake `LuauEvaluator`. The\n * public {@link loadConfig} wraps this with the real lute adapter; the rest\n * of the loader pipeline is identical.\n *\n * @param deps - Injected dependencies. Only the evaluator is configurable.\n * @param options - Same loader options accepted by {@link loadConfig}.\n * @returns Same `Result<Config, ConfigError>` shape as `loadConfig`.\n */\nexport async function loadConfigWith(\n\tdeps: LoadConfigDeps,\n\toptions?: LoadConfigOptions,\n): Promise<Result<Config, ConfigError>> {\n\tconst cwd = options?.cwd ?? process.cwd();\n\tconst configFile =\n\t\toptions?.configFile === undefined ? undefined : resolveConfigPath(cwd, options.configFile);\n\tif (configFile !== undefined && !isExistingFile(configFile)) {\n\t\treturn { err: { kind: \"fileNotFound\", searchedFrom: cwd }, success: false };\n\t}\n\n\tlet resolved: Awaited<ReturnType<typeof c12LoadConfig<Record<string, unknown>>>>;\n\ttry {\n\t\tresolved = await c12LoadConfig<Record<string, unknown>>({\n\t\t\tname: \"bedrock\",\n\t\t\tcwd,\n\t\t\tresolve: makeLuauResolver({\n\t\t\t\tcallerConfigFile: configFile,\n\t\t\t\tdefaultCwd: cwd,\n\t\t\t\tevaluator: deps.evaluator,\n\t\t\t}),\n\t\t\t...(configFile === undefined ? {} : { configFile }),\n\t\t});\n\t} catch (err) {\n\t\treturn { err: attributeLoadError(err, cwd), success: false };\n\t}\n\n\tif (resolved._configFile === undefined) {\n\t\treturn { err: { kind: \"fileNotFound\", searchedFrom: cwd }, success: false };\n\t}\n\n\treturn validateConfig(resolved.config, resolved._configFile);\n}\n\n/**\n * Discover, parse, and validate the project config.\n *\n * Looks for `bedrock.config.{ts,js,mjs,cjs,yaml,yml,json}`, `.bedrockrc*`,\n * and `package.json#bedrock` starting at `options.cwd` (or the current\n * working directory). Returns a fresh, mutable `Config` on every call so\n * long-running scripts see up-to-date values.\n *\n * When the exported default is a function (sync or async), `loadConfig`\n * invokes it with an empty `ConfigContext` and awaits the result before\n * validating.\n *\n * Errors return via `Result`:\n * - `fileNotFound` - no config file was discovered under the search path.\n * - `parseFailed` - a config file was found but could not be parsed (for\n * example, malformed YAML or JSON).\n * - `validationFailed` - a file was found and parsed, but its content did\n * not satisfy the runtime schema.\n * - `configFunctionFailed` - a function-form config threw or its returned\n * promise rejected while being invoked.\n *\n * @param options - Loader options.\n * @returns `Ok` with the validated `Config`, or `Err` with a `ConfigError`.\n * @example\n *\n * ```ts\n * import { loadConfig } from \"@bedrock-rbx/core\";\n *\n * return loadConfig({\n * configFile: \"bedrock.staging.config.yaml\",\n * cwd: \"/path/that/does/not/have/a/config\",\n * }).then((result) => {\n * expect(result.success).toBeFalse();\n * if (!result.success) {\n * expect(result.err.kind).toBe(\"fileNotFound\");\n * }\n * });\n * ```\n */\nexport async function loadConfig(\n\toptions?: LoadConfigOptions,\n): Promise<Result<Config, ConfigError>> {\n\treturn loadConfigWith({ evaluator: createLuteLuauEvaluator() }, options);\n}\n\nfunction resolveConfigPath(cwd: string, configFile: string): string {\n\treturn isAbsolute(configFile) ? configFile : join(cwd, configFile);\n}\n\nfunction isExistingFile(path: string): boolean {\n\ttry {\n\t\treturn statSync(path).isFile();\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Resolve a c12 `resolve` source string to an absolute path of a Luau\n * config file, if one applies. Handles two shapes:\n *\n * - Explicit `.luau` paths (`extends: \"./base.luau\"` or absolute) - always\n * ours when the file exists.\n * - The `\".\"` auto-discovery source - claim only when `bedrock.config.luau`\n * is present in the search directory.\n *\n * Returns `undefined` for every other shape so c12 falls through to its\n * built-in resolution.\n * @param source - The c12 `resolve` source string (`\".\"` for auto-discovery,\n * or a relative or absolute path for explicit `extends` references).\n * @param cwd - The directory to resolve relative paths against and to search\n * for `bedrock.config.luau` in.\n * @returns The absolute path of the matched Luau config, or `undefined` to\n * defer to c12's built-in resolution.\n */\nfunction locateLuauConfig(source: string, cwd: string): string | undefined {\n\tif (source.endsWith(\".luau\")) {\n\t\tconst candidate = isAbsolute(source) ? source : resolvePath(cwd, source);\n\t\treturn existsSync(candidate) ? candidate : undefined;\n\t}\n\n\tif (source === \".\") {\n\t\t// Defer to c12's built-in resolution when a native-format config sits\n\t\t// alongside the Luau one. This matches the documented precedence:\n\t\t// TypeScript / JavaScript / JSON / YAML beat Luau when both are\n\t\t// present. Only claim the Luau file when it's the sole candidate.\n\t\tif (\n\t\t\tNATIVE_CONFIG_EXTENSIONS.some((extension) =>\n\t\t\t\texistsSync(join(cwd, `bedrock.config.${extension}`)),\n\t\t\t)\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst candidate = join(cwd, LUAU_CONFIG_BASENAME);\n\t\treturn existsSync(candidate) ? candidate : undefined;\n\t}\n\n\treturn undefined;\n}\n\nconst NATIVE_CONFIG_EXTENSIONS = [\n\t\"ts\",\n\t\"mts\",\n\t\"cts\",\n\t\"js\",\n\t\"mjs\",\n\t\"cjs\",\n\t\"json\",\n\t\"yaml\",\n\t\"yml\",\n] as const;\n\ninterface PickLuauTargetContext {\n\treadonly callerConfigFile: string | undefined;\n\treadonly cwd: string;\n}\n\ninterface LuauResolverDeps {\n\treadonly callerConfigFile: string | undefined;\n\treadonly defaultCwd: string;\n\treadonly evaluator: LuauEvaluator;\n}\n\n/**\n * Internal-only wrapper used at the c12 boundary: makeLuauResolver maps an\n * evaluator `Err` into this throwable, which `attributeLoadError` unwraps\n * directly. This keeps the port on the `Result` contract per ADR-009 while\n * still satisfying c12's exception-based `resolve` callback.\n */\nclass EvaluatorThrow extends Error {\n\tpublic readonly configError: ConfigError;\n\n\tconstructor(configError: ConfigError) {\n\t\tsuper();\n\t\tthis.configError = configError;\n\t}\n}\n\n/**\n * Decide which Luau file the resolver should evaluate for a given c12 source,\n * or `undefined` to defer to c12's built-in loaders.\n *\n * When the caller named a specific configFile, c12 always invokes us with\n * source === \".\" for the main config (and normalizes its own\n * options.configFile to \"bedrock.config\", so we cannot inspect it). Route\n * the explicit path through our evaluator if it points at a `.luau` file;\n * otherwise defer.\n * @param source - The c12 `resolve` source string.\n * @param context - Caller-side state: the resolved cwd to search in, and the\n * caller's configFile path (or undefined when no explicit path was supplied).\n * @returns The absolute path of the Luau file to evaluate, or `undefined`.\n */\nfunction pickLuauTarget(source: string, context: PickLuauTargetContext): string | undefined {\n\tconst { callerConfigFile, cwd } = context;\n\tif (source === \".\" && callerConfigFile !== undefined) {\n\t\treturn callerConfigFile.endsWith(\".luau\") ? callerConfigFile : undefined;\n\t}\n\n\treturn locateLuauConfig(source, cwd);\n}\n\nfunction evaluationErrorToConfigError(err: LuauEvaluationError, sourceFile: string): ConfigError {\n\tif (err.kind === \"missingRuntime\") {\n\t\treturn { hint: err.hint, kind: \"luauRuntimeMissing\", sourceFile };\n\t}\n\n\treturn { kind: \"parseFailed\", message: err.message, sourceFile };\n}\n\nfunction makeLuauResolver(\n\tdeps: LuauResolverDeps,\n): (\n\tsource: string,\n\tc12Options: { readonly cwd?: string },\n) => Promise<LuauResolveResult | undefined> {\n\treturn async (source, c12Options) => {\n\t\tconst cwd = c12Options.cwd ?? deps.defaultCwd;\n\t\tconst luauPath = pickLuauTarget(source, { callerConfigFile: deps.callerConfigFile, cwd });\n\t\tif (luauPath === undefined) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = await deps.evaluator(luauPath);\n\t\tif (!result.success) {\n\t\t\tthrow new EvaluatorThrow(evaluationErrorToConfigError(result.err, luauPath));\n\t\t}\n\n\t\treturn {\n\t\t\t_configFile: luauPath,\n\t\t\tconfig: result.data,\n\t\t\tconfigFile: luauPath,\n\t\t\tcwd,\n\t\t};\n\t};\n}\n\nconst LUAU_CONFIG_BASENAME = \"bedrock.config.luau\";\n\n// `.luau` is intentionally absent: Luau errors travel through the evaluator\n// adapter's ERR sentinel, not JS stack frames, so the file path is attributed\n// by `discoverConfigFile` rather than scraped from the throw site.\nconst CONFIG_FILE_IN_FRAME = /[^\\s():\"']*bedrock\\.config\\.(?:ts|js|mjs|cjs|yaml|yml|json)/;\n\nfunction extractConfigFileFromStack(err: unknown): string | undefined {\n\tif (!(err instanceof Error) || err.stack === undefined) {\n\t\treturn undefined;\n\t}\n\n\tfor (const rawLine of err.stack.split(\"\\n\")) {\n\t\tconst line = rawLine.trimStart();\n\t\tif (!line.startsWith(\"at \")) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst match = CONFIG_FILE_IN_FRAME.exec(line);\n\t\tif (match !== null) {\n\t\t\treturn match[0];\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction discoverConfigFile(cwd: string): string | undefined {\n\tlet entries: ReadonlyArray<string>;\n\ttry {\n\t\tentries = readdirSync(cwd);\n\t} catch {\n\t\treturn undefined;\n\t}\n\n\tconst match = entries.toSorted().find((entry) => entry.startsWith(\"bedrock.config.\"));\n\treturn match === undefined ? undefined : join(cwd, match);\n}\n\nfunction attributeLoadError(err: unknown, cwd: string): ConfigError {\n\tif (err instanceof EvaluatorThrow) {\n\t\treturn err.configError;\n\t}\n\n\tconst message = err instanceof Error ? err.message : String(err);\n\tconst frameFile = extractConfigFileFromStack(err);\n\tif (frameFile !== undefined) {\n\t\treturn { kind: \"configFunctionFailed\", message, sourceFile: frameFile };\n\t}\n\n\treturn { kind: \"parseFailed\", message, sourceFile: discoverConfigFile(cwd) ?? cwd };\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { readFile as nodeReadFile } from \"node:fs/promises\";\nimport process from \"node:process\";\n\nimport { createDefaultProgressAdapter } from \"../adapters/clack-progress-adapter.ts\";\nimport type { GistFetch } from \"../adapters/gist-state-adapter.ts\";\nimport { createNoOpProgressAdapter } from \"../adapters/no-op-progress-adapter.ts\";\nimport { assertAllReconcilable } from \"../core/assert-all-reconcilable.ts\";\nimport type { ConfigError } from \"../core/config-error.ts\";\nimport { diff } from \"../core/diff.ts\";\nimport { flattenConfig } from \"../core/flatten.ts\";\nimport { resolveStateConfig, type StateNotConfiguredError } from \"../core/resolve-state-config.ts\";\nimport type { ResourceCurrentState } from \"../core/resources.ts\";\nimport type { Config, ResolvedConfig } from \"../core/schema.ts\";\nimport {\n\ttype IncompletePassEntryError,\n\ttype IncompletePlaceEntryError,\n\ttype IncompleteUniverseEntryError,\n\tselectEnvironment,\n\ttype UnknownEnvironmentError,\n} from \"../core/select-environment.ts\";\nimport type { BedrockState, StateError } from \"../core/state.ts\";\nimport type { ProgressPort } from \"../ports/progress-port.ts\";\nimport type { DriverRegistry } from \"../ports/resource-driver.ts\";\nimport type { StatePort } from \"../ports/state-port.ts\";\nimport { type AggregateApplyError, applyOps } from \"./apply-ops.ts\";\nimport { buildDefaultRegistry, type RegistryConfigError } from \"./build-default-registry.ts\";\nimport { buildDesired, type BuildDesiredError } from \"./build-desired.ts\";\nimport {\n\tbuildStatePort,\n\ttype MissingCredentialError,\n\ttype UnsupportedBackendError,\n} from \"./build-state-port.ts\";\nimport { loadConfig as defaultLoadConfig, type LoadConfigOptions } from \"./load-config.ts\";\n\n/**\n * Inputs for `deploy`. Every field except `environment` is optional;\n * omitted dependencies are default-constructed from the project config\n * and the environment variables `BEDROCK_GITHUB_TOKEN` and `BEDROCK_API_KEY`.\n */\nexport interface DeployOptions {\n\t/** Pre-loaded, optionally-mutated project config. Omit to call `loadConfig()` automatically. */\n\treadonly config?: Config;\n\t/** Environment name; threaded into `StatePort.read` and the persisted snapshot. */\n\treadonly environment: string;\n\t/** `fetch` override plumbed into the default-constructed gist adapter when `statePort` is omitted. */\n\treadonly fetch?: GistFetch;\n\t/** Reads an environment variable; defaults to `(name) => process.env[name]`. */\n\treadonly getEnv?: (name: string) => string | undefined;\n\t/** Loader invoked when `config` is omitted; defaults to `loadConfig` from this package. */\n\treadonly loadConfig?: (options?: LoadConfigOptions) => Promise<Result<Config, ConfigError>>;\n\t/**\n\t * Optional sink for per-resource and aggregate progress events. When\n\t * supplied, `applyOps` emits one started/terminal pair per non-noop op\n\t * (plus per-noop and summary events), and `deploy` emits `stateWritten`\n\t * after a successful state-write. Omit to run silently.\n\t */\n\treadonly progress?: ProgressPort;\n\t/** Reads file bytes for resources that have file-backed inputs. Defaults to `node:fs/promises.readFile`. */\n\treadonly readFile?: (path: string) => Promise<Uint8Array>;\n\t/** Per-kind driver table consulted for create / update dispatch. Default-constructed from `BEDROCK_API_KEY` when omitted. */\n\treadonly registry?: DriverRegistry;\n\t/** Backend used to read the prior snapshot and persist the new one. Default-constructed from `config.state` and `BEDROCK_GITHUB_TOKEN` when omitted. */\n\treadonly statePort?: StatePort;\n}\n\n/**\n * Failure surfaced by `deploy`. Stage-tagged so callers can branch on\n * `kind` to distinguish reconciliation failures (`stateReadFailed`,\n * `applyFailed`, ...) from default-construction failures\n * (`configLoadFailed`, `stateNotConfigured`, `unknownEnvironment`,\n * `incompletePlaceEntry`, `incompleteUniverseEntry`, `missingCredential`,\n * `unsupportedBackend`, `registryConfigMissing`).\n */\nexport type DeployError =\n\t| IncompletePassEntryError\n\t| IncompletePlaceEntryError\n\t| IncompleteUniverseEntryError\n\t| MissingCredentialError\n\t| RegistryConfigError\n\t| StateNotConfiguredError\n\t| UnknownEnvironmentError\n\t| UnsupportedBackendError\n\t| { readonly cause: AggregateApplyError; readonly kind: \"applyFailed\" }\n\t| { readonly cause: BuildDesiredError; readonly kind: \"buildDesiredFailed\" }\n\t| { readonly cause: ConfigError; readonly kind: \"configLoadFailed\" }\n\t| { readonly cause: StateError; readonly kind: \"stateReadFailed\" }\n\t| {\n\t\t\treadonly cause: StateError;\n\t\t\treadonly kind: \"stateWriteFailed\";\n\t\t\treadonly unsavedState: BedrockState;\n\t };\n\ninterface SnapshotInputs {\n\treadonly applied: Result<ReadonlyArray<ResourceCurrentState>, AggregateApplyError>;\n\treadonly environment: string;\n\treadonly priorResources: ReadonlyArray<ResourceCurrentState>;\n}\n\ninterface FinalizeInputs {\n\treadonly applied: Result<ReadonlyArray<ResourceCurrentState>, AggregateApplyError>;\n\treadonly merged: BedrockState;\n\treadonly written: Result<void, StateError>;\n}\n\ninterface ResolvedDepsBase {\n\treadonly config: ResolvedConfig;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\treadonly registry: DriverRegistry;\n\treadonly statePort: StatePort;\n}\n\ninterface ResolvedDeps extends ResolvedDepsBase {\n\treadonly progress: ProgressPort;\n}\n\ninterface PickRegistryInputs {\n\treadonly config: ResolvedConfig;\n\treadonly options: DeployOptions;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n}\n\ninterface EmitTerminalEventInputs {\n\treadonly environment: string;\n\treadonly progress: ProgressPort;\n\treadonly result: Result<BedrockState, DeployError>;\n}\n\n/**\n * Decide whether `BEDROCK_CLI` should select the clack-backed default\n * progress adapter. Exported for direct unit coverage of the boundary\n * (`undefined` and empty string both flip to no-op; any non-empty value\n * picks clack).\n *\n * @param value - Raw `BEDROCK_CLI` value as returned by `getEnv`.\n * @returns `true` if the clack adapter should be the default.\n */\nexport function isCliEnvironmentFlagSet(value: string | undefined): boolean {\n\treturn value !== undefined && value !== \"\";\n}\n\n/**\n * Run a full reconcile end-to-end. Default-constructs missing deps from\n * the project config and the environment variables `BEDROCK_GITHUB_TOKEN`\n * and `BEDROCK_API_KEY`; emits a terminal `deploySuccess` or `deployFailure`\n * event through the resolved `progress` port. When `progress` is omitted,\n * the default port comes from `BEDROCK_CLI`: a non-empty value selects the\n * clack-backed adapter, any other reading selects the no-op adapter. No\n * environment lookups happen when `statePort`, `registry`, `config`, and\n * `progress` are all supplied explicitly.\n *\n * @param options - Target environment plus optional overrides.\n * @returns The persisted `BedrockState` on success, or a stage-tagged\n * `DeployError` on failure.\n * @example\n *\n * ```ts\n * import { deploy } from \"@bedrock-rbx/core\";\n *\n * return deploy({ environment: \"production\" }).then((result) => {\n * expect(result.success).toBeFalse();\n * if (!result.success) {\n * expect([\"configLoadFailed\", \"stateNotConfigured\"]).toContain(result.err.kind);\n * }\n * });\n * ```\n *\n * @example\n *\n * ```ts\n * import { deploy, type BedrockState, type DriverRegistry, type StatePort } from \"@bedrock-rbx/core\";\n *\n * const store = new Map<string, BedrockState>();\n * const statePort: StatePort = {\n * async read(environment) {\n * return { data: store.get(environment), success: true };\n * },\n * async write(state) {\n * store.set(state.environment, state);\n * return { data: undefined, success: true };\n * },\n * };\n * const registry: DriverRegistry = {\n * developerProduct: {\n * create: async () => { throw new Error(\"unreachable: empty config\"); },\n * },\n * gamePass: { create: async () => { throw new Error(\"unreachable: empty config\"); } },\n * place: { create: async () => { throw new Error(\"unreachable: empty config\"); } },\n * universe: { create: async () => { throw new Error(\"unreachable: empty config\"); } },\n * };\n *\n * return deploy({\n * config: {\n * environments: { production: {} },\n * state: { backend: \"gist\", gistId: \"abc\" },\n * passes: {},\n * },\n * environment: \"production\",\n * registry,\n * statePort,\n * }).then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.environment).toBe(\"production\");\n * expect(result.data.resources).toBeEmpty();\n * }\n * });\n * ```\n */\nexport async function deploy(options: DeployOptions): Promise<Result<BedrockState, DeployError>> {\n\tif (options.progress !== undefined) {\n\t\treturn runAndEmit(options, options.progress);\n\t}\n\n\tif (!isCliEnvironmentFlagSet(getEnvironmentOf(options)(\"BEDROCK_CLI\"))) {\n\t\treturn runAndEmit(options, createNoOpProgressAdapter());\n\t}\n\n\treturn runWithDeferredClackProgress(options);\n}\n\nfunction readProcessEnvironment(name: string): string | undefined {\n\treturn process.env[name];\n}\n\nfunction getEnvironmentOf(options: DeployOptions): (name: string) => string | undefined {\n\treturn options.getEnv ?? readProcessEnvironment;\n}\n\nasync function pickConfig(options: DeployOptions): Promise<Result<Config, DeployError>> {\n\tif (options.config !== undefined) {\n\t\treturn { data: options.config, success: true };\n\t}\n\n\tconst loader = options.loadConfig ?? defaultLoadConfig;\n\tconst loaded = await loader();\n\tif (!loaded.success) {\n\t\treturn { err: { cause: loaded.err, kind: \"configLoadFailed\" }, success: false };\n\t}\n\n\treturn { data: loaded.data, success: true };\n}\n\nfunction pickStatePort(\n\toptions: DeployOptions,\n\tconfig: ResolvedConfig,\n): Result<StatePort, DeployError> {\n\tif (options.statePort !== undefined) {\n\t\treturn { data: options.statePort, success: true };\n\t}\n\n\tconst stateConfig = resolveStateConfig(config, options.environment);\n\tif (!stateConfig.success) {\n\t\treturn { err: stateConfig.err, success: false };\n\t}\n\n\treturn buildStatePort({\n\t\tfetch: options.fetch,\n\t\tgetEnv: getEnvironmentOf(options),\n\t\tstateConfig: stateConfig.data,\n\t});\n}\n\nfunction pickRegistry(inputs: PickRegistryInputs): Result<DriverRegistry, DeployError> {\n\tif (inputs.options.registry !== undefined) {\n\t\treturn { data: inputs.options.registry, success: true };\n\t}\n\n\treturn buildDefaultRegistry({\n\t\tconfig: inputs.config,\n\t\tgetEnv: getEnvironmentOf(inputs.options),\n\t\treadFile: inputs.readFile,\n\t});\n}\n\nasync function resolveDeps(options: DeployOptions): Promise<Result<ResolvedDepsBase, DeployError>> {\n\tconst config = await pickConfig(options);\n\tif (!config.success) {\n\t\treturn config;\n\t}\n\n\tconst selected = selectEnvironment(config.data, options.environment);\n\tif (!selected.success) {\n\t\treturn { err: selected.err, success: false };\n\t}\n\n\tconst effective = selected.data;\n\tconst readFile = options.readFile ?? nodeReadFile;\n\tconst statePort = pickStatePort(options, effective);\n\tif (!statePort.success) {\n\t\treturn statePort;\n\t}\n\n\tconst registry = pickRegistry({ config: effective, options, readFile });\n\tif (!registry.success) {\n\t\treturn registry;\n\t}\n\n\treturn {\n\t\tdata: { config: effective, readFile, registry: registry.data, statePort: statePort.data },\n\t\tsuccess: true,\n\t};\n}\n\nfunction mergeResources(\n\tpre: ReadonlyArray<ResourceCurrentState>,\n\tapplied: ReadonlyArray<ResourceCurrentState>,\n): ReadonlyArray<ResourceCurrentState> {\n\tconst byKey = new Map<string, ResourceCurrentState>();\n\tfor (const resource of pre) {\n\t\tbyKey.set(`${resource.kind}:${resource.key}`, resource);\n\t}\n\n\tfor (const resource of applied) {\n\t\tbyKey.set(`${resource.kind}:${resource.key}`, resource);\n\t}\n\n\treturn [...byKey.values()];\n}\n\nfunction buildSnapshot(inputs: SnapshotInputs): BedrockState {\n\tconst appliedResources = inputs.applied.success\n\t\t? inputs.applied.data\n\t\t: inputs.applied.err.applied;\n\treturn {\n\t\tenvironment: inputs.environment,\n\t\tresources: mergeResources(inputs.priorResources, appliedResources),\n\t\tversion: 1,\n\t};\n}\n\nfunction finalize(inputs: FinalizeInputs): Result<BedrockState, DeployError> {\n\t// Check write before apply: only the write carries `unsavedState`.\n\tif (!inputs.written.success) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tcause: inputs.written.err,\n\t\t\t\tkind: \"stateWriteFailed\",\n\t\t\t\tunsavedState: inputs.merged,\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\tif (!inputs.applied.success) {\n\t\treturn { err: { cause: inputs.applied.err, kind: \"applyFailed\" }, success: false };\n\t}\n\n\treturn { data: inputs.merged, success: true };\n}\n\nasync function runReconcile(\n\tenvironment: string,\n\tdeps: ResolvedDeps,\n): Promise<Result<BedrockState, DeployError>> {\n\tconst desired = await buildDesired(flattenConfig(deps.config), deps.readFile);\n\tif (!desired.success) {\n\t\treturn { err: { cause: desired.err, kind: \"buildDesiredFailed\" }, success: false };\n\t}\n\n\tconst prior = await deps.statePort.read(environment);\n\tif (!prior.success) {\n\t\treturn { err: { cause: prior.err, kind: \"stateReadFailed\" }, success: false };\n\t}\n\n\tconst priorResources = prior.data?.resources ?? [];\n\tconst validated = assertAllReconcilable(desired.data, priorResources);\n\tif (!validated.success) {\n\t\treturn { err: { cause: validated.err, kind: \"buildDesiredFailed\" }, success: false };\n\t}\n\n\tconst ops = diff(desired.data, priorResources);\n\tconst applied = await applyOps(ops, deps.registry, { environment, progress: deps.progress });\n\tconst merged = buildSnapshot({ applied, environment, priorResources });\n\n\tconst written = await deps.statePort.write(merged);\n\tif (written.success) {\n\t\tdeps.progress.emit({ environment, kind: \"stateWritten\" });\n\t}\n\n\treturn finalize({ applied, merged, written });\n}\n\nasync function runDeploy(\n\toptions: DeployOptions,\n\tprogress: ProgressPort,\n): Promise<Result<BedrockState, DeployError>> {\n\tconst resolved = await resolveDeps(options);\n\tif (!resolved.success) {\n\t\treturn resolved;\n\t}\n\n\treturn runReconcile(options.environment, { ...resolved.data, progress });\n}\n\nfunction emitTerminalEvent(inputs: EmitTerminalEventInputs): void {\n\tconst { environment, progress, result } = inputs;\n\tif (result.success) {\n\t\tprogress.emit({\n\t\t\tenvironment,\n\t\t\tkind: \"deploySuccess\",\n\t\t\tresourceCount: result.data.resources.length,\n\t\t});\n\t\treturn;\n\t}\n\n\tprogress.emit({ environment, error: result.err, kind: \"deployFailure\" });\n}\n\nasync function runAndEmit(\n\toptions: DeployOptions,\n\tprogress: ProgressPort,\n): Promise<Result<BedrockState, DeployError>> {\n\tconst result = await runDeploy(options, progress);\n\temitTerminalEvent({ environment: options.environment, progress, result });\n\treturn result;\n}\n\nasync function runWithDeferredClackProgress(\n\toptions: DeployOptions,\n): Promise<Result<BedrockState, DeployError>> {\n\tconst resolved = await resolveDeps(options);\n\tconst labelConfig = resolved.success ? resolved.data.config : options.config;\n\tconst progress = createDefaultProgressAdapter(labelConfig);\n\n\tif (!resolved.success) {\n\t\temitTerminalEvent({ environment: options.environment, progress, result: resolved });\n\t\treturn resolved;\n\t}\n\n\tconst result = await runReconcile(options.environment, { ...resolved.data, progress });\n\temitTerminalEvent({ environment: options.environment, progress, result });\n\treturn result;\n}\n","import { asResourceKey, asRobloxAssetId } from \"../../types/ids.ts\";\nimport type { ResourceKey, Sha256Hex } from \"../../types/ids.ts\";\nimport {\n\ttype ResourceCurrentState,\n\tUNIVERSE_SINGLETON_KEY,\n\ttype UniverseOutputs,\n} from \"../resources.ts\";\nimport type { ResolvedUniverseEntry } from \"../schema.ts\";\nimport type { BedrockState } from \"../state.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\nimport type { PassFoldEntry } from \"./fold-passes.ts\";\nimport type { PlaceFoldEntry } from \"./fold-places.ts\";\nimport type { ProductFoldEntry } from \"./fold-products.ts\";\n\n/**\n * Inputs to {@link buildState}. Bundled into one object because the\n * call site already groups these per-environment values together (the\n * shell threads them in lockstep) and named fields read better than a\n * three-positional-argument signature.\n */\ninterface BuildStateInputs {\n\t/** Environment name; written verbatim onto the state. */\n\treadonly environment: string;\n\t/** Per-kind fold results for this environment. */\n\treadonly folded: EnvironmentFoldResult;\n\t/**\n\t * Per-pass-key locale-keyed icon hashes recomputed from disk by the\n\t * shell. Keys absent from the map fall back to `mantleIconFileHashes`\n\t * from the matching fold entry.\n\t */\n\treadonly passIconHashesByKey: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>;\n\t/**\n\t * Per-product-key locale-keyed icon hashes recomputed from disk by the\n\t * shell. Keys absent from the map fall back to `mantleIconFileHashes`\n\t * from the matching fold entry; products without any icon partner emit\n\t * resources without `iconFileHashes`.\n\t */\n\treadonly productIconHashesByKey: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>;\n}\n\ninterface UniverseResourceInputs {\n\treadonly entry: ResolvedUniverseEntry;\n\treadonly outputs: UniverseOutputs;\n}\n\n/**\n * Compose one environment's folded data into the on-disk `BedrockState`\n * snapshot the migrator's caller writes per environment.\n *\n * The resulting state carries one `kind: \"universe\"` resource (when an\n * experience folded), followed by one `kind: \"place\"` resource per\n * matched place pair, then one `kind: \"gamePass\"` resource per folded\n * pass entry, then one `kind: \"developerProduct\"` resource per folded\n * product entry, in declaration order. Each resource's declared fields\n * mirror its fold output; pass and product resources receive their\n * `iconFileHashes` from the matching `*IconHashesByKey` map (computed by\n * the shell from the icon file's bytes) and fall back to the\n * Mantle-recorded hashes when the map omits the key. Product resources\n * without an icon partner omit `icon` and `iconFileHashes` entirely. The\n * `outputs` field carries the Mantle-recorded identifiers (universe\n * `rootPlaceId`, place `versionNumber`, pass `assetId` and `iconAssetIds`,\n * product `productId` and optional `iconImageAssetId`).\n *\n * @param inputs - Folded data plus recomputed hashes for this environment.\n * @returns A `BedrockState` populated with one resource per folded kind.\n */\nexport function buildState(inputs: BuildStateInputs): BedrockState {\n\treturn {\n\t\tenvironment: inputs.environment,\n\t\tresources: composeResources(inputs),\n\t\tversion: 1,\n\t};\n}\n\nfunction universeResource(inputs: UniverseResourceInputs): ResourceCurrentState<\"universe\"> {\n\tconst { entry, outputs } = inputs;\n\treturn {\n\t\tkey: UNIVERSE_SINGLETON_KEY,\n\t\tconsoleEnabled: entry.consoleEnabled,\n\t\tdesktopEnabled: entry.desktopEnabled,\n\t\tdisplayName: entry.displayName,\n\t\tkind: \"universe\",\n\t\tmobileEnabled: entry.mobileEnabled,\n\t\toutputs,\n\t\ttabletEnabled: entry.tabletEnabled,\n\t\tuniverseId: asRobloxAssetId(entry.universeId),\n\t\tvoiceChatEnabled: entry.voiceChatEnabled,\n\t\tvrEnabled: entry.vrEnabled,\n\t};\n}\n\nfunction placeResource(key: string, fold: PlaceFoldEntry): ResourceCurrentState<\"place\"> {\n\treturn {\n\t\tkey: asResourceKey(key),\n\t\tdescription: fold.entry.description,\n\t\tdisplayName: fold.entry.displayName,\n\t\tfileHash: fold.fileHash,\n\t\tfilePath: fold.entry.filePath,\n\t\tkind: \"place\",\n\t\toutputs: fold.outputs,\n\t\tplaceId: asRobloxAssetId(fold.placeId),\n\t\tserverSize: fold.entry.serverSize,\n\t};\n}\n\nfunction passResource(\n\tfold: PassFoldEntry,\n\ticonFileHashes: Record<\"en-us\", Sha256Hex>,\n): ResourceCurrentState<\"gamePass\"> {\n\treturn {\n\t\tkey: fold.key,\n\t\tname: fold.entry.name,\n\t\tdescription: fold.entry.description,\n\t\ticon: fold.entry.icon,\n\t\ticonFileHashes,\n\t\tkind: \"gamePass\",\n\t\toutputs: fold.outputs,\n\t\tprice: fold.entry.price,\n\t};\n}\n\nfunction productResource(\n\tfold: ProductFoldEntry,\n\tproductIconHashesByKey: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>,\n): ResourceCurrentState<\"developerProduct\"> {\n\tconst base: ResourceCurrentState<\"developerProduct\"> = {\n\t\tkey: fold.key,\n\t\tname: fold.entry.name,\n\t\tdescription: fold.entry.description,\n\t\tisRegionalPricingEnabled: undefined,\n\t\tkind: \"developerProduct\",\n\t\toutputs: fold.outputs,\n\t\tprice: fold.entry.price,\n\t\tstorePageEnabled: undefined,\n\t};\n\n\tif (fold.entry.icon === undefined || fold.mantleIconFileHashes === undefined) {\n\t\treturn base;\n\t}\n\n\treturn {\n\t\t...base,\n\t\ticon: fold.entry.icon,\n\t\ticonFileHashes: productIconHashesByKey.get(fold.key) ?? fold.mantleIconFileHashes,\n\t};\n}\n\nfunction composeResources(inputs: BuildStateInputs): ReadonlyArray<ResourceCurrentState> {\n\tconst { folded, passIconHashesByKey, productIconHashesByKey } = inputs;\n\tconst universeResources: ReadonlyArray<ResourceCurrentState> =\n\t\tfolded.universe === undefined\n\t\t\t? []\n\t\t\t: [\n\t\t\t\t\tuniverseResource({\n\t\t\t\t\t\tentry: folded.universe.entry,\n\t\t\t\t\t\toutputs: folded.universe.outputs,\n\t\t\t\t\t}),\n\t\t\t\t];\n\n\tconst placeResources: ReadonlyArray<ResourceCurrentState> = [...folded.places.entries()].map(\n\t\t([key, entry]) => placeResource(key, entry),\n\t);\n\n\tconst passResources: ReadonlyArray<ResourceCurrentState> = folded.passes.map((entry) => {\n\t\treturn passResource(\n\t\t\tentry,\n\t\t\tpassIconHashesByKey.get(entry.key) ?? entry.mantleIconFileHashes,\n\t\t);\n\t});\n\n\tconst productResources: ReadonlyArray<ResourceCurrentState> = folded.products.map((entry) => {\n\t\treturn productResource(entry, productIconHashesByKey);\n\t});\n\n\treturn [...universeResources, ...placeResources, ...passResources, ...productResources];\n}\n","/**\n * Result of inspecting a display name for a Mantle-style `[LABEL]` prefix.\n * `label` is `undefined` when the input does not match the bracketed-prefix\n * shape Mantle emits; in that case `body` echoes the input unchanged.\n */\ninterface ExtractedDisplayName {\n\t/** Display name with the bracketed prefix and trailing whitespace stripped, or the input verbatim when no prefix matched. */\n\treadonly body: string;\n\t/** Captured label from the bracketed prefix, or `undefined` when the input did not match. */\n\treadonly label: string | undefined;\n}\n\n/**\n * Lift the leading bracketed prefix off a display name produced by Mantle's\n * environment-name stamping. A match requires `[LABEL] body` with a\n * non-empty label, mandatory whitespace after the closing bracket, and a\n * non-empty body. When the input does not match, `label` is `undefined` and\n * `body` is the input verbatim, so callers can round-trip arbitrary\n * display-name shapes without losing data.\n *\n * @param value - Raw display name to inspect.\n * @returns The captured label (or `undefined`) and the unprefixed body.\n */\nexport function extractDisplayNamePrefix(value: string): ExtractedDisplayName {\n\tconst passthrough: ExtractedDisplayName = { body: value, label: undefined };\n\tif (!value.startsWith(\"[\")) {\n\t\treturn passthrough;\n\t}\n\n\tconst closeIndex = value.indexOf(\"]\");\n\tif (closeIndex < 2) {\n\t\treturn passthrough;\n\t}\n\n\tconst afterBracket = value.slice(closeIndex + 1);\n\tconst body = afterBracket.trimStart();\n\tif (body === afterBracket) {\n\t\treturn passthrough;\n\t}\n\n\tif (body === \"\") {\n\t\treturn passthrough;\n\t}\n\n\treturn { body, label: value.slice(1, closeIndex) };\n}\n","import { extractDisplayNamePrefix } from \"./extract-display-name-prefix.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\n\n/**\n * Compute the environment-label candidate that round-trips a Mantle-style\n * `[LABEL]` prefix through bedrock's deploy-time prefix system. Inspects\n * every present `displayName` on the env's universe and place folds; if all\n * extractions agree on the same captured label the function returns it\n * lowercased. Any disagreement (mixed prefixes, prefixed alongside\n * unprefixed, or all unprefixed) yields `undefined`, signalling that the\n * caller should keep raw per-environment displayName overrides instead of\n * promoting a label.\n *\n * @param fold - Per-environment fold result to inspect.\n * @returns Lowercased label string when every displayName shares one\n * bracketed prefix; `undefined` otherwise.\n */\nexport function computeEnvironmentLabel(fold: EnvironmentFoldResult): string | undefined {\n\tconst displayNames = [\n\t\tfold.universe?.entry.displayName,\n\t\t...[...fold.places.values()].map(({ entry }) => entry.displayName),\n\t].filter((displayName): displayName is string => displayName !== undefined);\n\tconst labels = new Set(\n\t\tdisplayNames.map((displayName) => extractDisplayNamePrefix(displayName).label),\n\t);\n\tif (labels.size !== 1) {\n\t\treturn undefined;\n\t}\n\n\tconst [label] = labels;\n\treturn label?.toLowerCase();\n}\n","import type { EnvironmentFoldResult } from \"./fold-environment.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\n\nconst RESOURCE_MISSING_RULE = \"factorize-environments/resource-missing-from-env\";\n\n/**\n * Inputs to {@link collectMissingResourceWarnings}.\n */\ninterface MissingResourceInputs {\n\t/** Per-environment fold results, keyed by environment name. */\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\t/** The chosen primary environment, used as the symmetry baseline. */\n\treadonly primary: { readonly fold: EnvironmentFoldResult; readonly name: string };\n}\n\ninterface AsymmetryContext {\n\treadonly environmentName: string;\n\treadonly fold: EnvironmentFoldResult;\n\treadonly primary: EnvironmentFoldResult;\n}\n\ninterface MissingResourcePaths {\n\treadonly bedrockSegment: string;\n\treadonly environmentName: string;\n\treadonly mantleSegment: string;\n}\n\ninterface KeyedAsymmetrySpec {\n\treadonly bedrockPrefix: string;\n\treadonly mantlePrefix: string;\n\treadonly readKeys: (fold: EnvironmentFoldResult) => ReadonlyArray<string>;\n}\n\nconst KEYED_ASYMMETRY_SPECS: ReadonlyArray<KeyedAsymmetrySpec> = [\n\t{\n\t\tbedrockPrefix: \"places\",\n\t\tmantlePrefix: \"place_\",\n\t\treadKeys: (fold) => [...fold.places.keys()],\n\t},\n\t{\n\t\tbedrockPrefix: \"passes\",\n\t\tmantlePrefix: \"pass_\",\n\t\treadKeys: (fold) => fold.passes.map(({ key }) => key),\n\t},\n\t{\n\t\tbedrockPrefix: \"products\",\n\t\tmantlePrefix: \"product_\",\n\t\treadKeys: (fold) => fold.products.map(({ key }) => key),\n\t},\n];\n\n/**\n * Walk every environment fold and emit one `interpretive` warning per\n * resource the environment lacks relative to the chosen primary, and per\n * resource the environment introduces that the primary lacks. The walk\n * covers the universe singleton plus every keyed kind in\n * {@link KEYED_ASYMMETRY_SPECS}.\n *\n * @param inputs - Per-environment folds and the resolved primary.\n * @returns Flat list of resource-asymmetry warnings.\n */\nexport function collectMissingResourceWarnings(\n\tinputs: MissingResourceInputs,\n): ReadonlyArray<MigrationWarning> {\n\tconst primaryFold = inputs.primary.fold;\n\treturn [...inputs.folds.entries()].flatMap(([name, fold]): ReadonlyArray<MigrationWarning> => {\n\t\tconst context: AsymmetryContext = { environmentName: name, fold, primary: primaryFold };\n\t\treturn [\n\t\t\t...universeAsymmetryWarnings(context),\n\t\t\t...KEYED_ASYMMETRY_SPECS.flatMap((spec) => keyedAsymmetryWarnings(context, spec)),\n\t\t];\n\t});\n}\n\nfunction asymmetricKeys(\n\tenvironmentKeys: ReadonlyArray<string>,\n\tprimaryKeys: ReadonlyArray<string>,\n): ReadonlyArray<string> {\n\tconst environmentSet = new Set(environmentKeys);\n\tconst primarySet = new Set(primaryKeys);\n\tconst onlyInEnvironment = environmentKeys.filter((key) => !primarySet.has(key));\n\tconst onlyInPrimary = primaryKeys.filter((key) => !environmentSet.has(key));\n\treturn [...onlyInEnvironment, ...onlyInPrimary];\n}\n\nfunction missingResourceWarning(paths: MissingResourcePaths): MigrationWarning {\n\treturn {\n\t\tbedrockPath: `environments.${paths.environmentName}.${paths.bedrockSegment}`,\n\t\tkind: \"interpretive\",\n\t\tmantlePath: `${paths.environmentName}.${paths.mantleSegment}`,\n\t\trule: RESOURCE_MISSING_RULE,\n\t};\n}\n\nfunction keyedAsymmetryWarnings(\n\tcontext: AsymmetryContext,\n\tspec: KeyedAsymmetrySpec,\n): ReadonlyArray<MigrationWarning> {\n\treturn asymmetricKeys(spec.readKeys(context.fold), spec.readKeys(context.primary)).map(\n\t\t(key) => {\n\t\t\treturn missingResourceWarning({\n\t\t\t\tbedrockSegment: `${spec.bedrockPrefix}.${key}`,\n\t\t\t\tenvironmentName: context.environmentName,\n\t\t\t\tmantleSegment: `${spec.mantlePrefix}${key}`,\n\t\t\t});\n\t\t},\n\t);\n}\n\nfunction universeAsymmetryWarnings(context: AsymmetryContext): ReadonlyArray<MigrationWarning> {\n\tconst hasEnvironmentUniverse = context.fold.universe !== undefined;\n\tconst hasPrimaryUniverse = context.primary.universe !== undefined;\n\tif (hasEnvironmentUniverse === hasPrimaryUniverse) {\n\t\treturn [];\n\t}\n\n\treturn [\n\t\tmissingResourceWarning({\n\t\t\tbedrockSegment: \"universe\",\n\t\t\tenvironmentName: context.environmentName,\n\t\t\tmantleSegment: \"experience_singleton\",\n\t\t}),\n\t];\n}\n","import type { EnvironmentEntry, PlaceEntry } from \"../schema.ts\";\nimport { extractDisplayNamePrefix } from \"./extract-display-name-prefix.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\nimport type { PlaceFoldEntry } from \"./fold-places.ts\";\n\n/**\n * Inputs to {@link buildRootPlaces}.\n */\ninterface BuildRootPlacesInputs {\n\t/** Per-environment fold results, keyed by environment name. */\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\t/**\n\t * Per-environment label map. An environment whose label is `undefined`\n\t * contributes raw `displayName` values to consensus; otherwise the value\n\t * is stripped via {@link extractDisplayNamePrefix} before comparison.\n\t */\n\treadonly labels: ReadonlyMap<string, string | undefined>;\n\t/** The chosen primary environment's fold; supplies `filePath` for every place entry on root. */\n\treadonly primaryFold: EnvironmentFoldResult;\n}\n\n/**\n * Inputs to {@link buildPlacesOverlay}.\n */\ninterface BuildPlacesOverlayInputs {\n\t/** The per-environment fold whose places are being overlaid. */\n\treadonly fold: EnvironmentFoldResult;\n\t/** The environment's label (or `undefined`); enables prefix-stripping on the overlay's `displayName`. */\n\treadonly label: string | undefined;\n\t/** The already-built root `places` block; matching field values suppress overlay entries. */\n\treadonly rootPlaces: Record<string, PlaceEntry> | undefined;\n}\n\ntype PlaceOverlayEntry = NonNullable<EnvironmentEntry[\"places\"]>[string];\n\ntype OptionalPlaceField = \"description\" | \"serverSize\";\n\ninterface LabeledFold {\n\treadonly fold: EnvironmentFoldResult;\n\treadonly label: string | undefined;\n}\n\ninterface ConsensusInputs<F extends OptionalPlaceField> {\n\treadonly field: F;\n\treadonly folds: ReadonlyArray<LabeledFold>;\n\treadonly placeKey: string;\n}\n\ninterface DisplayNameConsensusInputs {\n\treadonly folds: ReadonlyArray<LabeledFold>;\n\treadonly placeKey: string;\n}\n\ninterface BuildPlaceOverlayEntryInputs {\n\treadonly fold: PlaceFoldEntry;\n\treadonly label: string | undefined;\n\treadonly rootEntry: PlaceEntry | undefined;\n}\n\n/**\n * Project the per-environment place folds into bedrock's root `places`\n * block, giving each optional metadata field (`description`, `displayName`,\n * `serverSize`) a value only when every environment that owns the place\n * key agrees. `displayName` consensus runs on the unprefixed body when an\n * environment carries a label, so a Mantle-stamped `[ENV] Foo` in one\n * environment matches a plain `Foo` in another. Divergent or partially-set\n * fields are omitted from root and surface on each environment's overlay\n * via {@link buildPlacesOverlay}.\n *\n * @param inputs - Per-environment folds, label map, and primary fold.\n * @returns The root `places` block, or `undefined` when the primary has\n * no place folds.\n */\nexport function buildRootPlaces(\n\tinputs: BuildRootPlacesInputs,\n): Record<string, PlaceEntry> | undefined {\n\tconst { folds, labels, primaryFold } = inputs;\n\tif (primaryFold.places.size === 0) {\n\t\treturn undefined;\n\t}\n\n\tconst folded: ReadonlyArray<LabeledFold> = [...folds.entries()].map(([name, fold]) => {\n\t\treturn { fold, label: labels.get(name) };\n\t});\n\treturn Object.fromEntries(\n\t\t[...primaryFold.places.entries()].map(([placeKey, primary]) => [\n\t\t\tplaceKey,\n\t\t\tbuildRootPlaceEntry(primary, { folds: folded, placeKey }),\n\t\t]),\n\t);\n}\n\n/**\n * Build the per-environment overlay for `places`, carrying each field only\n * when the environment's value diverges from the resolved root entry. When\n * the environment has a label, the overlay stores the unprefixed\n * `displayName` body so the deploy-time prefix system reapplies the bracket\n * cleanly without double-prefixing. Fields the environment omits are absent\n * from the overlay so the consumer's defu merge resolves them to the root's\n * (also absent) value rather than overwriting with `undefined`.\n *\n * @param inputs - Fold, label, and the already-built root `places` block.\n * @returns The overlay `places` block, or `undefined` when the fold has\n * no place entries.\n */\nexport function buildPlacesOverlay(\n\tinputs: BuildPlacesOverlayInputs,\n): Record<string, PlaceOverlayEntry> | undefined {\n\tconst { fold, label, rootPlaces } = inputs;\n\tif (fold.places.size === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn Object.fromEntries(\n\t\t[...fold.places.entries()].map(([placeKey, foldEntry]) => {\n\t\t\treturn [\n\t\t\t\tplaceKey,\n\t\t\t\tbuildPlaceOverlayEntry({\n\t\t\t\t\tfold: foldEntry,\n\t\t\t\t\tlabel,\n\t\t\t\t\trootEntry: rootPlaces?.[placeKey],\n\t\t\t\t}),\n\t\t\t];\n\t\t}),\n\t);\n}\n\nfunction placeFieldConsensus<F extends OptionalPlaceField>(\n\tinputs: ConsensusInputs<F>,\n): PlaceEntry[F] | undefined {\n\tconst values = inputs.folds.flatMap(({ fold }) => {\n\t\tconst entry = fold.places.get(inputs.placeKey)?.entry;\n\t\treturn entry === undefined ? [] : [entry[inputs.field]];\n\t});\n\n\tconst [first] = values;\n\treturn values.every((value) => Object.is(first, value)) ? first : undefined;\n}\n\nfunction resolveDisplayName(\n\tdisplayName: string | undefined,\n\tlabel: string | undefined,\n): string | undefined {\n\tif (displayName === undefined || label === undefined) {\n\t\treturn displayName;\n\t}\n\n\treturn extractDisplayNamePrefix(displayName).body;\n}\n\nfunction displayNameConsensus(inputs: DisplayNameConsensusInputs): string | undefined {\n\tconst values = inputs.folds.flatMap(({ fold, label }): ReadonlyArray<string | undefined> => {\n\t\tconst entry = fold.places.get(inputs.placeKey)?.entry;\n\t\treturn entry === undefined ? [] : [resolveDisplayName(entry.displayName, label)];\n\t});\n\n\tconst [first] = values;\n\treturn values.every((value) => Object.is(first, value)) ? first : undefined;\n}\n\nfunction buildRootPlaceEntry(\n\tprimary: PlaceFoldEntry,\n\tconsensusBase: {\n\t\treadonly folds: ReadonlyArray<LabeledFold>;\n\t\treadonly placeKey: string;\n\t},\n): PlaceEntry {\n\tconst description = placeFieldConsensus({ ...consensusBase, field: \"description\" });\n\tconst displayName = displayNameConsensus(consensusBase);\n\tconst serverSize = placeFieldConsensus({ ...consensusBase, field: \"serverSize\" });\n\treturn {\n\t\tfilePath: primary.entry.filePath,\n\t\t...(description !== undefined && { description }),\n\t\t...(displayName !== undefined && { displayName }),\n\t\t...(serverSize !== undefined && { serverSize }),\n\t};\n}\n\nfunction buildPlaceOverlayEntry(inputs: BuildPlaceOverlayEntryInputs): PlaceOverlayEntry {\n\tconst { fold, label, rootEntry } = inputs;\n\tconst { description, displayName: rawDisplayName, filePath, serverSize } = fold.entry;\n\tconst displayName = resolveDisplayName(rawDisplayName, label);\n\treturn {\n\t\tplaceId: fold.placeId,\n\t\t...(filePath !== rootEntry?.filePath && { filePath }),\n\t\t...(!Object.is(rootEntry?.description, description) && { description }),\n\t\t...(!Object.is(rootEntry?.displayName, displayName) && { displayName }),\n\t\t...(!Object.is(rootEntry?.serverSize, serverSize) && { serverSize }),\n\t};\n}\n","import type { DeveloperProductEntry, EnvironmentEntry } from \"../schema.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\nimport type { ProductFoldEntry } from \"./fold-products.ts\";\n\ntype ProductOverlayEntry = NonNullable<EnvironmentEntry[\"products\"]>[string];\n\ntype OptionalProductField = \"icon\" | \"isRegionalPricingEnabled\" | \"price\" | \"storePageEnabled\";\n\ninterface ConsensusInputs<F extends OptionalProductField> {\n\treadonly field: F;\n\treadonly folds: ReadonlyArray<EnvironmentFoldResult>;\n\treadonly productKey: string;\n}\n\ninterface FieldEqualInputs {\n\treadonly field: OptionalProductField;\n\treadonly left: DeveloperProductEntry[OptionalProductField];\n\treadonly right: DeveloperProductEntry[OptionalProductField];\n}\n\n/**\n * Project the per-environment product folds into bedrock's root `products`\n * block, giving each optional field (`icon`, `price`, `isRegionalPricingEnabled`,\n * `storePageEnabled`) a value only when every environment that owns the\n * product key agrees. Required fields (`name`, `description`) fall back to\n * the primary's values; divergence on those surfaces in each environment's\n * overlay. Optional fields that diverge stay off root so an environment that\n * omits an optional field does not silently inherit the primary's value\n * through defu's \"undefined treated as empty\" merge.\n *\n * @param folds - Per-environment fold results, keyed by environment name.\n * @param primaryFold - The chosen primary environment's fold; supplies the\n * product key list and the `name` / `description` fallbacks.\n * @returns The root `products` block, or `undefined` when the primary has\n * no product folds.\n */\nexport function buildRootProducts(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n\tprimaryFold: EnvironmentFoldResult,\n): Record<string, DeveloperProductEntry> | undefined {\n\tif (primaryFold.products.length === 0) {\n\t\treturn undefined;\n\t}\n\n\tconst folded = [...folds.values()];\n\treturn Object.fromEntries(\n\t\tprimaryFold.products.map(({ key, entry }) => [\n\t\t\tkey,\n\t\t\tbuildRootProductEntry(entry, { folds: folded, productKey: key }),\n\t\t]),\n\t);\n}\n\n/**\n * Build the per-environment overlay for `products`, carrying each field only\n * when the environment's value diverges from the resolved root entry. Fields\n * the environment omits are absent from the overlay so the consumer's defu\n * merge resolves them to the root's (also absent) value rather than\n * inheriting a primary value the environment never had.\n *\n * @param fold - The per-environment fold whose products are being overlaid.\n * @param rootProducts - The already-built root `products` block; per-product\n * field values from this map suppress overlay entries that match.\n * @returns The overlay `products` block, or `undefined` when no product\n * diverges from the root.\n */\nexport function buildProductsOverlay(\n\tfold: EnvironmentFoldResult,\n\trootProducts: Record<string, DeveloperProductEntry> | undefined,\n): Record<string, ProductOverlayEntry> | undefined {\n\tconst overlay: Record<string, ProductOverlayEntry> = {};\n\tfor (const { key, entry } of fold.products) {\n\t\tconst productOverlay = buildProductOverlayEntry(entry, rootProducts?.[key]);\n\t\tif (Object.keys(productOverlay).length > 0) {\n\t\t\toverlay[key] = productOverlay;\n\t\t}\n\t}\n\n\treturn Object.keys(overlay).length === 0 ? undefined : overlay;\n}\n\nfunction entryForKey(\n\tfold: EnvironmentFoldResult,\n\tproductKey: string,\n): DeveloperProductEntry | undefined {\n\treturn fold.products.find(({ key }) => key === productKey)?.entry;\n}\n\nfunction asIcon(\n\tvalue: DeveloperProductEntry[OptionalProductField],\n): Record<\"en-us\", string> | undefined {\n\treturn typeof value === \"object\" ? value : undefined;\n}\n\nfunction optionalFieldEqual(inputs: FieldEqualInputs): boolean {\n\tif (inputs.field === \"icon\") {\n\t\treturn Object.is(asIcon(inputs.left)?.[\"en-us\"], asIcon(inputs.right)?.[\"en-us\"]);\n\t}\n\n\treturn Object.is(inputs.left, inputs.right);\n}\n\nfunction productOptionalFieldConsensus<F extends OptionalProductField>(\n\tinputs: ConsensusInputs<F>,\n): DeveloperProductEntry[F] | undefined {\n\tconst values = inputs.folds.flatMap((fold): ReadonlyArray<DeveloperProductEntry[F]> => {\n\t\tconst entry = entryForKey(fold, inputs.productKey);\n\t\treturn entry === undefined ? [] : [entry[inputs.field]];\n\t});\n\n\tconst [first] = values;\n\treturn values.every((value) => {\n\t\treturn optionalFieldEqual({ field: inputs.field, left: first, right: value });\n\t})\n\t\t? first\n\t\t: undefined;\n}\n\nfunction buildRootProductEntry(\n\tprimaryEntry: ProductFoldEntry[\"entry\"],\n\tconsensusBase: {\n\t\treadonly folds: ReadonlyArray<EnvironmentFoldResult>;\n\t\treadonly productKey: string;\n\t},\n): DeveloperProductEntry {\n\tconst icon = productOptionalFieldConsensus({ ...consensusBase, field: \"icon\" });\n\tconst isRegionalPricingEnabled = productOptionalFieldConsensus({\n\t\t...consensusBase,\n\t\tfield: \"isRegionalPricingEnabled\",\n\t});\n\tconst price = productOptionalFieldConsensus({ ...consensusBase, field: \"price\" });\n\tconst isStorePageEnabled = productOptionalFieldConsensus({\n\t\t...consensusBase,\n\t\tfield: \"storePageEnabled\",\n\t});\n\n\treturn {\n\t\tname: primaryEntry.name,\n\t\tdescription: primaryEntry.description,\n\t\t...(icon !== undefined && { icon }),\n\t\t...(isRegionalPricingEnabled !== undefined && { isRegionalPricingEnabled }),\n\t\t...(price !== undefined && { price }),\n\t\t...(isStorePageEnabled !== undefined && { storePageEnabled: isStorePageEnabled }),\n\t};\n}\n\nfunction buildProductOverlayEntry(\n\tentry: ProductFoldEntry[\"entry\"],\n\trootEntry: DeveloperProductEntry | undefined,\n): ProductOverlayEntry {\n\treturn {\n\t\t...(entry.name !== rootEntry?.name && { name: entry.name }),\n\t\t...(entry.description !== rootEntry?.description && { description: entry.description }),\n\t\t...(!Object.is(rootEntry?.icon?.[\"en-us\"], entry.icon?.[\"en-us\"]) && { icon: entry.icon }),\n\t\t...(!Object.is(rootEntry?.price, entry.price) && { price: entry.price }),\n\t\t...(!Object.is(rootEntry?.isRegionalPricingEnabled, entry.isRegionalPricingEnabled) && {\n\t\t\tisRegionalPricingEnabled: entry.isRegionalPricingEnabled,\n\t\t}),\n\t\t...(!Object.is(rootEntry?.storePageEnabled, entry.storePageEnabled) && {\n\t\t\tstorePageEnabled: entry.storePageEnabled,\n\t\t}),\n\t};\n}\n","import type { EnvironmentEntry, UniverseEntry } from \"../schema.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\n\ntype UniverseOverlay = NonNullable<EnvironmentEntry[\"universe\"]>;\n\ninterface UniverseOverlayContext {\n\treadonly fold: EnvironmentFoldResult;\n\treadonly hasDivergentUniverseIds: boolean;\n}\n\n/**\n * Decide whether a fold map carries more than one distinct universeId,\n * which forces the migrator to omit `universeId` from the root universe\n * block and write it on every environment overlay (the schema-level XOR\n * rule rejects the same `universeId` appearing in both places).\n *\n * @param folds - Per-environment fold results.\n * @returns `true` when at least two folds carry different universeIds.\n */\nexport function hasDivergentUniverseIds(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n): boolean {\n\tconst distinctIds = new Set(\n\t\t[...folds.values()].flatMap((fold) => {\n\t\t\treturn fold.universe === undefined ? [] : [fold.universe.entry.universeId];\n\t\t}),\n\t);\n\treturn distinctIds.size > 1;\n}\n\n/**\n * Project the chosen primary environment's universe entry onto bedrock's\n * root `universe` block. Strips `universeId` when universes diverge across\n * environments so the schema-level XOR rule stays satisfied; the per-env\n * overlay carries the id in that case (see {@link buildUniverseOverlay}).\n *\n * @param primaryFold - The chosen primary environment's fold; supplies\n * the universe entry whose shared fields land on root.\n * @param shouldOmitUniverseId - `true` when universes diverge across the\n * input fold map (typically the output of {@link hasDivergentUniverseIds}).\n * @returns The root `universe` block, or `undefined` when the primary has\n * no universe fold or when divergent universes leave nothing to land\n * at root.\n */\nexport function buildRootUniverse(\n\tprimaryFold: EnvironmentFoldResult,\n\tshouldOmitUniverseId: boolean,\n): undefined | UniverseEntry {\n\tconst primaryUniverse = primaryFold.universe?.entry;\n\tif (primaryUniverse === undefined) {\n\t\treturn undefined;\n\t}\n\n\tif (!shouldOmitUniverseId) {\n\t\treturn primaryUniverse;\n\t}\n\n\tconst { universeId: _omittedUniverseId, ...sharedFields } = primaryUniverse;\n\tif (Object.keys(sharedFields).length === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn sharedFields;\n}\n\n/**\n * Build the per-environment overlay for `universe`. Returns the env's\n * `universeId` when universes diverge across environments (schema-level\n * XOR demands one universeId source per env); otherwise returns\n * `undefined` so the env inherits the root universe block.\n *\n * @param context - The fold to overlay onto and the divergence flag.\n * @returns The overlay, or `undefined` when no per-env overlay is needed.\n */\nexport function buildUniverseOverlay(context: UniverseOverlayContext): undefined | UniverseOverlay {\n\tconst foldUniverse = context.fold.universe;\n\tif (foldUniverse === undefined) {\n\t\treturn undefined;\n\t}\n\n\tif (context.hasDivergentUniverseIds) {\n\t\treturn { universeId: foldUniverse.entry.universeId };\n\t}\n\n\treturn undefined;\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport type {\n\tConfig,\n\tDeveloperProductEntry,\n\tEnvironmentEntry,\n\tGamePassEntry,\n\tPlaceEntry,\n\tUniverseEntry,\n} from \"../schema.ts\";\nimport { computeEnvironmentLabel } from \"./environment-label.ts\";\nimport { extractDisplayNamePrefix } from \"./extract-display-name-prefix.ts\";\nimport { collectMissingResourceWarnings } from \"./factorize-environments-warnings.ts\";\nimport { buildPlacesOverlay, buildRootPlaces } from \"./factorize-places.ts\";\nimport { buildProductsOverlay, buildRootProducts } from \"./factorize-products.ts\";\nimport {\n\tbuildRootUniverse,\n\tbuildUniverseOverlay,\n\thasDivergentUniverseIds,\n} from \"./factorize-universe.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\nimport type { MigrateError, MigrationWarning } from \"./migration-report.ts\";\n\ninterface FactorizeInputs {\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\treadonly primaryEnvironment: string | undefined;\n}\n\ninterface FactorizeResult {\n\treadonly config: Config;\n\treadonly primaryEnvironment: string;\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ntype PassOverlayEntry = NonNullable<EnvironmentEntry[\"passes\"]>[string];\n\ninterface ResolvedPrimary {\n\treadonly name: string;\n\treadonly fold: EnvironmentFoldResult;\n}\n\ninterface OverlayContext {\n\treadonly hasDivergentUniverseIds: boolean;\n\treadonly labels: ReadonlyMap<string, string | undefined>;\n\treadonly primary: EnvironmentFoldResult | undefined;\n\treadonly rootPlaces: Record<string, PlaceEntry> | undefined;\n\treadonly rootProducts: Record<string, DeveloperProductEntry> | undefined;\n}\n\ninterface EnvironmentEntryInputs {\n\treadonly context: OverlayContext;\n\treadonly fold: EnvironmentFoldResult;\n\treadonly label: string | undefined;\n}\n\ninterface BuildConfigInputs {\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\treadonly primaryFold: EnvironmentFoldResult;\n\treadonly primaryName: string;\n}\n\ninterface LooseConfigForBuild {\n\tenvironments: Record<string, EnvironmentEntry>;\n\tpasses?: Record<string, GamePassEntry>;\n\tplaces?: Record<string, PlaceEntry>;\n\tproducts?: Record<string, DeveloperProductEntry>;\n\tuniverse?: UniverseEntry;\n}\n\n/**\n * Project per-environment fold results into a single bedrock `Config` by\n * factoring the chosen primary environment's resolved values up to the root\n * and recording per-environment overlays for fields that diverge from the\n * primary. Pure: no I/O.\n *\n * Primary selection: a single-environment input auto-picks; any other shape\n * requires `inputs.primaryEnvironment` and returns\n * `Err({ kind: \"primaryEnvironmentRequired\" })` when omitted, or\n * `Err({ kind: \"primaryEnvironmentNotFound\" })` when the supplied name is\n * not in the fold map.\n *\n * @param inputs - Per-environment folds and the optional primary hint.\n * @returns `Ok` with the factorized config on success, or `Err` with a\n * discriminated `MigrateError` on primary-selection failure.\n */\nexport function factorizeEnvironments(\n\tinputs: FactorizeInputs,\n): Result<FactorizeResult, MigrateError> {\n\tconst primaryResult = pickPrimary(inputs.folds, inputs.primaryEnvironment);\n\tif (!primaryResult.success) {\n\t\treturn primaryResult;\n\t}\n\n\tconst primary = primaryResult.data;\n\treturn {\n\t\tdata: {\n\t\t\tconfig: buildConfig({\n\t\t\t\tfolds: inputs.folds,\n\t\t\t\tprimaryFold: primary.fold,\n\t\t\t\tprimaryName: primary.name,\n\t\t\t}),\n\t\t\tprimaryEnvironment: primary.name,\n\t\t\twarnings: collectMissingResourceWarnings({ folds: inputs.folds, primary }),\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction pickPrimary(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n\tprimary: string | undefined,\n): Result<ResolvedPrimary, MigrateError> {\n\tconst available = [...folds.keys()];\n\tconst requested = primary ?? (available.length === 1 ? available[0] : undefined);\n\tif (requested === undefined) {\n\t\treturn { err: { available, kind: \"primaryEnvironmentRequired\" }, success: false };\n\t}\n\n\tconst fold = folds.get(requested);\n\tif (fold === undefined) {\n\t\treturn {\n\t\t\terr: { available, kind: \"primaryEnvironmentNotFound\", primary: requested },\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: { name: requested, fold }, success: true };\n}\n\nfunction buildRootPasses(\n\tprimaryFold: EnvironmentFoldResult,\n): Record<string, GamePassEntry> | undefined {\n\tif (primaryFold.passes.length === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn Object.fromEntries(primaryFold.passes.map(({ key, entry }) => [key, entry]));\n}\n\nfunction buildFullPassOverlay(entry: GamePassEntry): PassOverlayEntry {\n\treturn {\n\t\tname: entry.name,\n\t\tdescription: entry.description,\n\t\ticon: entry.icon,\n\t\t...(entry.price !== undefined && { price: entry.price }),\n\t};\n}\n\nfunction buildPassOverlayEntry(\n\tentry: GamePassEntry,\n\tprimary: GamePassEntry,\n): PassOverlayEntry | undefined {\n\tconst overlay: PassOverlayEntry = {};\n\tif (!Object.is(primary.name, entry.name)) {\n\t\toverlay.name = entry.name;\n\t}\n\n\tif (!Object.is(primary.description, entry.description)) {\n\t\toverlay.description = entry.description;\n\t}\n\n\tif (!Object.is(primary.icon[\"en-us\"], entry.icon[\"en-us\"])) {\n\t\toverlay.icon = entry.icon;\n\t}\n\n\tif (!Object.is(primary.price, entry.price)) {\n\t\toverlay.price = entry.price;\n\t}\n\n\treturn Object.keys(overlay).length === 0 ? undefined : overlay;\n}\n\nfunction buildPassesOverlay(\n\tfold: EnvironmentFoldResult,\n\tprimary: EnvironmentFoldResult | undefined,\n): Record<string, PassOverlayEntry> | undefined {\n\tconst primaryByKey = new Map<string, GamePassEntry>(\n\t\tprimary?.passes.map(({ key, entry }) => [key, entry]),\n\t);\n\tconst overlay: Record<string, PassOverlayEntry> = {};\n\tfor (const { key, entry } of fold.passes) {\n\t\tconst primaryEntry = primaryByKey.get(key);\n\t\tconst passOverlay =\n\t\t\tprimaryEntry === undefined\n\t\t\t\t? buildFullPassOverlay(entry)\n\t\t\t\t: buildPassOverlayEntry(entry, primaryEntry);\n\t\tif (passOverlay !== undefined) {\n\t\t\toverlay[key] = passOverlay;\n\t\t}\n\t}\n\n\treturn Object.keys(overlay).length === 0 ? undefined : overlay;\n}\n\nfunction buildEnvironmentEntry(inputs: EnvironmentEntryInputs): EnvironmentEntry {\n\tconst { context, fold, label } = inputs;\n\tconst passes = buildPassesOverlay(fold, context.primary);\n\tconst places = buildPlacesOverlay({ fold, label, rootPlaces: context.rootPlaces });\n\tconst products = buildProductsOverlay(fold, context.rootProducts);\n\tconst universe = buildUniverseOverlay({\n\t\tfold,\n\t\thasDivergentUniverseIds: context.hasDivergentUniverseIds,\n\t});\n\treturn {\n\t\t...(label !== undefined && { label }),\n\t\t...(passes !== undefined && { passes }),\n\t\t...(places !== undefined && { places }),\n\t\t...(products !== undefined && { products }),\n\t\t...(universe !== undefined && { universe }),\n\t};\n}\n\nfunction buildEnvironmentEntries(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n\tcontext: OverlayContext,\n): Record<string, EnvironmentEntry> {\n\treturn Object.fromEntries(\n\t\t[...folds].map(([name, fold]) => [\n\t\t\tname,\n\t\t\tbuildEnvironmentEntry({ context, fold, label: context.labels.get(name) }),\n\t\t]),\n\t);\n}\n\nfunction buildEnvironmentLabels(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n): ReadonlyMap<string, string | undefined> {\n\treturn new Map([...folds].map(([name, fold]) => [name, computeEnvironmentLabel(fold)]));\n}\n\nfunction stripDisplayNamePrefix(\n\tuniverse: undefined | UniverseEntry,\n\tlabel: string | undefined,\n): undefined | UniverseEntry {\n\tif (universe === undefined || label === undefined || universe.displayName === undefined) {\n\t\treturn universe;\n\t}\n\n\treturn { ...universe, displayName: extractDisplayNamePrefix(universe.displayName).body };\n}\n\nfunction buildConfig(inputs: BuildConfigInputs): Config {\n\tconst { folds, primaryFold, primaryName } = inputs;\n\tconst labels = buildEnvironmentLabels(folds);\n\tconst places = buildRootPlaces({ folds, labels, primaryFold });\n\tconst products = buildRootProducts(folds, primaryFold);\n\tconst shouldOmitRootUniverseId = hasDivergentUniverseIds(folds);\n\tconst environments = buildEnvironmentEntries(folds, {\n\t\thasDivergentUniverseIds: shouldOmitRootUniverseId,\n\t\tlabels,\n\t\tprimary: primaryFold,\n\t\trootPlaces: places,\n\t\trootProducts: products,\n\t});\n\tconst passes = buildRootPasses(primaryFold);\n\tconst universe = stripDisplayNamePrefix(\n\t\tbuildRootUniverse(primaryFold, shouldOmitRootUniverseId),\n\t\tlabels.get(primaryName),\n\t);\n\tconst config: LooseConfigForBuild = {\n\t\tenvironments,\n\t\t...(passes !== undefined && { passes }),\n\t\t...(places !== undefined && { places }),\n\t\t...(products !== undefined && { products }),\n\t\t...(universe !== undefined && { universe }),\n\t};\n\t// Precondition for the cast: `hasDivergentUniverseIds` decides\n\t// `shouldOmitRootUniverseId` and `buildRootUniverse` and\n\t// `buildUniverseOverlay` honour it, so the constructed config always\n\t// lands in one arm of the discriminated `Config` union (root has\n\t// `universeId` and no env carries one, or every env carries one and\n\t// root omits it).\n\treturn config as unknown as Config;\n}\n","import { asResourceKey, asRobloxAssetId, asSha256Hex, isSha256Hex } from \"../../types/ids.ts\";\nimport type { ResourceKey, Sha256Hex } from \"../../types/ids.ts\";\nimport type { GamePassOutputs } from \"../resources.ts\";\nimport type { GamePassEntry } from \"../schema.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst PASS_KIND = \"pass\";\n\n/**\n * Folded representation of one Mantle `pass_<k>` resource.\n *\n * `entry` carries the bedrock `Config.passes[<k>]` shape (omitting\n * `iconFileHashes` because the hashes are recomputed from disk by the shell\n * before they land on state). `outputs` carries the Roblox-assigned\n * identifiers for the pass and its icon. `mantleIconFileHashes` preserves\n * the hashes recorded by Mantle so the shell can fall back to them when the\n * icon file is missing on disk. `mantlePath` roots warnings at the\n * resource so the report is searchable.\n */\nexport interface PassFoldEntry {\n\t/** User-supplied Mantle key, branded as a bedrock `ResourceKey`. */\n\treadonly key: ResourceKey;\n\t/** Bedrock `Config.passes[<k>]` block populated from the pass resource. */\n\treadonly entry: GamePassEntry;\n\t/** Locale-keyed Mantle-recorded icon hashes; retained as a fallback for hash recomputation. */\n\treadonly mantleIconFileHashes: Record<\"en-us\", Sha256Hex>;\n\t/** Resource-rooted Mantle path (`pass_<k>`) used to anchor warnings. */\n\treadonly mantlePath: string;\n\t/** Roblox-assigned identifiers carried into `BedrockState.resources[*].outputs`. */\n\treadonly outputs: GamePassOutputs;\n}\n\n/**\n * Output of folding the `pass_<k>` Mantle resources of one environment\n * into bedrock-shaped game-pass entries.\n *\n * `passes` carries one record per well-formed `pass_<k>` resource;\n * malformed resources are dropped silently in this slice (warning-emitting\n * variants land alongside their consuming rules). `warnings` accumulates\n * per-rule diagnostics; the slice emits an empty list because no\n * interpretive rules have been wired yet.\n */\ninterface PassesFoldResult {\n\t/** One folded entry per well-formed Mantle `pass_<k>` resource. */\n\treadonly passes: ReadonlyArray<PassFoldEntry>;\n\t/** Per-rule diagnostics; empty in this slice, populated as rules land. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface PassInputs {\n\treadonly name: string;\n\treadonly description: string;\n\treadonly iconFileHash: Sha256Hex;\n\treadonly iconFilePath: string;\n\treadonly price: number | undefined;\n}\n\ninterface PassOutputsRaw {\n\treadonly assetId: string;\n\treadonly iconAssetId: string;\n}\n\n/**\n * Fold the `pass_<k>` Mantle resources of one environment into a list\n * of bedrock game-pass entries plus matching outputs.\n *\n * Each well-formed `pass_<k>.inputs.{name, description, iconFilePath,\n * price}` becomes the corresponding `Config.passes[<k>]` entry; the\n * matching `pass_<k>.outputs.{assetId, iconAssetId}` becomes the\n * `BedrockState` resource's `outputs`. The Mantle-recorded\n * `iconFileHash` is preserved on the result so the shell can fall back\n * to it when the icon file is missing on disk.\n *\n * Resources whose payload is malformed (non-object, missing required\n * string field, non-`Sha256Hex` hash, missing output IDs) are dropped\n * silently in this slice; warning-emitting variants land alongside the\n * specific interpretive rules that need them.\n *\n * @param resources - Resource list for one Mantle environment.\n * @returns The folded pass entries plus an aggregated warnings list.\n */\nexport function foldPasses(resources: ReadonlyArray<MantleResource>): PassesFoldResult {\n\tconst passes = resources.flatMap((resource): ReadonlyArray<PassFoldEntry> => {\n\t\tif (resource.kind !== PASS_KIND) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst folded = foldOnePass(resource);\n\t\treturn folded === undefined ? [] : [folded];\n\t});\n\n\treturn { passes, warnings: [] };\n}\n\nfunction buildEntry(inputs: PassInputs): GamePassEntry {\n\tconst base = {\n\t\tname: inputs.name,\n\t\tdescription: inputs.description,\n\t\ticon: { \"en-us\": inputs.iconFilePath },\n\t};\n\n\treturn inputs.price === undefined ? base : { ...base, price: inputs.price };\n}\n\nfunction isObjectPayload(value: unknown): value is Record<string, unknown> {\n\treturn Object.prototype.toString.call(value) === \"[object Object]\";\n}\n\nfunction readString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction readPrice(raw: Record<string, unknown>): number | undefined {\n\tconst candidate = raw[\"price\"];\n\treturn typeof candidate === \"number\" ? candidate : undefined;\n}\n\nfunction readPassInputs(raw: unknown): PassInputs | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst name = readString(raw[\"name\"]);\n\tconst description = readString(raw[\"description\"]);\n\tconst iconFilePath = readString(raw[\"iconFilePath\"]);\n\tconst iconFileHashRaw = readString(raw[\"iconFileHash\"]);\n\tif (\n\t\tname === undefined ||\n\t\tdescription === undefined ||\n\t\ticonFilePath === undefined ||\n\t\ticonFileHashRaw === undefined ||\n\t\t!isSha256Hex(iconFileHashRaw)\n\t) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tname,\n\t\tdescription,\n\t\ticonFileHash: asSha256Hex(iconFileHashRaw),\n\t\ticonFilePath,\n\t\tprice: readPrice(raw),\n\t};\n}\n\nfunction coerceRobloxId(value: unknown): string | undefined {\n\tif (typeof value === \"string\") {\n\t\treturn value;\n\t}\n\n\tif (Number.isInteger(value)) {\n\t\treturn String(value);\n\t}\n\n\treturn undefined;\n}\n\nfunction readPassOutputs(raw: unknown): PassOutputsRaw | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst assetId = coerceRobloxId(raw[\"assetId\"]);\n\tconst iconAssetId = coerceRobloxId(raw[\"iconAssetId\"]);\n\tif (assetId === undefined || iconAssetId === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { assetId, iconAssetId };\n}\n\nfunction foldOnePass(resource: MantleResource): PassFoldEntry | undefined {\n\tconst inputs = readPassInputs(resource.inputs);\n\tif (inputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst outputs = readPassOutputs(resource.outputs);\n\tif (outputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tkey: asResourceKey(resource.key),\n\t\tentry: buildEntry(inputs),\n\t\tmantleIconFileHashes: { \"en-us\": inputs.iconFileHash },\n\t\tmantlePath: `${PASS_KIND}_${resource.key}`,\n\t\toutputs: {\n\t\t\tassetId: asRobloxAssetId(outputs.assetId),\n\t\t\ticonAssetIds: { \"en-us\": asRobloxAssetId(outputs.iconAssetId) },\n\t\t},\n\t};\n}\n","import type { UniverseOutputs } from \"../resources.ts\";\nimport type { UniverseEntry } from \"../schema.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\n\n/**\n * Output of one fold rule contributing to the eventual `UniverseEntry`.\n * The orchestrator (`foldUniverse`) composes fragments by spreading\n * `entryFragment` into the final entry, spreading `outputsFragment` into\n * the matching `BedrockState` resource's `outputs`, and concatenating\n * `warnings`. Most rules contribute only to the entry; rules that surface\n * Roblox-assigned identifiers populate `outputsFragment`. The\n * `mergeFragment` helper, used inside individual fold modules to combine\n * sub-fragments, only merges `entryFragment` and `warnings` because no\n * rule that uses it currently produces outputs.\n */\nexport interface FoldFragment {\n\t/**\n\t * Subset of universe fields this rule populated (empty when the rule\n\t * did not fire). `universeId` is excluded because the orchestrator\n\t * seeds it from the experience resource's outputs; no fold rule\n\t * contributes to it.\n\t */\n\treadonly entryFragment: UniverseEntryFragment;\n\t/** Subset of universe outputs this rule populated; absent when the rule contributes no Roblox-assigned identifiers. */\n\treadonly outputsFragment?: Partial<UniverseOutputs>;\n\t/** Diagnostics this rule emitted. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\n/**\n * Subset of {@link UniverseEntry} that any individual fold rule may\n * populate. `universeId` is excluded because the orchestrator seeds it\n * from the experience resource's outputs; no fold rule contributes to it.\n */\ntype UniverseEntryFragment = {\n\treadonly [Key in Exclude<keyof UniverseEntry, \"universeId\">]?: UniverseEntry[Key];\n};\n\n/** Sentinel value for fold rules that produced neither output nor warnings. */\nexport const EMPTY_FRAGMENT: FoldFragment = { entryFragment: {}, warnings: [] };\n\n/**\n * One row in a static blocked-field table: the Mantle field name and the\n * upstream limitation reason that the migrator surfaces verbatim in the\n * warning's `reason` slot. Used by folds that emit one `blocked`\n * `MigrationWarning` per non-`undefined` legacy-only field.\n */\nexport interface BlockedFieldRule {\n\t/** Mantle input key to test for non-`undefined`. */\n\treadonly field: string;\n\t/** Human-readable reason naming the upstream limitation. */\n\treadonly reason: string;\n}\n\n/**\n * Required fields for an `interpretive` warning, gathered as a single\n * argument so this builder stays under the project's max-params cap.\n */\ninterface InterpretiveWarningSpec {\n\t/** Path the migrator wrote in the bedrock config. */\n\treadonly bedrockPath: string;\n\t/** Resource-rooted Mantle path the rule consumed. */\n\treadonly mantlePath: string;\n\t/** Stable rule identifier audited in the migration report. */\n\treadonly rule: string;\n}\n\n/**\n * Narrow a value to a plain object payload (rejects arrays, null, primitives).\n *\n * @param value - Value to test.\n * @returns `true` when `value` is a plain object record.\n */\nexport function isObjectPayload(value: unknown): value is Record<string, unknown> {\n\treturn Object.prototype.toString.call(value) === \"[object Object]\";\n}\n\n/**\n * Coerce a Mantle-recorded Roblox identifier to a string. YAML can carry\n * IDs as either a quoted string or a bare integer; both are accepted.\n * Anything else (null, float, object) returns `undefined`.\n *\n * @param value - Raw value pulled from a Mantle resource's outputs.\n * @returns The stringified ID, or `undefined` when the value is not a valid wire shape.\n */\nexport function coerceRobloxId(value: unknown): string | undefined {\n\tif (typeof value === \"string\") {\n\t\treturn value;\n\t}\n\n\tif (Number.isInteger(value)) {\n\t\treturn String(value);\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Combine two fragments, with the right-hand side overriding overlapping\n * `entryFragment` keys and its `warnings` appended after the left's.\n * Does not merge `outputsFragment`; outputs composition is handled by\n * `foldUniverse` across the whole fragment list, not by this helper.\n *\n * @param left - Earlier fragment.\n * @param right - Later fragment whose entry-fragment keys win on conflict.\n * @returns The merged fragment.\n */\nexport function mergeFragment(left: FoldFragment, right: FoldFragment): FoldFragment {\n\treturn {\n\t\tentryFragment: { ...left.entryFragment, ...right.entryFragment },\n\t\twarnings: [...left.warnings, ...right.warnings],\n\t};\n}\n\n/**\n * Build an `interpretive` `MigrationWarning`. Used by every fold rule that\n * applies a documented mapping; the report consumer narrows on `kind`.\n *\n * @param spec - The bedrock path, mantle path, and rule identifier.\n * @returns A `MigrationWarning` with `kind: \"interpretive\"`.\n */\nexport function interpretiveWarning(spec: InterpretiveWarningSpec): MigrationWarning {\n\treturn {\n\t\tbedrockPath: spec.bedrockPath,\n\t\tkind: \"interpretive\",\n\t\tmantlePath: spec.mantlePath,\n\t\trule: spec.rule,\n\t};\n}\n\n/**\n * Build a `blocked` `MigrationWarning`. Used when no Open Cloud writable\n * endpoint exists for the field the migrator encountered.\n *\n * @param mantlePath - Resource-rooted Mantle path of the blocked field.\n * @param reason - Human-readable reason describing what is unsupported.\n * @returns A `MigrationWarning` with `kind: \"blocked\"`.\n */\nexport function blockedWarning(mantlePath: string, reason: string): MigrationWarning {\n\treturn { kind: \"blocked\", mantlePath, reason };\n}\n\n/**\n * Build an `ambiguous` `MigrationWarning`. Used when a value is mappable\n * but unsafe to act on without user input; the hint guides the next step.\n *\n * @param mantlePath - Resource-rooted Mantle path of the ambiguous field.\n * @param hint - Human-readable suggestion for resolving the ambiguity.\n * @returns A `MigrationWarning` with `kind: \"ambiguous\"`.\n */\nexport function ambiguousWarning(mantlePath: string, hint: string): MigrationWarning {\n\treturn { hint, kind: \"ambiguous\", mantlePath };\n}\n","import { type BlockedFieldRule, blockedWarning, isObjectPayload } from \"./fold-universe-shared.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst PLACE_CONFIGURATION_KIND = \"placeConfiguration\";\n\n/**\n * `placeConfiguration_<k>` fields with no Open Cloud writable endpoint.\n * `name`, `description`, and `maxPlayerCount` are intentionally omitted:\n * `foldDisplayName` and `foldPlaces` fold them into the bedrock config.\n */\nconst BLOCKED_FIELDS: ReadonlyArray<BlockedFieldRule> = [\n\t{\n\t\tfield: \"allowCopying\",\n\t\treason: \"placeConfiguration.allowCopying has no Open Cloud equivalent\",\n\t},\n\t{\n\t\tfield: \"socialSlotType\",\n\t\treason: \"placeConfiguration social-slot config has no Open Cloud equivalent\",\n\t},\n\t{\n\t\tfield: \"customSocialSlotsCount\",\n\t\treason: \"placeConfiguration social-slot config has no Open Cloud equivalent\",\n\t},\n];\n\n/**\n * Emit one `blocked` `MigrationWarning` per non-`undefined` legacy-only\n * field on every `placeConfiguration_<k>` resource. The Mantle null\n * sentinel (`~`) is normalized to `undefined` by `parseState`, so absent\n * and null fields both skip.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns One warning per blocked field present, ordered by resource\n * list then by the static field table.\n */\nexport function foldBlockedPlaceFields(\n\tresources: ReadonlyArray<MantleResource>,\n): ReadonlyArray<MigrationWarning> {\n\treturn resources.flatMap((resource) => {\n\t\tif (resource.kind !== PLACE_CONFIGURATION_KIND || !isObjectPayload(resource.inputs)) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst { key, inputs } = resource;\n\t\treturn BLOCKED_FIELDS.flatMap((rule) => {\n\t\t\tif (inputs[rule.field] === undefined) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\treturn [\n\t\t\t\tblockedWarning(`${PLACE_CONFIGURATION_KIND}_${key}.${rule.field}`, rule.reason),\n\t\t\t];\n\t\t});\n\t});\n}\n","import { isSha256Hex, type Sha256Hex } from \"../../types/ids.ts\";\nimport type { PlaceOutputs } from \"../resources.ts\";\nimport type { PlaceEntry } from \"../schema.ts\";\nimport { foldBlockedPlaceFields } from \"./fold-blocked-place-fields.ts\";\nimport { interpretiveWarning, isObjectPayload } from \"./fold-universe-shared.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst PLACE_KIND = \"place\";\nconst PLACE_FILE_KIND = \"placeFile\";\nconst PLACE_CONFIGURATION_KIND = \"placeConfiguration\";\n\n/**\n * Folded place data for one Mantle resource key.\n *\n * `entry` populates the bedrock root `Config.places.<key>` block (currently\n * just `filePath`). `placeId` is the Roblox-assigned place ID Mantle stored\n * under `place_<k>.outputs.assetId`; the migrator threads it onto the\n * matching per-environment overlay (`environments[env].places.<key>`).\n * `fileHash` is the SHA-256 digest Mantle recorded under\n * `placeFile_<k>.inputs.fileHash`; the shell consumes it as the fallback\n * value when the migrator cannot recompute the hash from disk. `outputs`\n * carries the auto-incrementing publish version number Mantle stored under\n * `placeFile_<k>.outputs.version`.\n */\nexport interface PlaceFoldEntry {\n\t/** Bedrock root `Config.places.<key>` body (currently `filePath` only). */\n\treadonly entry: PlaceEntry;\n\t/** Mantle-recorded SHA-256 hex digest of the place file (fallback for hash recomputation). */\n\treadonly fileHash: Sha256Hex;\n\t/** Roblox-assigned identifiers carried into `BedrockState.resources[*].outputs`. */\n\treadonly outputs: PlaceOutputs;\n\t/** Roblox-assigned place ID copied from `place_<key>.outputs.assetId`. */\n\treadonly placeId: string;\n}\n\n/**\n * Output of folding the place-related Mantle resources of one environment\n * into bedrock's `places` shape. Each Mantle place key produces one entry\n * in `entries`; orphans (a `place_<k>` without a `placeFile_<k>`, or vice\n * versa) emit one `ambiguous` warning each instead of being projected into\n * `entries`.\n */\ninterface PlaceFoldResult {\n\t/** Folded entries keyed by Mantle's place key (the suffix after the first `_`). */\n\treadonly entries: ReadonlyMap<string, PlaceFoldEntry>;\n\t/** Per-rule diagnostics: orphan resources surface as `ambiguous` warnings. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface PlaceOutputsRaw {\n\treadonly assetId: string;\n}\n\ninterface PlaceFileInputsRaw {\n\treadonly fileHash: Sha256Hex;\n\treadonly filePath: string;\n}\n\ninterface PlaceFileOutputsRaw {\n\treadonly version: number;\n}\n\ninterface PlaceConfigFoldResult {\n\treadonly entry: PlaceFoldEntry;\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface ApplyPlaceConfigFieldsInputs {\n\treadonly key: string;\n\treadonly configResource: MantleResource | undefined;\n\treadonly folded: PlaceFoldEntry;\n\treadonly isStart: boolean;\n}\n\ninterface PlaceConfigFragmentRule {\n\treadonly bedrockField: string;\n\treadonly mantleField: string;\n\treadonly rule: string;\n}\n\ninterface PlaceConfigFragment {\n\treadonly entry: Partial<PlaceEntry>;\n\treadonly warnings: ReadonlyArray<PlaceConfigFragmentRule>;\n}\n\ninterface PlaceConfigRule {\n\treadonly bedrockField: keyof PlaceEntry;\n\treadonly mantleField: string;\n\treadonly read: (value: unknown) => number | string | undefined;\n\treadonly rule: string;\n}\n\ninterface PlaceBuckets {\n\treadonly placeConfigurations: Map<string, MantleResource>;\n\treadonly placeFiles: Map<string, MantleResource>;\n\treadonly places: Map<string, MantleResource>;\n}\n\n/**\n * Fold the place-related Mantle resources of one environment into a map of\n * `PlaceFoldEntry` plus accompanying warnings. Pairs each `place_<k>`\n * with the matching `placeFile_<k>` by key; an unmatched resource on\n * either side emits an `ambiguous` warning carrying the orphan's\n * `mantlePath` and a hint instructing the user to verify their Mantle\n * state.\n *\n * Matched pairs whose payloads fail shape validation (missing or\n * non-numeric `assetId`, missing `filePath`, malformed `fileHash`, etc.)\n * are dropped silently rather than surfacing as warnings, mirroring the\n * malformed-input handling in `foldUniverse`. A `malformed` warning kind\n * spanning the per-kind folds is left to a follow-up slice.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns The folded entries plus orphan warnings.\n */\nexport function foldPlaces(resources: ReadonlyArray<MantleResource>): PlaceFoldResult {\n\tconst buckets = bucketByKind(resources);\n\tconst matched = collectMatchedFolds(buckets);\n\tconst orphans = collectOrphanWarnings(buckets);\n\treturn {\n\t\tentries: matched.entries,\n\t\twarnings: [...matched.warnings, ...orphans, ...foldBlockedPlaceFields(resources)],\n\t};\n}\n\nfunction collectMatchedFolds(buckets: PlaceBuckets): PlaceFoldResult {\n\tconst { placeConfigurations, placeFiles, places } = buckets;\n\tconst entries = new Map<string, PlaceFoldEntry>();\n\tconst warnings: Array<MigrationWarning> = [];\n\tfor (const [key, placeResource] of places) {\n\t\tconst fileResource = placeFiles.get(key);\n\t\tif (fileResource === undefined) {\n\t\t\twarnings.push(orphanWarning(PLACE_KIND, key));\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst folded = mergeMatchedPair(placeResource, fileResource);\n\t\tif (folded === undefined) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst configFold = applyPlaceConfigFields({\n\t\t\tkey,\n\t\t\tconfigResource: placeConfigurations.get(key),\n\t\t\tfolded,\n\t\t\tisStart: isStartPlace(placeResource),\n\t\t});\n\t\tentries.set(key, configFold.entry);\n\t\twarnings.push(...configFold.warnings);\n\t}\n\n\treturn { entries, warnings };\n}\n\nfunction collectOrphanWarnings(buckets: PlaceBuckets): ReadonlyArray<MigrationWarning> {\n\tconst { placeConfigurations, placeFiles, places } = buckets;\n\tconst warnings: Array<MigrationWarning> = [];\n\tfor (const [key] of placeFiles) {\n\t\tif (!places.has(key)) {\n\t\t\twarnings.push(orphanWarning(PLACE_FILE_KIND, key));\n\t\t}\n\t}\n\n\tfor (const [key] of placeConfigurations) {\n\t\tif (!places.has(key) || !placeFiles.has(key)) {\n\t\t\twarnings.push(placeConfigOrphanWarning(key));\n\t\t}\n\t}\n\n\treturn warnings;\n}\n\nfunction bucketByKind(resources: ReadonlyArray<MantleResource>): PlaceBuckets {\n\tconst places = new Map<string, MantleResource>();\n\tconst placeFiles = new Map<string, MantleResource>();\n\tconst placeConfigurations = new Map<string, MantleResource>();\n\tfor (const resource of resources) {\n\t\tswitch (resource.kind) {\n\t\t\tcase PLACE_CONFIGURATION_KIND: {\n\t\t\t\tplaceConfigurations.set(resource.key, resource);\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase PLACE_FILE_KIND: {\n\t\t\t\tplaceFiles.set(resource.key, resource);\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase PLACE_KIND: {\n\t\t\t\tplaces.set(resource.key, resource);\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// No default\n\t\t}\n\t}\n\n\treturn { placeConfigurations, placeFiles, places };\n}\n\nfunction readString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction readPositiveInteger(value: unknown): number | undefined {\n\tif (typeof value !== \"number\" || !Number.isInteger(value) || value <= 0) {\n\t\treturn undefined;\n\t}\n\n\treturn value;\n}\n\nconst ALWAYS_RULES: ReadonlyArray<PlaceConfigRule> = [\n\t{\n\t\tbedrockField: \"description\",\n\t\tmantleField: \"description\",\n\t\tread: readString,\n\t\trule: \"place-description\",\n\t},\n\t{\n\t\tbedrockField: \"serverSize\",\n\t\tmantleField: \"maxPlayerCount\",\n\t\tread: readPositiveInteger,\n\t\trule: \"max-player-count-to-server-size\",\n\t},\n];\n\nconst NON_START_RULES: ReadonlyArray<PlaceConfigRule> = [\n\t{\n\t\tbedrockField: \"displayName\",\n\t\tmantleField: \"name\",\n\t\tread: readString,\n\t\trule: \"place-name-to-display-name\",\n\t},\n];\n\nfunction placeConfigRules(isStart: boolean): ReadonlyArray<PlaceConfigRule> {\n\treturn isStart ? ALWAYS_RULES : [...ALWAYS_RULES, ...NON_START_RULES];\n}\n\nfunction readPlaceConfigFragment(\n\tinputs: Record<string, unknown>,\n\trules: ReadonlyArray<PlaceConfigRule>,\n): PlaceConfigFragment {\n\treturn rules.reduce<{\n\t\tentry: Partial<PlaceEntry>;\n\t\twarnings: Array<PlaceConfigFragmentRule>;\n\t}>(\n\t\t(accumulator, rule) => {\n\t\t\tconst value = rule.read(inputs[rule.mantleField]);\n\t\t\tif (value === undefined) {\n\t\t\t\treturn accumulator;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tentry: { ...accumulator.entry, [rule.bedrockField]: value },\n\t\t\t\twarnings: [\n\t\t\t\t\t...accumulator.warnings,\n\t\t\t\t\t{\n\t\t\t\t\t\tbedrockField: rule.bedrockField,\n\t\t\t\t\t\tmantleField: rule.mantleField,\n\t\t\t\t\t\trule: rule.rule,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t};\n\t\t},\n\t\t{ entry: {}, warnings: [] },\n\t);\n}\n\nfunction applyPlaceConfigFields(inputs: ApplyPlaceConfigFieldsInputs): PlaceConfigFoldResult {\n\tconst { key, configResource, folded, isStart } = inputs;\n\tif (configResource === undefined || !isObjectPayload(configResource.inputs)) {\n\t\treturn { entry: folded, warnings: [] };\n\t}\n\n\tconst fragment = readPlaceConfigFragment(configResource.inputs, placeConfigRules(isStart));\n\tconst entry: PlaceFoldEntry = {\n\t\t...folded,\n\t\tentry: { ...folded.entry, ...fragment.entry },\n\t};\n\n\treturn {\n\t\tentry,\n\t\twarnings: fragment.warnings.map((rule) => {\n\t\t\treturn interpretiveWarning({\n\t\t\t\tbedrockPath: `places.${key}.${rule.bedrockField}`,\n\t\t\t\tmantlePath: `${PLACE_CONFIGURATION_KIND}_${key}.${rule.mantleField}`,\n\t\t\t\trule: rule.rule,\n\t\t\t});\n\t\t}),\n\t};\n}\n\nfunction isStartPlace(resource: MantleResource): boolean {\n\tif (!isObjectPayload(resource.inputs)) {\n\t\treturn false;\n\t}\n\n\treturn resource.inputs[\"isStart\"] === true;\n}\n\nfunction coerceRobloxId(value: unknown): string | undefined {\n\tif (typeof value === \"string\") {\n\t\treturn value;\n\t}\n\n\tif (Number.isInteger(value)) {\n\t\treturn String(value);\n\t}\n\n\treturn undefined;\n}\n\nfunction readPlaceOutputs(resource: MantleResource): PlaceOutputsRaw | undefined {\n\tconst { outputs } = resource;\n\tif (!isObjectPayload(outputs)) {\n\t\treturn undefined;\n\t}\n\n\tconst assetId = coerceRobloxId(outputs[\"assetId\"]);\n\tif (assetId === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { assetId };\n}\n\nfunction readPlaceFileInputs(resource: MantleResource): PlaceFileInputsRaw | undefined {\n\tconst { inputs } = resource;\n\tif (!isObjectPayload(inputs)) {\n\t\treturn undefined;\n\t}\n\n\tconst { fileHash, filePath } = inputs;\n\tif (typeof filePath !== \"string\" || typeof fileHash !== \"string\" || !isSha256Hex(fileHash)) {\n\t\treturn undefined;\n\t}\n\n\treturn { fileHash, filePath };\n}\n\nfunction readPlaceFileOutputs(resource: MantleResource): PlaceFileOutputsRaw | undefined {\n\tconst { outputs } = resource;\n\tif (!isObjectPayload(outputs)) {\n\t\treturn undefined;\n\t}\n\n\tconst { version } = outputs;\n\tif (typeof version !== \"number\") {\n\t\treturn undefined;\n\t}\n\n\treturn { version };\n}\n\nfunction mergeMatchedPair(\n\tplaceResource: MantleResource,\n\tfileResource: MantleResource,\n): PlaceFoldEntry | undefined {\n\tconst placeOutputs = readPlaceOutputs(placeResource);\n\tconst fileInputs = readPlaceFileInputs(fileResource);\n\tconst fileOutputs = readPlaceFileOutputs(fileResource);\n\tif (placeOutputs === undefined || fileInputs === undefined || fileOutputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tentry: { filePath: fileInputs.filePath },\n\t\tfileHash: fileInputs.fileHash,\n\t\toutputs: { versionNumber: fileOutputs.version },\n\t\tplaceId: placeOutputs.assetId,\n\t};\n}\n\nfunction orphanWarning(kind: string, key: string): MigrationWarning {\n\treturn {\n\t\thint: \"Verify your Mantle state file: each place_<k> resource must be paired with a matching placeFile_<k>, and vice versa.\",\n\t\tkind: \"ambiguous\",\n\t\tmantlePath: `${kind}_${key}`,\n\t};\n}\n\nfunction placeConfigOrphanWarning(key: string): MigrationWarning {\n\treturn {\n\t\thint: \"Verify your Mantle state file: each placeConfiguration_<k> requires a matching place_<k>+placeFile_<k> pair to fold its data into the bedrock config.\",\n\t\tkind: \"ambiguous\",\n\t\tmantlePath: `${PLACE_CONFIGURATION_KIND}_${key}`,\n\t};\n}\n","import { asResourceKey, asRobloxAssetId, asSha256Hex, isSha256Hex } from \"../../types/ids.ts\";\nimport type { ResourceKey, Sha256Hex } from \"../../types/ids.ts\";\nimport type { DeveloperProductOutputs } from \"../resources.ts\";\nimport type { DeveloperProductEntry } from \"../schema.ts\";\nimport { coerceRobloxId, isObjectPayload } from \"./fold-universe-shared.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst PRODUCT_KIND = \"product\";\nconst PRODUCT_ICON_KIND = \"productIcon\";\n\n/**\n * Folded representation of one Mantle `product_<k>` resource.\n *\n * `entry` carries the bedrock `Config.products[<k>]` shape; when a matching\n * `productIcon_<k>` resource is present it also carries the locale-keyed icon\n * path. `outputs` carries the Roblox-assigned identifiers for the product and\n * its optional icon. `mantleIconFileHashes` preserves the hash recorded by\n * Mantle so the shell can fall back to it when the icon file is missing on\n * disk; absent when no icon partner is found. `mantlePath` roots warnings at\n * the resource so the report is searchable.\n */\nexport interface ProductFoldEntry {\n\t/** User-supplied Mantle key, branded as a bedrock `ResourceKey`. */\n\treadonly key: ResourceKey;\n\t/** Bedrock `Config.products[<k>]` block populated from the product resource. */\n\treadonly entry: DeveloperProductEntry;\n\t/** Locale-keyed Mantle-recorded icon hashes; absent when no productIcon partner is paired. */\n\treadonly mantleIconFileHashes?: Record<\"en-us\", Sha256Hex>;\n\t/** Resource-rooted Mantle path (`product_<k>`) used to anchor warnings. */\n\treadonly mantlePath: string;\n\t/** Roblox-assigned identifiers carried into `BedrockState.resources[*].outputs`. */\n\treadonly outputs: DeveloperProductOutputs;\n}\n\n/**\n * Output of folding the `product_<k>` and `productIcon_<k>` Mantle resources\n * of one environment into bedrock-shaped developer-product entries.\n *\n * `products` carries one record per well-formed `product_<k>` resource;\n * malformed resources are dropped silently. `warnings` carries one\n * `ambiguous` warning per orphan `productIcon_<k>` (no matching product).\n */\ninterface ProductsFoldResult {\n\t/** One folded entry per well-formed Mantle `product_<k>` resource. */\n\treadonly products: ReadonlyArray<ProductFoldEntry>;\n\t/** Per-rule diagnostics: orphan productIcon resources surface as `ambiguous` warnings. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface ProductInputs {\n\treadonly name: string;\n\treadonly description: string;\n\treadonly price: number | undefined;\n}\n\ninterface ProductOutputsRaw {\n\treadonly productId: string;\n}\n\ninterface ProductIconInputs {\n\treadonly fileHash: Sha256Hex;\n\treadonly filePath: string;\n}\n\ninterface ProductIconOutputsRaw {\n\treadonly assetId: string;\n}\n\ninterface ProductIconParts {\n\treadonly icon: Record<\"en-us\", string>;\n\treadonly iconImageAssetId: string;\n\treadonly mantleIconFileHashes: Record<\"en-us\", Sha256Hex>;\n}\n\n/**\n * Fold the `product_<k>` (and matching `productIcon_<k>`) Mantle resources\n * of one environment into a list of bedrock developer-product entries plus\n * matching outputs. Pairs each `product_<k>` with the optional\n * `productIcon_<k>` resource sharing the same key; when the icon partner is\n * present and well-formed, the locale-keyed icon path lands on the entry\n * and the Roblox-assigned `iconImageAssetId` lands on the outputs.\n *\n * Resources whose payload is malformed (non-object, missing required string\n * field, missing `assetId`, malformed `fileHash`) are dropped silently.\n * Orphan `productIcon_<k>` resources (no matching product) emit one\n * `ambiguous` warning each.\n *\n * @param resources - Resource list for one Mantle environment.\n * @returns The folded product entries plus per-rule diagnostics.\n */\nexport function foldProducts(resources: ReadonlyArray<MantleResource>): ProductsFoldResult {\n\tconst { productIcons, products } = bucketByKind(resources);\n\tconst folded = products.flatMap((resource): ReadonlyArray<ProductFoldEntry> => {\n\t\tconst iconResource = productIcons.get(resource.key);\n\t\tconst entry = foldOneProduct(resource, iconResource);\n\t\treturn entry === undefined ? [] : [entry];\n\t});\n\n\tconst productKeys = new Set(products.map((resource) => resource.key));\n\tconst warnings = [...productIcons.keys()]\n\t\t.filter((key) => !productKeys.has(key))\n\t\t.map((key) => orphanIconWarning(key));\n\n\treturn { products: folded, warnings };\n}\n\nfunction bucketByKind(resources: ReadonlyArray<MantleResource>): {\n\treadonly productIcons: Map<string, MantleResource>;\n\treadonly products: ReadonlyArray<MantleResource>;\n} {\n\tconst products: Array<MantleResource> = [];\n\tconst productIcons = new Map<string, MantleResource>();\n\tfor (const resource of resources) {\n\t\tif (resource.kind === PRODUCT_KIND) {\n\t\t\tproducts.push(resource);\n\t\t} else if (resource.kind === PRODUCT_ICON_KIND) {\n\t\t\tproductIcons.set(resource.key, resource);\n\t\t}\n\t}\n\n\treturn { productIcons, products };\n}\n\nfunction buildEntry(\n\tinputs: ProductInputs,\n\ticon: Record<\"en-us\", string> | undefined,\n): DeveloperProductEntry {\n\tconst base: DeveloperProductEntry = {\n\t\tname: inputs.name,\n\t\tdescription: inputs.description,\n\t};\n\tconst withPrice = inputs.price === undefined ? base : { ...base, price: inputs.price };\n\treturn icon === undefined ? withPrice : { ...withPrice, icon };\n}\n\nfunction readString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction readPrice(raw: Record<string, unknown>): number | undefined {\n\tconst candidate = raw[\"price\"];\n\treturn typeof candidate === \"number\" ? candidate : undefined;\n}\n\nfunction readProductInputs(raw: unknown): ProductInputs | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst name = readString(raw[\"name\"]);\n\tconst description = readString(raw[\"description\"]);\n\tif (name === undefined || description === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { name, description, price: readPrice(raw) };\n}\n\nfunction readProductOutputs(raw: unknown): ProductOutputsRaw | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\t// Mantle's `assetId` is the canonical marketplace product id (the value\n\t// MarketplaceService.PromptProductPurchase and Open Cloud's URL accept).\n\t// Mantle's `productId` is a legacy config id from a different endpoint\n\t// family that Open Cloud v2 does not route.\n\tconst productId = coerceRobloxId(raw[\"assetId\"]);\n\tif (productId === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { productId };\n}\n\nfunction readProductIconInputs(raw: unknown): ProductIconInputs | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst filePath = readString(raw[\"filePath\"]);\n\tconst fileHash = readString(raw[\"fileHash\"]);\n\tif (filePath === undefined || fileHash === undefined || !isSha256Hex(fileHash)) {\n\t\treturn undefined;\n\t}\n\n\treturn { fileHash: asSha256Hex(fileHash), filePath };\n}\n\nfunction readProductIconOutputs(raw: unknown): ProductIconOutputsRaw | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst assetId = coerceRobloxId(raw[\"assetId\"]);\n\tif (assetId === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { assetId };\n}\n\nfunction readProductIconParts(resource: MantleResource): ProductIconParts | undefined {\n\tconst inputs = readProductIconInputs(resource.inputs);\n\tconst outputs = readProductIconOutputs(resource.outputs);\n\tif (inputs === undefined || outputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\ticon: { \"en-us\": inputs.filePath },\n\t\ticonImageAssetId: outputs.assetId,\n\t\tmantleIconFileHashes: { \"en-us\": inputs.fileHash },\n\t};\n}\n\nfunction foldOneProduct(\n\tresource: MantleResource,\n\ticonResource: MantleResource | undefined,\n): ProductFoldEntry | undefined {\n\tconst inputs = readProductInputs(resource.inputs);\n\tif (inputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst outputs = readProductOutputs(resource.outputs);\n\tif (outputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst iconParts = iconResource === undefined ? undefined : readProductIconParts(iconResource);\n\tconst productOutputs: DeveloperProductOutputs =\n\t\ticonParts === undefined\n\t\t\t? { iconImageAssetId: undefined, productId: asRobloxAssetId(outputs.productId) }\n\t\t\t: {\n\t\t\t\t\ticonImageAssetId: asRobloxAssetId(iconParts.iconImageAssetId),\n\t\t\t\t\tproductId: asRobloxAssetId(outputs.productId),\n\t\t\t\t};\n\n\tconst base: ProductFoldEntry = {\n\t\tkey: asResourceKey(resource.key),\n\t\tentry: buildEntry(inputs, iconParts?.icon),\n\t\tmantlePath: `${PRODUCT_KIND}_${resource.key}`,\n\t\toutputs: productOutputs,\n\t};\n\n\treturn iconParts === undefined\n\t\t? base\n\t\t: { ...base, mantleIconFileHashes: iconParts.mantleIconFileHashes };\n}\n\nfunction orphanIconWarning(key: string): MigrationWarning {\n\treturn {\n\t\thint: \"Verify your Mantle state file: each productIcon_<k> resource must be paired with a matching product_<k>.\",\n\t\tkind: \"ambiguous\",\n\t\tmantlePath: `${PRODUCT_ICON_KIND}_${key}`,\n\t};\n}\n","import {\n\ttype BlockedFieldRule,\n\tblockedWarning,\n\ttype FoldFragment,\n\tisObjectPayload,\n} from \"./fold-universe-shared.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst EXPERIENCE_KIND = \"experience\";\nconst EXPERIENCE_CONFIGURATION_KIND = \"experienceConfiguration\";\nconst UNIVERSE_AVATAR_PREFIX = \"universeAvatar\";\nconst UNIVERSE_AVATAR_REASON = \"avatar configuration has no Open Cloud equivalent\";\nconst GROUP_ID_REASON =\n\t\"Mantle used `groupId` to set the owning group when creating the experience. Bedrock requires pre-existing universe and place IDs and does not use this field.\";\n\n/**\n * `experienceConfiguration_singleton` fields with no Open Cloud writable\n * endpoint.\n */\nconst BLOCKED_FIELDS: ReadonlyArray<BlockedFieldRule> = [\n\t{ field: \"genre\", reason: \"Roblox does not expose `genre` via Open Cloud\" },\n\t{ field: \"isForSale\", reason: \"paid-access flag has no Open Cloud equivalent\" },\n\t{ field: \"price\", reason: \"paid-access flag has no Open Cloud equivalent\" },\n\t{\n\t\tfield: \"studioAccessToApisAllowed\",\n\t\treason: \"Open Cloud does not currently expose a write endpoint for studio API access\",\n\t},\n\t{\n\t\tfield: \"permissions\",\n\t\treason: \"experienceConfiguration.permissions has no Open Cloud equivalent\",\n\t},\n\t{ field: \"isArchived\", reason: \"isArchived has no Open Cloud equivalent\" },\n\t{ field: \"isFriendsOnly\", reason: \"isFriendsOnly has no Open Cloud equivalent\" },\n];\n\n/**\n * Emit one `blocked` `MigrationWarning` per non-`undefined` legacy-only\n * field on the experience-side Mantle resources: every entry in the\n * static `BLOCKED_FIELDS` table on `experienceConfiguration_singleton`,\n * any key under the `universeAvatar` prefix, and `groupId` on\n * `experience_singleton`. The Mantle null sentinel (`~`) is normalized\n * to `undefined` by `parseState`, so absent and null fields both skip.\n *\n * The fragment's `entryFragment` is always empty; this fold contributes\n * only warnings, and the `warnings` array is empty when none of the\n * watched resources have a populated blocked field.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns A fragment whose warnings list every blocked field present.\n */\nexport function foldBlockedExperienceFields(\n\tresources: ReadonlyArray<MantleResource>,\n): FoldFragment {\n\treturn {\n\t\tentryFragment: {},\n\t\twarnings: [...configurationWarnings(resources), ...groupIdWarnings(resources)],\n\t};\n}\n\nfunction groupIdWarnings(\n\tresources: ReadonlyArray<MantleResource>,\n): ReadonlyArray<MigrationWarning> {\n\tconst experience = resources.find((resource) => resource.kind === EXPERIENCE_KIND);\n\tif (\n\t\texperience === undefined ||\n\t\t!isObjectPayload(experience.inputs) ||\n\t\texperience.inputs[\"groupId\"] === undefined\n\t) {\n\t\treturn [];\n\t}\n\n\treturn [blockedWarning(\"experience_singleton.groupId\", GROUP_ID_REASON)];\n}\n\nfunction staticFieldWarnings(inputs: Record<string, unknown>): ReadonlyArray<MigrationWarning> {\n\treturn BLOCKED_FIELDS.flatMap((rule) => {\n\t\tif (inputs[rule.field] === undefined) {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn [blockedWarning(`experienceConfiguration_singleton.${rule.field}`, rule.reason)];\n\t});\n}\n\nfunction avatarFieldWarnings(inputs: Record<string, unknown>): ReadonlyArray<MigrationWarning> {\n\treturn Object.keys(inputs)\n\t\t.filter((key) => key.startsWith(UNIVERSE_AVATAR_PREFIX) && inputs[key] !== undefined)\n\t\t.map((key) =>\n\t\t\tblockedWarning(`experienceConfiguration_singleton.${key}`, UNIVERSE_AVATAR_REASON),\n\t\t);\n}\n\nfunction configurationWarnings(\n\tresources: ReadonlyArray<MantleResource>,\n): ReadonlyArray<MigrationWarning> {\n\tconst config = resources.find((resource) => resource.kind === EXPERIENCE_CONFIGURATION_KIND);\n\tif (config === undefined || !isObjectPayload(config.inputs)) {\n\t\treturn [];\n\t}\n\n\treturn [...staticFieldWarnings(config.inputs), ...avatarFieldWarnings(config.inputs)];\n}\n","import {\n\tambiguousWarning,\n\tEMPTY_FRAGMENT,\n\ttype FoldFragment,\n\tinterpretiveWarning,\n\tisObjectPayload,\n} from \"./fold-universe-shared.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst PLACE_KIND = \"place\";\nconst PLACE_CONFIGURATION_KIND = \"placeConfiguration\";\n\ninterface NamedPlaceConfig {\n\treadonly key: string;\n\treadonly name: string;\n}\n\nfunction readPlaceConfigName(resource: MantleResource): NamedPlaceConfig | undefined {\n\tif (!isObjectPayload(resource.inputs)) {\n\t\treturn undefined;\n\t}\n\n\tconst { name } = resource.inputs;\n\treturn typeof name === \"string\" ? { key: resource.key, name } : undefined;\n}\n\nfunction isStartPlace(resource: MantleResource): boolean {\n\tif (resource.kind !== PLACE_KIND || !isObjectPayload(resource.inputs)) {\n\t\treturn false;\n\t}\n\n\treturn resource.inputs[\"isStart\"] === true;\n}\n\nfunction startKeys(resources: ReadonlyArray<MantleResource>): ReadonlyArray<string> {\n\treturn resources.filter(isStartPlace).map((resource) => resource.key);\n}\n\nfunction interpretiveFragment(named: NamedPlaceConfig): FoldFragment {\n\treturn {\n\t\tentryFragment: { displayName: named.name },\n\t\twarnings: [\n\t\t\tinterpretiveWarning({\n\t\t\t\tbedrockPath: \"universe.displayName\",\n\t\t\t\tmantlePath: `${PLACE_CONFIGURATION_KIND}_${named.key}.name`,\n\t\t\t\trule: \"start-place-name-to-display-name\",\n\t\t\t}),\n\t\t],\n\t};\n}\n\nconst AMBIGUOUS_MULTIPLE_STARTS: FoldFragment = {\n\tentryFragment: {},\n\twarnings: [\n\t\tambiguousWarning(\n\t\t\t\"place_*.isStart\",\n\t\t\t\"Multiple place_<k> resources have inputs.isStart=true; pick one as the canonical start place.\",\n\t\t),\n\t],\n};\n\n/**\n * Fold the start place's `placeConfiguration_<k>.name` into\n * `universe.displayName`. Non-start places' names are folded onto each\n * place's `displayName` by `foldPlaces`; multiple `isStart: true` places\n * emit one `ambiguous` warning and skip the displayName mapping.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns The folded displayName plus per-rule diagnostics.\n */\nexport function foldDisplayName(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tconst starts = startKeys(resources);\n\tif (starts.length >= 2) {\n\t\treturn AMBIGUOUS_MULTIPLE_STARTS;\n\t}\n\n\tconst [startKey] = starts;\n\tconst startConfigResource = resources.findLast((resource) => {\n\t\treturn resource.kind === PLACE_CONFIGURATION_KIND && resource.key === startKey;\n\t});\n\tif (startConfigResource === undefined) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\tconst named = readPlaceConfigName(startConfigResource);\n\treturn named === undefined ? EMPTY_FRAGMENT : interpretiveFragment(named);\n}\n","import {\n\tblockedWarning,\n\tEMPTY_FRAGMENT,\n\ttype FoldFragment,\n\tisObjectPayload,\n} from \"./fold-universe-shared.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst EXPERIENCE_ICON_KIND = \"experienceIcon\";\n\nconst BLOCKED_REASON =\n\t\"Open Cloud has no route to set a universe's source-language game icon; configure it via the Roblox creator portal.\";\n\n/**\n * Surface every Mantle `experienceIcon_<key>` resource as a `blocked`\n * migration warning. Bedrock has no `UniverseEntry.icon` field today\n * because no Open Cloud endpoint accepts a source-language game icon, so\n * the migrator emits one warning per legacy resource (rather than the\n * first matching entry only) so the operator can audit each affected\n * environment before reconfiguring the icon by hand.\n *\n * Resources whose payload is malformed (non-object inputs/outputs,\n * non-string `filePath`) are skipped silently, matching the\n * malformed-payload behaviour of the other fold rules.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns A fragment whose `warnings` carries one `blocked` entry per\n * legacy experience-icon resource, or {@link EMPTY_FRAGMENT} when none\n * are present.\n */\nexport function foldExperienceIcon(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tconst warnings = resources\n\t\t.filter(\n\t\t\t(resource) => resource.kind === EXPERIENCE_ICON_KIND && hasReadablePayload(resource),\n\t\t)\n\t\t.map((resource) =>\n\t\t\tblockedWarning(`${EXPERIENCE_ICON_KIND}_${resource.key}`, BLOCKED_REASON),\n\t\t);\n\n\tif (warnings.length === 0) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn { entryFragment: {}, warnings };\n}\n\nfunction hasReadablePayload(resource: MantleResource): boolean {\n\tif (!isObjectPayload(resource.inputs)) {\n\t\treturn false;\n\t}\n\n\tconst { filePath } = resource.inputs;\n\treturn typeof filePath === \"string\";\n}\n","import type { SocialLinkField } from \"../resources.ts\";\nimport {\n\tblockedWarning,\n\tEMPTY_FRAGMENT,\n\ttype FoldFragment,\n\tinterpretiveWarning,\n\tisObjectPayload,\n\tmergeFragment,\n} from \"./fold-universe-shared.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst SOCIAL_LINK_KIND = \"socialLink\";\n\nconst SOCIAL_LINK_DOMAIN_TO_FIELD: Readonly<Record<string, SocialLinkField>> = {\n\t\"discord.gg\": \"discordSocialLink\",\n\t\"facebook.com\": \"facebookSocialLink\",\n\t\"guilded.gg\": \"guildedSocialLink\",\n\t\"roblox.com\": \"robloxGroupSocialLink\",\n\t\"twitch.tv\": \"twitchSocialLink\",\n\t\"twitter.com\": \"twitterSocialLink\",\n\t\"www.roblox.com\": \"robloxGroupSocialLink\",\n\t\"youtube.com\": \"youtubeSocialLink\",\n};\n\ninterface KnownSocialLink {\n\treadonly field: SocialLinkField;\n\treadonly mantlePath: string;\n\treadonly title: string;\n\treadonly url: string;\n}\n\n/**\n * Fold every `socialLink_<domain>` Mantle resource into the matching\n * `universe.<field>SocialLink` entry. Unknown domains emit a `blocked`\n * warning and are dropped from the output. Malformed payloads (non-string\n * `title` or `url`) drop silently per the established convention.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns The folded social-link entries plus per-rule diagnostics.\n */\nexport function foldSocialLinks(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\treturn resources\n\t\t.filter((resource) => resource.kind === SOCIAL_LINK_KIND)\n\t\t.reduce<FoldFragment>(\n\t\t\t(accumulator, resource) => mergeFragment(accumulator, mapSocialLink(resource)),\n\t\t\tEMPTY_FRAGMENT,\n\t\t);\n}\n\nfunction knownFragment(known: KnownSocialLink): FoldFragment {\n\treturn {\n\t\tentryFragment: { [known.field]: { title: known.title, uri: known.url } },\n\t\twarnings: [\n\t\t\tinterpretiveWarning({\n\t\t\t\tbedrockPath: `universe.${known.field}`,\n\t\t\t\tmantlePath: known.mantlePath,\n\t\t\t\trule: \"domain-to-field\",\n\t\t\t}),\n\t\t],\n\t};\n}\n\nfunction mapSocialLink(resource: MantleResource): FoldFragment {\n\tconst mantlePath = `${SOCIAL_LINK_KIND}_${resource.key}`;\n\tconst field = SOCIAL_LINK_DOMAIN_TO_FIELD[resource.key];\n\tif (field === undefined) {\n\t\treturn {\n\t\t\tentryFragment: {},\n\t\t\twarnings: [blockedWarning(mantlePath, `Unknown socialLink domain: ${resource.key}`)],\n\t\t};\n\t}\n\n\tif (!isObjectPayload(resource.inputs)) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\tconst { title, url } = resource.inputs;\n\tif (typeof title !== \"string\" || typeof url !== \"string\") {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn knownFragment({ field, mantlePath, title, url });\n}\n","import { asRobloxAssetId } from \"../../types/ids.ts\";\nimport type { UniverseOutputs } from \"../resources.ts\";\nimport type { ResolvedUniverseEntry } from \"../schema.ts\";\nimport { foldBlockedExperienceFields } from \"./fold-blocked-experience-fields.ts\";\nimport { foldDisplayName } from \"./fold-display-name.ts\";\nimport { foldExperienceIcon } from \"./fold-experience-icon.ts\";\nimport { foldSocialLinks } from \"./fold-social-links.ts\";\nimport {\n\tblockedWarning,\n\tEMPTY_FRAGMENT,\n\ttype FoldFragment,\n\tinterpretiveWarning,\n\tisObjectPayload,\n\tmergeFragment,\n} from \"./fold-universe-shared.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst EXPERIENCE_KIND = \"experience\";\nconst EXPERIENCE_ACTIVATION_KIND = \"experienceActivation\";\nconst EXPERIENCE_CONFIGURATION_KIND = \"experienceConfiguration\";\nconst SPATIAL_VOICE_KIND = \"spatialVoice\";\n\nconst PLAYABLE_DEVICE_TO_FLAG: Readonly<\n\tRecord<string, \"consoleEnabled\" | \"desktopEnabled\" | \"mobileEnabled\" | \"tabletEnabled\">\n> = {\n\tComputer: \"desktopEnabled\",\n\tConsole: \"consoleEnabled\",\n\tPhone: \"mobileEnabled\",\n\tTablet: \"tabletEnabled\",\n};\n\n/**\n * Output of folding the experience-related Mantle resources of one\n * environment into Bedrock's `universe` shape.\n *\n * `entry` populates the bedrock `Config.universe` block. `outputs`\n * populates the matching `BedrockState` resource's `outputs` field.\n * `warnings` carries per-rule diagnostics that the migration report\n * presents to the user.\n */\ninterface UniverseFoldResult {\n\t/**\n\t * Bedrock universe entry populated from the experience resource. Carries\n\t * `universeId` because the Mantle state always knows which universe the\n\t * environment targets; downstream consumers rely on the post-merge\n\t * invariant that `universeId` is present.\n\t */\n\treadonly entry: ResolvedUniverseEntry;\n\t/** Roblox-assigned identifiers carried into `BedrockState.resources[*].outputs`. */\n\treadonly outputs: UniverseOutputs;\n\t/** Per-rule diagnostics emitted while folding this environment's resources. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface ExperienceOutputs {\n\treadonly assetId: string;\n\treadonly startPlaceId: string;\n}\n\n/**\n * Fold the universe-contributing Mantle resources of one environment\n * into a `UniverseEntry` plus matching `UniverseOutputs`.\n *\n * Returns `undefined` when no `experience_singleton` resource is present;\n * the caller treats that as \"this environment has no universe to migrate\"\n * and omits the `universe` field from the resulting `Config`.\n *\n * @param resources - Resource list for one Mantle environment.\n * @returns The folded universe data plus warnings, or `undefined` when\n * no `experience_singleton` is present.\n */\nexport function foldUniverse(\n\tresources: ReadonlyArray<MantleResource>,\n): undefined | UniverseFoldResult {\n\tconst experience = resources.find((resource) => resource.kind === EXPERIENCE_KIND);\n\tif (experience === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst outputs = readExperienceOutputs(experience);\n\tif (outputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst fragments = collectUniverseFragments(resources);\n\n\tconst entry: ResolvedUniverseEntry = fragments.reduce<ResolvedUniverseEntry>(\n\t\t(accumulator, fragment) => ({ ...accumulator, ...fragment.entryFragment }),\n\t\t{ universeId: outputs.assetId },\n\t);\n\n\tconst universeOutputs: UniverseOutputs = fragments.reduce<UniverseOutputs>(\n\t\t(accumulator, fragment) => ({ ...accumulator, ...fragment.outputsFragment }),\n\t\t{ rootPlaceId: asRobloxAssetId(outputs.startPlaceId) },\n\t);\n\n\tconst warnings = fragments.flatMap((fragment) => fragment.warnings);\n\n\treturn {\n\t\tentry,\n\t\toutputs: universeOutputs,\n\t\twarnings,\n\t};\n}\n\nfunction collectUniverseFragments(\n\tresources: ReadonlyArray<MantleResource>,\n): ReadonlyArray<FoldFragment> {\n\treturn [\n\t\tfoldPlayableDevices(resources),\n\t\tfoldPrivateServers(resources),\n\t\tfoldVoiceChat(resources),\n\t\tfoldExperienceActivation(resources),\n\t\tfoldSocialLinks(resources),\n\t\tfoldDisplayName(resources),\n\t\tfoldExperienceIcon(resources),\n\t\tfoldBlockedExperienceFields(resources),\n\t];\n}\n\nfunction coerceRobloxId(value: unknown): string | undefined {\n\tif (typeof value === \"string\") {\n\t\treturn value;\n\t}\n\n\tif (Number.isInteger(value)) {\n\t\treturn String(value);\n\t}\n\n\treturn undefined;\n}\n\nfunction readExperienceOutputs(resource: MantleResource): ExperienceOutputs | undefined {\n\tconst raw = resource.outputs;\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst assetId = coerceRobloxId(raw[\"assetId\"]);\n\tconst startPlaceId = coerceRobloxId(raw[\"startPlaceId\"]);\n\tif (assetId === undefined || startPlaceId === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { assetId, startPlaceId };\n}\n\nconst PLAYABLE_DEVICES_PATH = \"experienceConfiguration_singleton.playableDevices\";\n\nfunction mapPlayableDevice(raw: unknown): FoldFragment {\n\tconst flag = typeof raw === \"string\" ? PLAYABLE_DEVICE_TO_FLAG[raw] : undefined;\n\tif (flag === undefined) {\n\t\treturn {\n\t\t\tentryFragment: {},\n\t\t\twarnings: [\n\t\t\t\tblockedWarning(\n\t\t\t\t\tPLAYABLE_DEVICES_PATH,\n\t\t\t\t\t`Unknown playableDevices value: ${String(raw)}`,\n\t\t\t\t),\n\t\t\t],\n\t\t};\n\t}\n\n\treturn {\n\t\tentryFragment: { [flag]: true },\n\t\twarnings: [\n\t\t\tinterpretiveWarning({\n\t\t\t\tbedrockPath: `universe.${flag}`,\n\t\t\t\tmantlePath: PLAYABLE_DEVICES_PATH,\n\t\t\t\trule: \"list-to-flag\",\n\t\t\t}),\n\t\t],\n\t};\n}\n\nfunction foldPlayableDevices(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tconst config = resources.find((resource) => resource.kind === EXPERIENCE_CONFIGURATION_KIND);\n\tif (config === undefined || !isObjectPayload(config.inputs)) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\tconst { playableDevices } = config.inputs;\n\tif (!Array.isArray(playableDevices)) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn playableDevices.reduce<FoldFragment>(\n\t\t(accumulator, raw) => mergeFragment(accumulator, mapPlayableDevice(raw)),\n\t\tEMPTY_FRAGMENT,\n\t);\n}\n\nconst PRIVATE_SERVERS_DISABLED: FoldFragment = {\n\tentryFragment: {},\n\twarnings: [\n\t\tinterpretiveWarning({\n\t\t\tbedrockPath: \"universe.privateServerPriceRobux\",\n\t\t\tmantlePath: \"experienceConfiguration_singleton.allowPrivateServers\",\n\t\t\trule: \"private-servers-disabled-omitted\",\n\t\t}),\n\t],\n};\n\nfunction pricedPrivateServersFragment(price: number): FoldFragment {\n\treturn {\n\t\tentryFragment: { privateServerPriceRobux: price },\n\t\twarnings: [\n\t\t\tinterpretiveWarning({\n\t\t\t\tbedrockPath: \"universe.privateServerPriceRobux\",\n\t\t\t\tmantlePath: \"experienceConfiguration_singleton.privateServerPrice\",\n\t\t\t\trule: \"private-servers-priced\",\n\t\t\t}),\n\t\t],\n\t};\n}\n\nconst EXPERIENCE_ACTIVATION_BLOCKED: FoldFragment = {\n\tentryFragment: {},\n\twarnings: [\n\t\tblockedWarning(\n\t\t\t\"experienceActivation_singleton.isActive\",\n\t\t\t\"isActive has no Open Cloud equivalent\",\n\t\t),\n\t],\n};\n\nfunction readIsActive(resources: ReadonlyArray<MantleResource>): boolean | undefined {\n\tconst activation = resources.find((resource) => resource.kind === EXPERIENCE_ACTIVATION_KIND);\n\tif (activation === undefined || !isObjectPayload(activation.inputs)) {\n\t\treturn undefined;\n\t}\n\n\tconst { isActive } = activation.inputs;\n\treturn typeof isActive === \"boolean\" ? isActive : undefined;\n}\n\nfunction foldExperienceActivation(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tif (readIsActive(resources) === undefined) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn EXPERIENCE_ACTIVATION_BLOCKED;\n}\n\nfunction foldVoiceChat(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tconst voice = resources.find((resource) => resource.kind === SPATIAL_VOICE_KIND);\n\tif (voice === undefined || !isObjectPayload(voice.inputs)) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\tconst { enabled } = voice.inputs;\n\tif (typeof enabled !== \"boolean\") {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn {\n\t\tentryFragment: { voiceChatEnabled: enabled },\n\t\twarnings: [\n\t\t\tinterpretiveWarning({\n\t\t\t\tbedrockPath: \"universe.voiceChatEnabled\",\n\t\t\t\tmantlePath: \"spatialVoice_singleton.enabled\",\n\t\t\t\trule: \"voice-chat-enabled\",\n\t\t\t}),\n\t\t],\n\t};\n}\n\nfunction foldPrivateServers(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tconst config = resources.find((resource) => resource.kind === EXPERIENCE_CONFIGURATION_KIND);\n\tif (config === undefined || !isObjectPayload(config.inputs)) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\tconst { allowPrivateServers, privateServerPrice } = config.inputs;\n\tif (allowPrivateServers === false) {\n\t\treturn PRIVATE_SERVERS_DISABLED;\n\t}\n\n\tif (allowPrivateServers !== true || typeof privateServerPrice !== \"number\") {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn pricedPrivateServersFragment(privateServerPrice);\n}\n","import type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\n/**\n * Mantle resource kinds bedrock plans to support but does not yet model.\n * Maps the wire-form discriminator to the human-readable noun used in\n * each emitted `deferred` warning's `reason`. The reason string is\n * shared across all resources of the same kind.\n */\nconst DEFERRED_KIND_REASONS: Readonly<Record<string, string>> = {\n\tassetAlias: \"asset aliases\",\n\taudioAsset: \"audio assets\",\n\tbadge: \"badges\",\n\tbadgeIcon: \"badge icons\",\n\texperienceThumbnail: \"experience thumbnails\",\n\texperienceThumbnailOrder: \"experience thumbnail ordering\",\n\timageAsset: \"image assets\",\n\tnotification: \"experience notifications\",\n};\n\n/**\n * Emit one `deferred` `MigrationWarning` per Mantle resource whose kind\n * bedrock has reserved for a future slice. The warning's `mantlePath` is\n * resource-rooted (`<kind>_<key>`). Resources whose kind is not in the\n * deferred set are passed over silently.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns One warning per deferred-kind resource, in the input order.\n */\nexport function foldUnsupported(\n\tresources: ReadonlyArray<MantleResource>,\n): ReadonlyArray<MigrationWarning> {\n\treturn resources.flatMap((resource): ReadonlyArray<MigrationWarning> => {\n\t\tconst humanName = DEFERRED_KIND_REASONS[resource.kind];\n\t\tif (humanName === undefined) {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: \"deferred\",\n\t\t\t\tmantlePath: `${resource.kind}_${resource.key}`,\n\t\t\t\treason: `${humanName}: not yet modeled in bedrock; will surface once the relevant kind ships`,\n\t\t\t},\n\t\t];\n\t});\n}\n","import type { UniverseOutputs } from \"../resources.ts\";\nimport type { ResolvedUniverseEntry } from \"../schema.ts\";\nimport { foldPasses, type PassFoldEntry } from \"./fold-passes.ts\";\nimport { foldPlaces, type PlaceFoldEntry } from \"./fold-places.ts\";\nimport { foldProducts, type ProductFoldEntry } from \"./fold-products.ts\";\nimport { foldUniverse } from \"./fold-universe.ts\";\nimport { foldUnsupported } from \"./fold-unsupported.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\n/**\n * Result of folding one Mantle environment's resource list into bedrock\n * shapes. A Mantle environment without an experience resource yields\n * `universe: undefined`, signalling that the bedrock `Config` for this\n * environment carries no `universe` block. `places`, `passes`, and\n * `products` are always present (potentially empty) so the orchestrator\n * does not have to special-case \"no <kind>\" against \"<kind> not yet\n * wired\"; orphan place resources surface as `ambiguous` warnings, and\n * resources of kinds bedrock plans to support but does not yet model\n * surface as `deferred` warnings.\n */\nexport interface EnvironmentFoldResult {\n\t/** Folded pass entries for this environment, in declaration order. */\n\treadonly passes: ReadonlyArray<PassFoldEntry>;\n\t/** Folded place entries for this environment, keyed by Mantle's place key. */\n\treadonly places: ReadonlyMap<string, PlaceFoldEntry>;\n\t/** Folded developer-product entries for this environment, in declaration order. */\n\treadonly products: ReadonlyArray<ProductFoldEntry>;\n\t/** Folded universe data for this environment, or `undefined` when no experience is present. */\n\treadonly universe:\n\t\t| undefined\n\t\t| {\n\t\t\t\treadonly entry: ResolvedUniverseEntry;\n\t\t\t\treadonly outputs: UniverseOutputs;\n\t\t };\n\t/** Per-rule diagnostics aggregated across all per-kind folds. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\n/**\n * Orchestrate the per-kind folds for one Mantle environment, gathering\n * the results and warnings into a single `EnvironmentFoldResult`. Pure;\n * delegates to `foldUniverse`, `foldPlaces`, `foldPasses`, `foldProducts`,\n * and `foldUnsupported`. Emitted warning paths are resource-rooted.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns The folded per-kind data plus aggregated warnings.\n */\nexport function foldEnvironment(resources: ReadonlyArray<MantleResource>): EnvironmentFoldResult {\n\tconst universeResult = foldUniverse(resources);\n\tconst universe =\n\t\tuniverseResult === undefined\n\t\t\t? undefined\n\t\t\t: { entry: universeResult.entry, outputs: universeResult.outputs };\n\n\tconst placesResult = foldPlaces(resources);\n\tconst passesResult = foldPasses(resources);\n\tconst productsResult = foldProducts(resources);\n\n\tconst warnings = [\n\t\t...(universeResult?.warnings ?? []),\n\t\t...placesResult.warnings,\n\t\t...passesResult.warnings,\n\t\t...productsResult.warnings,\n\t\t...foldUnsupported(resources),\n\t];\n\n\treturn {\n\t\tpasses: passesResult.passes,\n\t\tplaces: placesResult.entries,\n\t\tproducts: productsResult.products,\n\t\tuniverse,\n\t\twarnings,\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { parseYAML } from \"confbox\";\n\nimport type { MigrateError } from \"./migration-report.ts\";\nimport type { MantleResource, MantleStateV6 } from \"./types.ts\";\n\nconst SUPPORTED_VERSIONS = [\"6\"] as const;\n\n/**\n * Parse the contents of a `.mantle-state.yml` file into a typed\n * `MantleStateV6` envelope. Pure: takes a string, returns a `Result`.\n *\n * The parser also normalizes JSON `null` values to `undefined` recursively\n * across each resource's `inputs` and `outputs` payloads (per the project\n * type convention) and splits each resource's `id` field into the\n * `(kind, key)` pair carried on `MantleResource`.\n *\n * Surfaces three discriminated `MigrateError` kinds:\n * - `stateParseFailed` when the YAML parser refuses the file or its\n * structural shape (missing `environments`, malformed resource entry,\n * non-string `id`, etc.) does not match the v6 envelope.\n * - `unsupportedMantleStateVersion` when the `version` field decodes but\n * is not one of the values in `SUPPORTED_VERSIONS`.\n *\n * @param raw - Raw YAML text decoded from the state file.\n * @param sourceFile - Path or identifier of the source file, threaded into\n * any returned `MigrateError` for diagnostics.\n * @returns `Ok` with the parsed envelope, or `Err` with a discriminated\n * `MigrateError` describing why the file was rejected.\n */\nexport function parseState(raw: string, sourceFile: string): Result<MantleStateV6, MigrateError> {\n\tlet envelope: Record<string, unknown>;\n\ttry {\n\t\tconst parsed = parseYAML(raw);\n\t\tenvelope = expectObject(parsed);\n\t} catch (err) {\n\t\treturn parseFailedFrom(err, sourceFile);\n\t}\n\n\tconst versionResult = parseVersion(envelope[\"version\"]);\n\tif (!versionResult.success) {\n\t\treturn versionResult;\n\t}\n\n\tlet environments: Readonly<Record<string, ReadonlyArray<MantleResource>>>;\n\ttry {\n\t\tenvironments = parseEnvironments(envelope[\"environments\"]);\n\t} catch (err) {\n\t\treturn parseFailedFrom(err, sourceFile);\n\t}\n\n\treturn {\n\t\tdata: { environments, version: versionResult.data },\n\t\tsuccess: true,\n\t};\n}\n\nfunction parseVersion(raw: unknown): Result<\"6\", MigrateError> {\n\tconst found = typeof raw === \"string\" ? raw : String(raw);\n\tif (found !== \"6\") {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tfound,\n\t\t\t\tkind: \"unsupportedMantleStateVersion\",\n\t\t\t\tsupported: SUPPORTED_VERSIONS,\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: \"6\", success: true };\n}\n\nfunction isObjectPayload(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction describe(value: unknown): string {\n\tif (value === null) {\n\t\treturn \"null\";\n\t}\n\n\tif (Array.isArray(value)) {\n\t\treturn \"array\";\n\t}\n\n\treturn typeof value;\n}\n\nfunction expectObject(value: unknown): Record<string, unknown> {\n\tif (!isObjectPayload(value)) {\n\t\tthrow new TypeError(`expected object, got ${describe(value)}`);\n\t}\n\n\treturn value;\n}\n\nfunction expectArray(value: unknown): ReadonlyArray<unknown> {\n\tif (!Array.isArray(value)) {\n\t\tthrow new TypeError(`expected array, got ${describe(value)}`);\n\t}\n\n\treturn value;\n}\n\nfunction expectString(value: unknown, label: string): string {\n\tif (typeof value !== \"string\") {\n\t\tthrow new TypeError(`expected ${label} to be a string, got ${describe(value)}`);\n\t}\n\n\treturn value;\n}\n\nfunction stripNulls(value: unknown): unknown {\n\tif (value === null) {\n\t\treturn undefined;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\treturn value.map(stripNulls);\n\t}\n\n\tif (isObjectPayload(value)) {\n\t\treturn Object.fromEntries(\n\t\t\tObject.entries(value).map(([key, child]) => [key, stripNulls(child)]),\n\t\t);\n\t}\n\n\treturn value;\n}\n\nfunction parseResource(value: unknown): MantleResource {\n\tconst resource = expectObject(value);\n\tconst id = expectString(resource[\"id\"], \"id\");\n\tconst underscoreIndex = id.indexOf(\"_\");\n\tif (underscoreIndex <= 0 || underscoreIndex === id.length - 1) {\n\t\tthrow new Error(`expected id of form <kind>_<key>, got \"${id}\"`);\n\t}\n\n\tconst kind = id.slice(0, underscoreIndex);\n\tconst key = id.slice(underscoreIndex + 1);\n\n\tconst inputsObject = expectObject(resource[\"inputs\"]);\n\tconst inputs = stripNulls(inputsObject[kind]);\n\n\tconst outputsRaw = resource[\"outputs\"];\n\tconst outputs = isObjectPayload(outputsRaw) ? stripNulls(outputsRaw[kind]) : undefined;\n\n\tconst dependencies = expectArray(resource[\"dependencies\"]).map((dep) => {\n\t\treturn expectString(dep, \"dependency\");\n\t});\n\n\treturn { key, dependencies, inputs, kind, outputs };\n}\n\nfunction parseEnvironments(raw: unknown): Readonly<Record<string, ReadonlyArray<MantleResource>>> {\n\tconst environmentsObject = expectObject(raw);\n\treturn Object.fromEntries(\n\t\tObject.entries(environmentsObject).map(([name, value]) => [\n\t\t\tname,\n\t\t\texpectArray(value).map(parseResource),\n\t\t]),\n\t);\n}\n\nfunction parseFailedFrom(err: unknown, sourceFile: string): Result<MantleStateV6, MigrateError> {\n\tconst reason = err instanceof Error ? err.message : String(err);\n\treturn {\n\t\terr: { kind: \"stateParseFailed\", path: sourceFile, reason },\n\t\tsuccess: false,\n\t};\n}\n","import { stringifyYAML } from \"confbox\";\n\nimport type { Config } from \"../schema.ts\";\n\nconst IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\n/**\n * Inputs for {@link serializeConfig}. Format dispatch lives on\n * `configFormat` so a single function shape covers both TypeScript and\n * YAML output.\n */\nexport interface SerializeConfigOptions {\n\t/** Validated bedrock config to render. */\n\treadonly config: Config;\n\t/** Output format. TypeScript emits `defineConfig({...})`; YAML emits a `bedrock.config.yaml` body. */\n\treadonly configFormat: \"typescript\" | \"yaml\";\n}\n\n/**\n * Render a bedrock `Config` as the source text of a `bedrock.config.{ts,yaml}`\n * file the user can write straight to disk.\n *\n * The TypeScript branch emits a hand-authored object literal: tab-indented,\n * with double-quoted string values and unquoted keys for valid identifier\n * names (quoted otherwise). The YAML branch delegates to `stringifyYAML`,\n * which already drops `undefined`-valued properties so the output never\n * surfaces `null` or `~` for absent managed fields. Both shapes round-trip\n * through `loadConfig` cleanly.\n *\n * @param options - Render inputs.\n * @returns A UTF-8 source string ending with a trailing newline.\n */\nexport function serializeConfig(options: SerializeConfigOptions): string {\n\tif (options.configFormat === \"yaml\") {\n\t\treturn `${stringifyYAML(options.config)}\\n`;\n\t}\n\n\tconst body = renderObjectLiteral(options.config, 0);\n\treturn [\n\t\t'import { defineConfig } from \"@bedrock-rbx/core/config\";',\n\t\t\"\",\n\t\t`export default defineConfig(${body});`,\n\t\t\"\",\n\t].join(\"\\n\");\n}\n\nfunction renderValue(value: unknown, depth: number): string {\n\tif (Array.isArray(value)) {\n\t\treturn renderArrayLiteral(value, depth);\n\t}\n\n\tif (value !== null && typeof value === \"object\") {\n\t\treturn renderObjectLiteral(value, depth);\n\t}\n\n\tif (typeof value === \"string\") {\n\t\treturn JSON.stringify(value);\n\t}\n\n\treturn String(value);\n}\n\nfunction renderObjectLiteral(value: object, depth: number): string {\n\tconst entries = Object.entries(value).filter(([, fieldValue]) => fieldValue !== undefined);\n\tif (entries.length === 0) {\n\t\treturn \"{}\";\n\t}\n\n\tconst innerIndent = \"\\t\".repeat(depth + 1);\n\tconst closeIndent = \"\\t\".repeat(depth);\n\tconst lines = entries.map(([key, fieldValue]) => {\n\t\tconst renderedKey = IDENTIFIER_PATTERN.test(key) ? key : JSON.stringify(key);\n\t\treturn `${innerIndent}${renderedKey}: ${renderValue(fieldValue, depth + 1)}`;\n\t});\n\treturn `{\\n${lines.join(\",\\n\")}\\n${closeIndent}}`;\n}\n\nfunction renderArrayLiteral(value: ReadonlyArray<unknown>, depth: number): string {\n\tif (value.length === 0) {\n\t\treturn \"[]\";\n\t}\n\n\tconst innerIndent = \"\\t\".repeat(depth + 1);\n\tconst closeIndent = \"\\t\".repeat(depth);\n\tconst lines = value.map((item) => `${innerIndent}${renderValue(item, depth + 1)}`);\n\treturn `[\\n${lines.join(\",\\n\")}\\n${closeIndent}]`;\n}\n","import type { MigrationSummary, MigrationWarning } from \"./migration-report.ts\";\n\nconst ZERO_SUMMARY: MigrationSummary = {\n\tambiguousCount: 0,\n\tblockedCount: 0,\n\tdeferredCount: 0,\n\tinterpretiveCount: 0,\n};\n\n/**\n * Fold a `MigrationWarning` array into a `MigrationSummary` so the\n * report's aggregate counts are derived from the warning list rather\n * than maintained in parallel. Future warning-emitting slices thread\n * through this helper instead of having to remember to update the\n * summary at every emission site.\n *\n * @param warnings - Warnings to aggregate; the empty array yields a\n * zeroed summary.\n * @returns Per-kind counts.\n */\nexport function summarizeWarnings(warnings: ReadonlyArray<MigrationWarning>): MigrationSummary {\n\treturn warnings.reduce<MigrationSummary>((accumulator, warning) => {\n\t\treturn {\n\t\t\t...accumulator,\n\t\t\t[`${warning.kind}Count`]: accumulator[`${warning.kind}Count`] + 1,\n\t\t};\n\t}, ZERO_SUMMARY);\n}\n","import { join } from \"node:path\";\n\nimport { sha256Hex } from \"../core/kinds/hash.ts\";\nimport type { EnvironmentFoldResult } from \"../core/migrate/fold-environment.ts\";\nimport type { PassFoldEntry } from \"../core/migrate/fold-passes.ts\";\nimport type { ProductFoldEntry } from \"../core/migrate/fold-products.ts\";\nimport type { MigrationWarning } from \"../core/migrate/migration-report.ts\";\nimport { asSha256Hex, type ResourceKey, type Sha256Hex } from \"../types/ids.ts\";\n\n/**\n * Result of walking each environment's pass and product entries and\n * recomputing the SHA-256 digest of every locale-keyed icon path from\n * disk. `passHashesByEnvironment` and `productHashesByEnvironment` carry\n * the recomputed digests keyed by environment then by resource key; the\n * inner record mirrors `GamePassDesiredState.iconFileHashes` /\n * `DeveloperProductDesiredState.iconFileHashes`. `warnings` carries one\n * `kind: \"ambiguous\"` warning per resource whose icon could not be read so\n * the caller can fall back to the Mantle-recorded hashes without losing\n * the diagnostic.\n */\nexport interface IconHashRecomputation {\n\t/** Recomputed pass-icon digests keyed by environment then by pass key. */\n\treadonly passHashesByEnvironment: ReadonlyMap<\n\t\tstring,\n\t\tReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>\n\t>;\n\t/** Recomputed product-icon digests keyed by environment then by product key. */\n\treadonly productHashesByEnvironment: ReadonlyMap<\n\t\tstring,\n\t\tReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>\n\t>;\n\t/** One ambiguous warning per resource whose icon could not be read. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\n/** Inputs for {@link recomputeIconHashes}. */\ninterface RecomputeIconHashesInputs {\n\t/** Per-environment fold results carrying pass and product entries to walk. */\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\t/** Reads file bytes; same shape as `MigrateMantleStateDeps.readFile`. */\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\t/** Directory the state file lives in; relative icon paths resolve against it. */\n\treadonly stateFileDirectory: string;\n}\n\ninterface IconWalkEntry {\n\treadonly key: ResourceKey;\n\treadonly iconPath: string;\n\treadonly mantlePath: string;\n}\n\ninterface IconWalkInputs {\n\treadonly entries: ReadonlyArray<IconWalkEntry>;\n\treadonly environmentName: string;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\treadonly stateFileDirectory: string;\n}\n\ninterface IconWalkResult {\n\treadonly perKey: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>;\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface AmbiguousIconWarningInputs {\n\treadonly environmentName: string;\n\treadonly mantlePath: string;\n\treadonly resolvedPath: string;\n}\n\ninterface WalkEnvironmentInputs {\n\treadonly environmentName: string;\n\treadonly folded: EnvironmentFoldResult;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\treadonly stateFileDirectory: string;\n}\n\ninterface WalkEnvironmentResult {\n\treadonly passHashes: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>;\n\treadonly productHashes: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>;\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\n/**\n * Walk each environment's folded pass and product entries, resolve the\n * locale-keyed icon paths against `stateFileDirectory`, read the bytes via\n * the injected `readFile`, and compute the SHA-256 hex digest. Files that\n * cannot be read surface as `ambiguous` `MigrationWarning`s with the\n * environment-prefixed `mantlePath` and a hint pointing at the resolved\n * path; the caller carries the Mantle-recorded hashes forward as a\n * fallback. Products without an icon partner are silently skipped.\n *\n * @param inputs - Per-environment fold results plus I/O dependencies.\n * @returns Per-environment recomputed pass and product hashes plus\n * accumulated ambiguous warnings.\n */\nexport async function recomputeIconHashes(\n\tinputs: RecomputeIconHashesInputs,\n): Promise<IconHashRecomputation> {\n\tconst walked = await Promise.all(\n\t\t[...inputs.folds.entries()].map(async ([environment, folded]) => {\n\t\t\tconst result = await walkEnvironment({\n\t\t\t\tenvironmentName: environment,\n\t\t\t\tfolded,\n\t\t\t\treadFile: inputs.readFile,\n\t\t\t\tstateFileDirectory: inputs.stateFileDirectory,\n\t\t\t});\n\t\t\treturn [environment, result] as const;\n\t\t}),\n\t);\n\n\treturn collectRecomputation(walked);\n}\n\nfunction collectRecomputation(\n\twalked: ReadonlyArray<readonly [string, WalkEnvironmentResult]>,\n): IconHashRecomputation {\n\treturn {\n\t\tpassHashesByEnvironment: new Map(\n\t\t\twalked.map(([environment, walk]) => [environment, walk.passHashes]),\n\t\t),\n\t\tproductHashesByEnvironment: new Map(\n\t\t\twalked.map(([environment, walk]) => [environment, walk.productHashes]),\n\t\t),\n\t\twarnings: walked.flatMap(([, walk]) => walk.warnings),\n\t};\n}\n\nfunction passWalkEntry(entry: PassFoldEntry): IconWalkEntry {\n\treturn {\n\t\tkey: entry.key,\n\t\ticonPath: entry.entry.icon[\"en-us\"],\n\t\tmantlePath: entry.mantlePath,\n\t};\n}\n\nfunction productWalkEntries(entry: ProductFoldEntry): ReadonlyArray<IconWalkEntry> {\n\tif (entry.entry.icon === undefined) {\n\t\treturn [];\n\t}\n\n\treturn [\n\t\t{\n\t\t\tkey: entry.key,\n\t\t\ticonPath: entry.entry.icon[\"en-us\"],\n\t\t\tmantlePath: entry.mantlePath,\n\t\t},\n\t];\n}\n\nasync function tryRecomputeHash(\n\treadFile: (path: string) => Promise<Uint8Array>,\n\tpath: string,\n): Promise<Sha256Hex | undefined> {\n\ttry {\n\t\tconst bytes = await readFile(path);\n\t\treturn asSha256Hex(await sha256Hex(bytes));\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction buildAmbiguousIconWarning(inputs: AmbiguousIconWarningInputs): MigrationWarning {\n\treturn {\n\t\thint: `Could not read icon file at ${inputs.resolvedPath}; verify the file's location relative to the state file or correct the icon entry before re-running.`,\n\t\tkind: \"ambiguous\",\n\t\tmantlePath: `${inputs.environmentName}.${inputs.mantlePath}`,\n\t};\n}\n\nasync function walkIconEntries(inputs: IconWalkInputs): Promise<IconWalkResult> {\n\tconst perKey = new Map<ResourceKey, Record<\"en-us\", Sha256Hex>>();\n\tconst warnings: Array<MigrationWarning> = [];\n\tfor (const entry of inputs.entries) {\n\t\tconst resolved = join(inputs.stateFileDirectory, entry.iconPath);\n\t\tconst recomputed = await tryRecomputeHash(inputs.readFile, resolved);\n\t\tif (recomputed === undefined) {\n\t\t\twarnings.push(\n\t\t\t\tbuildAmbiguousIconWarning({\n\t\t\t\t\tenvironmentName: inputs.environmentName,\n\t\t\t\t\tmantlePath: entry.mantlePath,\n\t\t\t\t\tresolvedPath: resolved,\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tperKey.set(entry.key, { \"en-us\": recomputed });\n\t\t}\n\t}\n\n\treturn { perKey, warnings };\n}\n\nasync function walkEnvironment(inputs: WalkEnvironmentInputs): Promise<WalkEnvironmentResult> {\n\tconst passWalk = await walkIconEntries({\n\t\tentries: inputs.folded.passes.map(passWalkEntry),\n\t\tenvironmentName: inputs.environmentName,\n\t\treadFile: inputs.readFile,\n\t\tstateFileDirectory: inputs.stateFileDirectory,\n\t});\n\tconst productWalk = await walkIconEntries({\n\t\tentries: inputs.folded.products.flatMap(productWalkEntries),\n\t\tenvironmentName: inputs.environmentName,\n\t\treadFile: inputs.readFile,\n\t\tstateFileDirectory: inputs.stateFileDirectory,\n\t});\n\treturn {\n\t\tpassHashes: passWalk.perKey,\n\t\tproductHashes: productWalk.perKey,\n\t\twarnings: [...passWalk.warnings, ...productWalk.warnings],\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { readFile as nodeReadFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nimport { buildState } from \"../core/migrate/build-state.ts\";\nimport { factorizeEnvironments } from \"../core/migrate/factorize-environments.ts\";\nimport { type EnvironmentFoldResult, foldEnvironment } from \"../core/migrate/fold-environment.ts\";\nimport type {\n\tMigrateError,\n\tMigrationReport,\n\tMigrationWarning,\n} from \"../core/migrate/migration-report.ts\";\nimport { parseState } from \"../core/migrate/parse-state.ts\";\nimport { serializeConfig } from \"../core/migrate/serialize-config.ts\";\nimport { summarizeWarnings } from \"../core/migrate/summarize-warnings.ts\";\nimport type { MantleStateV6 } from \"../core/migrate/types.ts\";\nimport { type Config, validateConfig } from \"../core/schema.ts\";\nimport type { BedrockState } from \"../core/state.ts\";\nimport type { ResourceKey, Sha256Hex } from \"../types/ids.ts\";\nimport { type IconHashRecomputation, recomputeIconHashes } from \"./recompute-icon-hashes.ts\";\n\ntype ConfigFormat = \"typescript\" | \"yaml\";\n\nconst FILE_MISSING_CODES = new Set([\"ENOENT\"]);\n\n/**\n * Inputs for `migrateMantleState`. The state file is read via\n * `readFile` (defaults to `node:fs/promises.readFile`) so callers can\n * inject in-memory fixtures from tests and the JSDoc `@example` block\n * stays self-contained.\n *\n * `configFormat` selects the output shape: `\"typescript\"` emits a\n * `bedrock.config.ts` with `defineConfig({...})`; `\"yaml\"` emits a\n * `bedrock.config.yaml` body. Both shapes round-trip through\n * `loadConfig` cleanly.\n */\nexport interface MigrateMantleStateDeps {\n\t/**\n\t * Output format for the emitted bedrock config file. `\"typescript\"`\n\t * produces a `defineConfig({...})` module; `\"yaml\"` produces a YAML\n\t * body whose keys match the `Config` schema.\n\t */\n\treadonly configFormat: ConfigFormat;\n\t/**\n\t * Environment in the input state file whose resolved values seed\n\t * the root config. Required when the state file declares more than\n\t * one environment; ignored when only one environment is present.\n\t */\n\treadonly primaryEnvironment?: string;\n\t/**\n\t * Reads file bytes; defaults to `node:fs/promises.readFile`. Kept\n\t * `Uint8Array`-typed to match `deploy`, `buildDesired`, and\n\t * `buildDefaultRegistry`. UTF-8 decoding happens inside the migrator\n\t * before YAML parsing.\n\t */\n\treadonly readFile?: (path: string) => Promise<Uint8Array>;\n\t/** Absolute path to the `.mantle-state.yml` file to migrate. */\n\treadonly stateFilePath: string;\n}\n\ninterface FinalizeReportInputs {\n\treadonly config: Config;\n\treadonly configFormat: ConfigFormat;\n\treadonly factorizeWarnings: ReadonlyArray<MigrationWarning>;\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\treadonly iconRecomputation: IconHashRecomputation;\n}\n\ninterface AssembleReportInputs {\n\treadonly configFormat: ConfigFormat;\n\treadonly primaryEnvironment: string | undefined;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\treadonly state: MantleStateV6;\n\treadonly stateFilePath: string;\n}\n\n/**\n * Read a Mantle state file and produce a `MigrationReport` containing a\n * bedrock config, per-environment `BedrockState`s, and a structured list\n * of fields that did not migrate verbatim.\n *\n * Skeleton: handles single-environment or multi-environment states with\n * universe, place, and game-pass resources. The primary environment\n * auto-picks when only one environment is present; multi-environment\n * inputs without an explicit `primaryEnvironment` return\n * `Err({ kind: \"primaryEnvironmentRequired\", available })` so the\n * migrator never silently picks a winner. Future slices add social\n * links and the deferred / blocked warning categories.\n *\n * @param deps - Inputs for the migration.\n * @returns `Ok` with a `MigrationReport` on success, or `Err` with a\n * discriminated `MigrateError` on failure.\n * @rejects Re-thrown `readFile` failure when the underlying error code is\n * not in the recognized \"missing file\" set; surfaced so callers see\n * permission or filesystem outages instead of having them coerced to\n * `stateFileNotFound`.\n * @example\n *\n * ```ts\n * import { migrateMantleState } from \"@bedrock-rbx/core\";\n *\n * const yaml = [\n * 'version: \"6\"',\n * \"environments:\",\n * \" production:\",\n * \" - id: experience_singleton\",\n * \" inputs:\",\n * \" experience:\",\n * \" groupId: ~\",\n * \" outputs:\",\n * \" experience:\",\n * \" assetId: 6031475575\",\n * \" startPlaceId: 17613681043\",\n * \" dependencies: []\",\n * \"\",\n * ].join(\"\\n\");\n *\n * async function readFile(): Promise<Uint8Array> {\n * return new TextEncoder().encode(yaml);\n * }\n *\n * return migrateMantleState({\n * configFormat: \"typescript\",\n * readFile,\n * stateFilePath: \".mantle-state.yml\",\n * }).then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.config.universe?.universeId).toBe(\"6031475575\");\n * }\n * });\n * ```\n */\nexport async function migrateMantleState(\n\tdeps: MigrateMantleStateDeps,\n): Promise<Result<MigrationReport, MigrateError>> {\n\tconst readFile = deps.readFile ?? nodeReadFile;\n\n\tlet bytes: Uint8Array;\n\ttry {\n\t\tbytes = await readFile(deps.stateFilePath);\n\t} catch (err) {\n\t\tif (isFileMissing(err)) {\n\t\t\treturn {\n\t\t\t\terr: { kind: \"stateFileNotFound\", path: deps.stateFilePath },\n\t\t\t\tsuccess: false,\n\t\t\t};\n\t\t}\n\n\t\tthrow err;\n\t}\n\n\tconst raw = new TextDecoder(\"utf-8\").decode(bytes);\n\tconst parsed = parseState(raw, deps.stateFilePath);\n\tif (!parsed.success) {\n\t\treturn parsed;\n\t}\n\n\treturn assembleReport({\n\t\tconfigFormat: deps.configFormat,\n\t\tprimaryEnvironment: deps.primaryEnvironment,\n\t\treadFile,\n\t\tstate: parsed.data,\n\t\tstateFilePath: deps.stateFilePath,\n\t});\n}\n\nconst EMPTY_HASHES: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>> = new Map();\n\ninterface BuildStatesByEnvironmentInputs {\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\treadonly passHashesByEnvironment: ReadonlyMap<\n\t\tstring,\n\t\tReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>\n\t>;\n\treadonly productHashesByEnvironment: ReadonlyMap<\n\t\tstring,\n\t\tReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>\n\t>;\n}\n\nfunction buildStatesByEnvironment(\n\tinputs: BuildStatesByEnvironmentInputs,\n): Readonly<Record<string, BedrockState>> {\n\treturn Object.fromEntries(\n\t\t[...inputs.folds.entries()].map(([name, folded]): [string, BedrockState] => {\n\t\t\treturn [\n\t\t\t\tname,\n\t\t\t\tbuildState({\n\t\t\t\t\tenvironment: name,\n\t\t\t\t\tfolded,\n\t\t\t\t\tpassIconHashesByKey: inputs.passHashesByEnvironment.get(name) ?? EMPTY_HASHES,\n\t\t\t\t\tproductIconHashesByKey:\n\t\t\t\t\t\tinputs.productHashesByEnvironment.get(name) ?? EMPTY_HASHES,\n\t\t\t\t}),\n\t\t\t];\n\t\t}),\n\t);\n}\n\nfunction prefixMantlePath(warning: MigrationWarning, environmentName: string): MigrationWarning {\n\treturn { ...warning, mantlePath: `${environmentName}.${warning.mantlePath}` };\n}\n\nfunction collectFoldWarnings(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n): ReadonlyArray<MigrationWarning> {\n\treturn [...folds.entries()].flatMap(([name, fold]) => {\n\t\treturn fold.warnings.map((warning) => prefixMantlePath(warning, name));\n\t});\n}\n\nfunction buildReport(inputs: FinalizeReportInputs, validated: Config): MigrationReport {\n\tconst {\n\t\tpassHashesByEnvironment,\n\t\tproductHashesByEnvironment,\n\t\twarnings: iconWarnings,\n\t} = inputs.iconRecomputation;\n\tconst warnings = [\n\t\t...collectFoldWarnings(inputs.folds),\n\t\t...inputs.factorizeWarnings,\n\t\t...iconWarnings,\n\t];\n\treturn {\n\t\tconfig: validated,\n\t\tconfigFileContent: serializeConfig({\n\t\t\tconfig: validated,\n\t\t\tconfigFormat: inputs.configFormat,\n\t\t}),\n\t\tstatesByEnvironment: buildStatesByEnvironment({\n\t\t\tfolds: inputs.folds,\n\t\t\tpassHashesByEnvironment,\n\t\t\tproductHashesByEnvironment,\n\t\t}),\n\t\tsummary: summarizeWarnings(warnings),\n\t\twarnings,\n\t};\n}\n\nfunction finalizeReport(inputs: FinalizeReportInputs): Result<MigrationReport, MigrateError> {\n\tconst validated = validateConfig(inputs.config, \"<migrate-mantle-state>\");\n\tif (!validated.success) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tcause: validated.err,\n\t\t\t\tkind: \"internalError\",\n\t\t\t\treason: \"migrator emitted a config that failed validateConfig\",\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: buildReport(inputs, validated.data), success: true };\n}\n\nasync function assembleReport(\n\tinputs: AssembleReportInputs,\n): Promise<Result<MigrationReport, MigrateError>> {\n\tconst available = Object.keys(inputs.state.environments);\n\tconst folds: ReadonlyMap<string, EnvironmentFoldResult> = new Map(\n\t\tavailable.map((name) => [name, foldEnvironment(inputs.state.environments[name] ?? [])]),\n\t);\n\tconst factorized = factorizeEnvironments({\n\t\tfolds,\n\t\tprimaryEnvironment: inputs.primaryEnvironment,\n\t});\n\tif (!factorized.success) {\n\t\treturn factorized;\n\t}\n\n\tconst iconRecomputation = await recomputeIconHashes({\n\t\tfolds,\n\t\treadFile: inputs.readFile,\n\t\tstateFileDirectory: dirname(inputs.stateFilePath),\n\t});\n\treturn finalizeReport({\n\t\tconfig: factorized.data.config,\n\t\tconfigFormat: inputs.configFormat,\n\t\tfactorizeWarnings: factorized.data.warnings,\n\t\tfolds,\n\t\ticonRecomputation,\n\t});\n}\n\nfunction isFileMissing(err: unknown): boolean {\n\treturn (\n\t\ttypeof err === \"object\" &&\n\t\terr !== null &&\n\t\t\"code\" in err &&\n\t\ttypeof err.code === \"string\" &&\n\t\tFILE_MISSING_CODES.has(err.code)\n\t);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,kBAA6B;AAC5C,QAAO;EACN,SAAS,YAAY;AACpB,UAAO,QAAQ;;EAEhB,QAAQ,YAAY;AACnB,SAAM,QAAQ;;EAEf,WAAW,YAAY;AACtB,OAAI,MAAM,QAAQ;;EAEnB,aAAa,YAAY;AACxB,OAAI,QAAQ,QAAQ;;EAErB,aAAa,YAAY;AACxB,OAAI,QAAQ,QAAQ;;EAErB,QAAQ,YAAY;AACnB,SAAM,QAAQ;;EAEf;;;;;;;;;;;;;;;;AC6CF,SAAgB,kBAAkB,KAAkB,MAAuB;AAC1E,KAAI,IAAI,SAAS,eAAe;AAC/B,OAAK,MAAM,WAAW,IAAI,MAAM,SAC/B,MAAK,SAAS,qBAAqB,QAAQ,IAAI,KAAK,iBAAiB,QAAQ,GAAG;AAGjF;;AAGD,MAAK,SAAS,mBAAmB,IAAI,CAAC;;;;;;;;;AAUvC,SAAgB,iBAAiB,KAAwB,MAAuB;AAC/E,MAAK,SAAS,kBAAkB,IAAI,CAAC;;;;;;;;;;;AAYtC,SAAgB,oBAAoB,OAA4B,MAAuB;AACtF,MAAK,SAAS,qBAAqB,MAAM,CAAC;;;;;;;;;;;AAY3C,SAAgB,6BAA6B,OAAgB,MAAuB;AACnF,MAAK,SAAS,8BAA8B,cAAc,MAAM,GAAG;;;;;;;;;AAUpE,SAAgB,wBAAwB,KAAwB,MAAuB;AACtF,MAAK,SAAS,yBAAyB,IAAI,CAAC;;;;;;;;;;AAW7C,SAAgB,mBAAmB,KAAmB,MAAuB;AAC5E,MAAK,SAAS,oBAAoB,IAAI,CAAC;;;;;;;;;AAUxC,SAAgB,0BACf,KACA,MACO;AACP,MAAK,SAAS,2BAA2B,IAAI,CAAC;;;;;;;;;;;;;;;;;AAkB/C,SAAgB,uBAAuB,OAA+B,MAAuB;CAC5F,MAAM,EAAE,gBAAgB,cAAc,eAAe,sBAAsB,MAAM;AACjF,KAAI,iBAAiB,GAAG;AACvB,OAAK,SACJ,oBAAoB,OAAO,eAAe,CAAC,+BAA+B,MAAM,aAChF;AACD;;CAGD,MAAM,aAAa,eAAe,gBAAgB;AAClD,KAAI,aAAa,EAChB,MAAK,WACJ,2BAA2B,MAAM,WAAW,OAAO,OAAO,WAAW,CAAC,gCACtE;;;;;;;;;AAWH,SAAgB,sBAAsB,OAA8B,MAAuB;AAC1F,MAAK,SACJ,2BAA2B,MAAM,YAAY,KAAK,MAAM,IAAI,KAAK,KAAK,MAAM,IAAI,SAChF;;AAGF,SAAS,iBAAiB,KAA8B;CACvD,MAAM,WAAW,IAAI,eAAe,SAAS;CAC7C,MAAM,QAAQ,WAAW,WAAW;CACpC,MAAM,UAAU,WAAW,SAAS;CACpC,MAAM,YAAY,IAAI,eAAe,KAAK,UAAU,IAAI,MAAM,GAAG,CAAC,KAAK,KAAK;AAC5E,QAAO,GAAG,IAAI,QAAQ,MAAM,IAAI,aAAa,qBAAqB,MAAM,GAAG,UAAU,UAAU,QAAQ;;AAGxG,SAAS,cAAc,OAAwB;AAC9C,KAAI,iBAAiB,MACpB,QAAO,MAAM;AAMd,KAAI;AACH,SAAO,OAAO,MAAM;SACb;AACP,SAAO;;;AAIT,SAAS,iBAAiB,OAA2B;AACpD,SAAQ,MAAM,MAAd;EACC,KAAK;AACJ,OAAI,MAAM,iBAAiB,gBAC1B,QAAO,iBAAiB,MAAM,MAAM;AAGrC,UAAO,MAAM,MAAM;EAEpB,KAAK,kBACJ,QAAO,qBAAqB,cAAc,MAAM,MAAM;EAEvD,KAAK,oBACJ,QAAO;;;AAKV,SAAS,mBAAmB,OAAkC;AAC7D,SAAQ,MAAM,MAAd;EACC,KAAK,iBACJ,QAAO,QAAQ,MAAM,IAAI,KAAK,MAAM,SAAS,KAAK,MAAM;EAEzD,KAAK,sBACJ,QAAO,QAAQ,MAAM,IAAI,KAAK,MAAM;EAErC,KAAK,yBAAyB;GAC7B,MAAM,CAAC,OAAO,UAAU,MAAM;AAC9B,UAAO,QAAQ,MAAM,SAAS,OAAO,KAAK,MAAM;;;;AAKnD,SAAS,kBAAkB,KAA0B;AACpD,SAAQ,IAAI,MAAZ;EACC,KAAK,uBACJ,QAAO,GAAG,IAAI,WAAW,2BAA2B,IAAI;EAEzD,KAAK,eACJ,QAAO,2BAA2B,IAAI;EAEvC,KAAK,qBACJ,QAAO,GAAG,IAAI,WAAW,IAAI,IAAI;EAElC,KAAK,cACJ,QAAO,GAAG,IAAI,WAAW,IAAI,IAAI;EAElC,KAAK,oBAAoB;GACxB,MAAM,QAAQ,IAAI,OAAO;AACzB,UAAO,UAAU,KAAA,IACd,GAAG,IAAI,WAAW,aAClB,GAAG,IAAI,WAAW,IAAI,MAAM,KAAK,KAAK,IAAI,CAAC,GAAG,MAAM;;;;AAK1D,SAAS,iBAAiB,OAA2B;AACpD,QAAO,IAAI,MAAM,KAAK,KAAK,MAAM;;AAIlC,SAAS,mBAAmB,KAA4D;AACvF,SAAQ,IAAI,MAAZ;EACC,KAAK,qBACJ,QAAO,8BAA8B,mBAAmB,IAAI,MAAM;EAEnE,KAAK,mBACJ,QAAO,uBAAuB,kBAAkB,IAAI,MAAM;EAE3D,KAAK,sBACJ,QAAO,SAAS,IAAI,IAAI,gBAAgB,IAAI,aAAa,uBAAuB,IAAI,YAAY;EAEjG,KAAK,uBACJ,QAAO,UAAU,IAAI,IAAI,gBAAgB,IAAI,aAAa,uBAAuB,IAAI,YAAY;EAElG,KAAK,0BACJ,QAAO,wBAAwB,IAAI,aAAa,uBAAuB,IAAI,YAAY;EAExF,KAAK,oBACJ,QAAO,4CAA4C,IAAI,SAAS;EAEjE,KAAK,wBACJ,QAAO,4BAA4B,IAAI,QAAQ,KAAK,IAAI,KAAK;EAE9D,KAAK,qBACJ,QAAO,yCAAyC,IAAI,YAAY;EAEjE,KAAK,kBACJ,QAAO,qBAAqB,iBAAiB,IAAI,MAAM;EAExD,KAAK,mBACJ,QAAO,sBAAsB,iBAAiB,IAAI,MAAM;EAEzD,KAAK,qBACJ,QAAO,wBAAwB,IAAI,YAAY,eAAe,IAAI,SAAS,KAAK,KAAK,CAAC;EAEvF,KAAK,qBACJ,QAAO,8BAA8B,IAAI,QAAQ,KAAK,IAAI,KAAK;;;AAKlE,SAAS,kBAAkB,KAAgC;AAC1D,SAAQ,IAAI,MAAZ;EACC,KAAK,eACJ,QAAO,6BAA6B,IAAI,KAAK;EAE9C,KAAK,kBACJ,QAAO,2BAA2B,IAAI;EAEvC,KAAK,cACJ,QAAO,mBAAmB,IAAI,KAAK;;;AAKtC,SAAS,qBAAqB,OAAoC;CACjE,MAAM,EAAE,aAAa,QAAQ;AAC7B,KAAI,IAAI,SAAS,eAChB,QAAO,GAAG,YAAY,gCAAgC,IAAI,MAAM;AAGjE,QAAO,GAAG,YAAY,8BAA8B,OAAO,IAAI,SAAS;;AAGzE,SAAS,yBAAyB,KAAgC;AACjE,KAAI,IAAI,SAAS,gBAChB,QAAO,6BAA6B,IAAI,SAAS,gBAAgB,IAAI,UAAU,KAAK,KAAK,CAAC;AAG3F,QAAO,kBAAkB,IAAI;;AAG9B,SAAS,oBAAoB,KAA2B;AACvD,SAAQ,IAAI,MAAZ;EACC,KAAK,gBACJ,QAAO,2BAA2B,IAAI,OAAO,IAAI,kBAAkB,IAAI,MAAM,CAAC;EAE/E,KAAK,6BACJ,QAAO,wBAAwB,IAAI,QAAQ,0BAA0B,IAAI,UAAU,KAAK,KAAK,CAAC;EAE/F,KAAK,6BACJ,QAAO,4CAA4C,IAAI,UAAU,KAAK,KAAK,CAAC;EAE7E,KAAK,oBACJ,QAAO,mCAAmC,IAAI,KAAK;EAEpD,KAAK,mBACJ,QAAO,yBAAyB,IAAI,KAAK,yBAAyB,IAAI;EAEvE,KAAK,gCACJ,QAAO,qCAAqC,IAAI,MAAM,gBAAgB,IAAI,UAAU,KAAK,KAAK,CAAC;;;AAKlG,SAAS,2BAA2B,KAA+D;AAClG,SAAQ,IAAI,MAAZ;EACC,KAAK,oBACJ,QAAO,4CAA4C,IAAI,SAAS;EAEjE,KAAK,qBACJ,QAAO,8BAA8B,IAAI,QAAQ,KAAK,IAAI,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACzVlE,SAAgB,mBACf,QACA,aAC+C;CAC/C,MAAM,WAAW,OAAO,aAAa,cAAc;AACnD,KAAI,aAAa,KAAA,EAChB,QAAO;EAAE,MAAM;EAAU,SAAS;EAAM;AAGzC,KAAI,OAAO,UAAU,KAAA,EACpB,QAAO;EAAE,MAAM,OAAO;EAAO,SAAS;EAAM;AAG7C,QAAO;EAAE,KAAK;GAAE;GAAa,MAAM;GAAsB;EAAE,SAAS;EAAO;;;;;;;;;ACjE5E,MAAa,8BAA8B;AAE3C,MAAM,uBAAuB,IAAI,OAAO,4BAA4B;AAEpE,MAAM,0BAA0B;AAEhC,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;AA0C3B,SAAgB,cAAc,KAAiC;AAC9D,QAAO,qBAAqB,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BtC,SAAgB,cAAc,KAA0B;AACvD,KAAI,CAAC,cAAc,IAAI,CACtB,OAAM,IAAI,WACT,0BAA0B,OAAO,qBAAqB,CAAC,QAAQ,KAAK,UAAU,IAAI,CAAC,GACnF;AAGF,QAAO;;;;;;;;;;;;;;;;;;;;;;;AAwBR,SAAgB,gBAAgB,KAAmC;AAClE,QAAO,wBAAwB,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCzC,SAAgB,gBAAgB,KAA4B;AAC3D,KAAI,CAAC,gBAAgB,IAAI,CACxB,OAAM,IAAI,WACT,gEAAgE,OAC/D,wBACA,CAAC,QAAQ,KAAK,UAAU,IAAI,CAAC,GAC9B;AAGF,QAAO;;;;;;;;;;;;;;;;;;;;;;;;AAyBR,SAAgB,YAAY,KAA+B;AAC1D,QAAO,mBAAmB,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCpC,SAAgB,YAAY,KAAwB;AACnD,KAAI,CAAC,YAAY,IAAI,CACpB,OAAM,IAAI,WACT,0DAA0D,OACzD,mBACA,CAAC,QAAQ,IAAI,OAAO,UAAU,KAAK,UAAU,IAAI,CAAC,GACnD;AAGF,QAAO;;;;;;;;;;;;;;;;AChNR,MAAa,0BAA0B;AAEvC,MAAM,2BAA2B,IAAI,OAAO,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BpE,SAAgB,wBAAwB,aAAiD;AACxF,KAAI,CAAC,yBAAyB,KAAK,YAAY,CAC9C,QAAO;EACN,KAAK;GACJ,MAAM;GACN,MAAM;GACN,QAAQ,wCAAwC,OAAO,yBAAyB;GAChF;EACD,SAAS;EACT;AAGF,QAAO;EAAE,MAAM;EAAa,SAAS;EAAM;;;;;;;;;;;ACxD5C,eAAsB,UAAU,OAAoC;CAInE,MAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,WAAW,KAAK,MAAM,CAAC;AAC5E,QAAO,MAAM,KAAK,IAAI,WAAW,OAAO,GAAG,SAAS,KAAK,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,KACvF,GACA;;;;ACVF,MAAM,sBAAsB,IAAI,WAAW;CAC1C;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAAM;CAC1F;CAAM;CACN,CAAC;;;;;;;AAQF,MAAa,qBAAqB;;;;;;;;;AAkBlC,SAAgB,mBAAmB,MAAuB;AACzD,QAAO,SAAS;;;;;;;;;;;AAYjB,SAAgB,iBACf,UACwC;AACxC,QAAO,OAAO,SAAS;AACtB,MAAI,mBAAmB,KAAK,CAC3B,QAAO,IAAI,WAAW,oBAAoB;AAG3C,SAAO,SAAS,KAAK;;;;;;;;;;;;;;;;;AC/CvB,eAAsB,UACrB,QACA,IACiD;AACjD,KAAI,mBAAmB,OAAO,SAAS,CACtC,QAAO;EAAE,MAAM,IAAI,WAAW,oBAAoB;EAAE,SAAS;EAAM;AAGpE,KAAI;AACH,SAAO;GAAE,MAAM,MAAM,GAAG,SAAS,OAAO,SAAS;GAAE,SAAS;GAAM;UAC1D,KAAK;AACb,SAAO;GACN,KAAK;IACJ,KAAK,OAAO;IACZ,UAAU,OAAO;IACjB,MAAM;IACN,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IACxD;GACD,SAAS;GACT;;;;;;;;;;;;ACrBH,MAAa,UAAyC,KAAK,EAC1D,SAAS,UACT,CAAC,CAAC,gBAAgB,SAAS;;;;;;;;;;;;;AAc5B,eAAsB,aACrB,QACA,IACgD;CAChD,MAAM,OAAO,MAAM,UAAU,QAAQ,GAAG;AACxC,KAAI,CAAC,KAAK,QACT,QAAO;AAGR,QAAO;EAAE,MAAM,YAAY,MAAM,UAAU,KAAK,KAAK,CAAC;EAAE,SAAS;EAAM;;;;;;;;;;;;;;AAexE,eAAsB,gBACrB,OACA,IACiE;CACjE,MAAM,OAAO,MAAM,aAAa;EAAE,KAAK,MAAM;EAAK,UAAU,MAAM,KAAK;EAAU,EAAE,GAAG;AACtF,KAAI,CAAC,KAAK,QACT,QAAO;AAGR,QAAO;EAAE,MAAM,EAAE,SAAS,KAAK,MAAM;EAAE,SAAS;EAAM;;;;;;;;;;;;;;;;;;;;;;;;AAyBvD,SAAgB,gBACf,SACA,SACU;AACV,KAAI,YAAY,KAAA,EACf,QAAO,YAAY,KAAA;AAGpB,KAAI,YAAY,KAAA,EACf,QAAO;AAGR,QAAO,QAAQ,aAAa,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCrC,SAAgB,mBACf,eACA,eACU;AACV,QAAO,CAAC,gBAAgB,eAAe,cAAc;;;;;;;;;;;;;;;AChHtD,SAAgB,wBACf,OACiC;CACjC,MAAM,iBAAiB,MAAM,UAAU;CACvC,MAAM,uBAAuB,MAAM,aAAa,KAAA;CAChD,MAAM,qBAAqB,OAAO,QAAQ,MAAM,aAAa;CAC7D,MAAM,2BAA2B,mBAAmB,MAClD,GAAG,iBAAiB,YAAY,UAAU,eAAe,KAAA,EAC1D;CAED,MAAM,oBAAoB,yBAAyB,gBAAgB,mBAAmB;CACtF,MAAM,aACL,wBAAwB,mBAAmB,KAAA,KAAa,CAAC,2BACtD,CACA;EACC,SACC;EACD,MAAM,CAAC,YAAY,aAAa;EAChC,CACD,GACA,EAAE;AAEN,QAAO,CAAC,GAAG,mBAAmB,GAAG,WAAW;;AAG7C,SAAS,yBACR,gBACA,oBACiC;AACjC,QAAO,mBAAmB,SAAS,CAAC,iBAAiB,iBAAiB;AACrE,MAAI,YAAY,aAAa,KAAA,EAC5B,QAAO,EAAE;AAGV,MAAI,mBAAmB,KAAA,KAAa,YAAY,SAAS,eAAe,KAAA,EACvE,QAAO,CACN;GACC,SACC;GACD,MAAM;IAAC;IAAgB;IAAiB;IAAY;IAAa;GACjE,CACD;AAGF,MAAI,mBAAmB,KAAA,KAAa,YAAY,SAAS,eAAe,KAAA,EACvE,QAAO,CACN;GACC,SACC;GACD,MAAM;IAAC;IAAgB;IAAiB;IAAY;IAAa;GACjE,CACD;AAGF,SAAO,EAAE;GACR;;;;;;;;;;;;;;;;;;;;;;;;;;;ACstBH,SAAgB,kBAAkB,QAAgD;AACjF,QAAO,OAAO,YAAY;;AAG3B,MAAMA,qBAAmB;AAEzB,MAAM,kBAAkB;AAExB,MAAM,eAAe;AAErB,MAAM,6BACL;;;;;;AAOD,MAAa,4BAA4B;;;;;;;;;;AAWzC,MAAa,uBAAuB;AAiBpC,MAAM,mBAf2B,KAAK;CACrC,gBAAgB;CAChB,SAAS;CACT,SAAS;CACT,UAAU;CACV,CAAC,CACA,gBAAgB,SAAS,CACzB,QAAQ,OAAO,QAAQ;AACvB,KAAI,OAAO,KAAK,MAAM,CAAC,WAAW,EACjC,QAAO,IAAI,OAAO,2BAA2B;AAG9C,QAAO;EACN,CAE+C,GAAGA,mBAAiB;AAetE,MAAM,gBAbwB,KAAK;CAClC,gBAAgB;CAChB,gBAAgB;CAChB,CAAC,CACA,gBAAgB,SAAS,CACzB,QAAQ,OAAO,QAAQ;AACvB,KAAI,OAAO,KAAK,MAAM,CAAC,WAAW,EACjC,QAAO,IAAI,OAAO,2BAA2B;AAG9C,QAAO;EACN,CAEyC,GAAGA,mBAAiB;AAiBhE,MAAM,kBAf0B,KAAK;CACpC,gBAAgB;CAChB,SAAS;CACT,SAAS;CACT,UAAU;CACV,CAAC,CACA,gBAAgB,SAAS,CACzB,QAAQ,OAAO,QAAQ;AACvB,KAAI,OAAO,KAAK,MAAM,CAAC,WAAW,EACjC,QAAO,IAAI,OAAO,2BAA2B;AAG9C,QAAO;EACN,CAE6C,GAAGA,mBAAiB;AAkBpE,MAAM,sBAhB8B,KAAK;CACxC,gBAAgB;CAChB,gBAAgB;CAChB,SAAS;CACT,SAAS;CACT,UAAU;CACV,CAAC,CACA,gBAAgB,SAAS,CACzB,QAAQ,OAAO,QAAQ;AACvB,KAAI,OAAO,KAAK,MAAM,CAAC,WAAW,EACjC,QAAO,IAAI,OAAO,2BAA2B;AAG9C,QAAO;EACN,CAEqD,GAAGA,mBAAiB;AAQ5E,MAAM,gBAAgB,KAAK;CAC1B,QAAQ;CACR,eAAe;CACf,QAAQ;CACR,UAAU;EACT,eAAe;CAChB,CAAC;AAEF,MAAM,mBAAmB,KAAK,GAC5B,KAAK,4BAA4B,MAAM,eACxC,CAAC,CAAC,gBAAgB,SAAS;AAE5B,MAAM,wBAAwB,KAAK;CAClC,QAAQ;CACR,eAAe;CACf,SAAS;CACT,6BAA6BA;CAC7B,UAAU;EACT,eAAe;CAChB,qBAAqBA;CACrB,CAAC,CAAC,gBAAgB,SAAS;AAE5B,MAAM,qBAAqB,KAAK,GAC9B,KAAK,4BAA4B,MAAM,uBACxC,CAAC,CAAC,gBAAgB,SAAS;AAE5B,MAAM,mBAAmB;AAEzB,MAAM,aAAa,KAAK;CACvB,gBAAgB;CAChB,gBAAgB;CAChB,YAAY;EACX,eAAe;CAChB,eAAe;CACf,CAAC,CAAC,gBAAgB,SAAS;AAE5B,MAAM,mBAAmB,KAAK,GAC5B,KAAK,4BAA4B,MAAM,YACxC,CAAC,CAAC,gBAAgB,SAAS;AAO5B,MAAMC,0BALa,KAAK;CACvB,OAAO;CACP,KAAK;CACL,CAAC,CAAC,gBAAgB,SAAS,CAEa,GAAG,YAAY;AAExD,MAAM,gBAAgB,KAAK;CAC1B,mBAAmBD;CACnB,mBAAmBA;CACnB,sBAAsBC;CACtB,gBAAgB;CAChB,uBAAuBA;CACvB,sBAAsBA;CACtB,kBAAkBD;CAClB,4BAA4B;CAC5B,0BAA0BC;CAC1B,kBAAkBD;CAClB,qBAAqBC;CACrB,sBAAsBA;CACtB,eAAe;CACf,qBAAqBD;CACrB,cAAcA;CACd,sBAAsBC;CACtB,CAAC,CAAC,gBAAgB,SAAS;AAE5B,MAAM,cAAc,KAAK;CACxB,WAAW;CACX,WAAW;CACX,CAAC,CAAC,gBAAgB,SAAS;AAM5B,MAAM,kBAAkB,KAAK;CAC5B,gBAAgB;CAChB,SAAS;CACT,SAAS;CACT,UAAU;EACT,eAAe;CAChB,CAAC,CAAC,gBAAgB,SAAS;AAE5B,MAAM,0BAA0B,KAAK,GACnC,KAAK,4BAA4B,MAAM,iBACxC,CAAC,CAAC,gBAAgB,SAAS;AAE5B,MAAM,0BAA0B,KAAK;CACpC,gBAAgB;CAChB,SAAS;CACT,6BAA6BD;CAC7B,SAAS;CACT,UAAU;EACT,eAAe;CAChB,qBAAqBA;CACrB,CAAC,CAAC,gBAAgB,SAAS;AAE5B,MAAM,4BAA4B,KAAK,GACrC,KAAK,4BAA4B,MAAM,yBACxC,CAAC,CAAC,gBAAgB,SAAS;AAE5B,MAAM,eAAe,KAAK;CACzB,gBAAgB;CAChB,gBAAgB;CAChB,aAAa;CACb,WAAW;EACV,eAAe;CAChB,eAAe;CACf,CAAC,CAAC,gBAAgB,SAAS;AAE5B,MAAM,0BAA0B,KAAK,GACnC,KAAK,4BAA4B,MAAM,cACxC,CAAC,CAAC,gBAAgB,SAAS;AAQ5B,MAAM,kBAAkB;AAExB,MAAM,mBAA2C,KAAK;CACrD,UAAU;CACV,WAAW;CACX,WAAW;CACX,aAAa;EACZ,eAAe;CAChB,UAAU;CACV,aAAa;CACb,CAAC,CAAC,gBAAgB,SAAS;AA4B5B,MAAM,aAAa,KAAK;CACvB,sBA3BwD,KAAK;EAC7D,YAAYA;EACZ,WAAW;EACX,CAAC,CAAC,gBAAgB,SAAS;CAyB3B,gBAvB8B,KAAK,GAClC,KAAK,wBAAwB,MAAM,kBACpC,CAAC,CACA,gBAAgB,SAAS,CACzB,QAAQ,OAAO,QAAQ;AACvB,MAAI,OAAO,KAAK,MAAM,CAAC,WAAW,EACjC,QAAO,IAAI,OAAO,gEAAgE;AAGnF,SAAO;GACN;CAcF,YAAY;CACZ,WAAW;CACX,WAAW;CACX,aAAa;CACb,UAAU;CACV,aAAa;CACb,CAAC,CACA,gBAAgB,SAAS,CACzB,QAAQ,OAAO,QAAQ;AAIvB,QAAO,wBAAwB,MAAM,CAAC,QAAiB,cAAc,UAAU;AAC9E,SAAO,IAAI,OAAO;GAAE,SAAS,MAAM;GAAS,MAAM,CAAC,GAAG,MAAM,KAAK;GAAE,CAAC;IAClE,KAAK;EACP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CH,SAAgB,eAAe,OAAgB,YAAiD;CAC/F,MAAM,YAAY,WAAW,MAAM;AACnC,KAAI,qBAAqB,UAQxB,QAAO;EACN,KAAK;GAAE,QARO,MAAM,KAAK,YAAY,UAAU;AAC/C,WAAO;KACN,SAAS,MAAM;KACf,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,KAAK,YAAY,OAAO,QAAQ,CAAC;KACvD;KACA;GAGc,MAAM;GAAoB;GAAY;EACrD,SAAS;EACT;AASF,QAAO;EAAE,MAAM;EAAgC,SAAS;EAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACxkC/D,SAAgB,2BAA2B,MAA8C;AACxF,QAAO,EACN,KAAK,OAA4B;AAChC,cAAY,OAAO,KAAK;IAEzB;;;;;;;;;;;;;;;;AAiBF,SAAgB,6BACf,QACe;CACf,MAAM,QAAQ,iBAAiB;AAC/B,QAAO,WAAW,KAAA,IACf,2BAA2B,EAAE,OAAO,CAAC,GACrC,2BAA2B;EAAE;EAAO;EAAQ,CAAC;;AAGjD,SAAS,iBAAiB,OAAiE;AAQ1F,QAAO,iBAPU,MAAM,aAAa,KAAM,QAAQ,EAAE,CAOrB,KANjB;EACb,GAAG,MAAM,QAAQ;EACjB,GAAG,MAAM,QAAQ;EACjB,GAAG,MAAM,KAAK;EACd,GAAG,MAAM,OAAO;EAChB,CACyC,KAAK,KAAK;;AAGrD,SAAS,iBAAiB,OAA4B;AACrD,KAAI,kBAAkB,MAAM,CAC3B,QAAO,QAAQ,MAAM;AAGtB,QAAO,MAAM;;AAGd,SAAS,iBACR,QACA,aACS;AACT,KAAI,WAAW,KAAA,EACd,QAAO;CAGR,MAAM,WAAW,mBAAmB,QAAQ,YAAY;AACxD,KAAI,CAAC,SAAS,QACb,QAAO;AAGR,QAAO,iBAAiB,SAAS,KAAK;;AAGvC,SAAS,kBAAkB,OAA2D;AACrF,SAAQ,MAAM,cAAd;EACC,KAAK,mBACJ,QAAO,MAAM,QAAQ;EAEtB,KAAK,WACJ,QAAO,MAAM,QAAQ;EAEtB,KAAK,QACJ;EAED,KAAK,WACJ,QAAO,MAAM,QAAQ;;;AAKxB,SAAS,0BACR,OACA,OACO;AACP,KAAI,MAAM,WAAW,UAAU;EAC9B,MAAM,KAAK,kBAAkB,MAAM;EACnC,MAAM,SAAS,OAAO,KAAA,IAAY,KAAK,QAAQ,GAAG;AAClD,QAAM,WAAW,GAAG,MAAM,aAAa,GAAG,MAAM,IAAI,UAAU,SAAS;AACvE;;AAGD,OAAM,WACL,GAAG,MAAM,aAAa,GAAG,MAAM,IAAI,GAAG,MAAM,cAAc,KAAK,KAAK,CAAC,UACrE;;AAGF,SAAS,mBAAmB,OAA2B;AACtD,SAAQ,MAAM,MAAd;EACC,KAAK,gBACJ,QAAO,WAAW,MAAM,MAAM;EAE/B,KAAK,kBACJ,QAAO;EAER,KAAK,oBACJ,QAAO;;;AAMV,SAAS,YAAY,OAAsB,MAAsC;CAChF,MAAM,EAAE,OAAO,WAAW;AAC1B,SAAQ,MAAM,MAAd;EACC,KAAK;AACJ,SAAM,WAAW,iBAAiB,MAAM,CAAC;AACzC;EAED,KAAK;AACJ,qBAAkB,MAAM,OAAO,MAAM;AACrC;EAED,KAAK;AACJ,SAAM,WAAW,GAAG,MAAM,YAAY,IAAI,MAAM,cAAc,uBAAuB;AACrF;EAED,KAAK;AACJ,SAAM,SAAS,GAAG,MAAM,aAAa,GAAG,MAAM,IAAI,GAAG,mBAAmB,MAAM,MAAM,GAAG;AACvF;EAED,KAAK;AACJ,SAAM,WAAW,GAAG,MAAM,aAAa,GAAG,MAAM,IAAI,YAAY;AAChE;EAED,KAAK,oBACJ;EAED,KAAK;AACJ,6BAA0B,OAAO,MAAM;AACvC;EAED,KAAK,eACJ,OAAM,WAAW,oBAAoB,iBAAiB,QAAQ,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;AC7KtF,SAAgB,kBAAkB,SAA8D;AAC/F,KAAI,QAAQ,UAAU,KAAA,EACrB,QAAO,EAAE,WAAW,OAAO;AAG5B,QAAO;EAAE,WAAW;EAAM,OAAO,QAAQ;EAAO;;;;;;;;;;;;;;ACdjD,SAAgB,kBACf,SACA,gBACgC;AAChC,KAAI,QAAQ,qBAAqB,KAAA,EAChC;AAGD,KAAI,QAAQ,qBAAqB,eAAe,iBAC/C;AAGD,QAAO,EAAE,kBAAkB,QAAQ,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC2GtD,SAAgB,6BACf,MACqC;CACrC,MAAM,YAAwC;EAC7C,GAAG;EACH,UAAU,iBAAiB,KAAK,SAAS;EACzC;AACD,QAAO;EACN,MAAM,OAAO,SAAS;AACrB,UAAO,UAAU,WAAW,QAAQ;;EAErC,MAAM,OAAO,SAAS,SAAS;AAC9B,UAAO,UAAU,WAAW;IAAE;IAAS;IAAS,CAAC;;EAElD;;AAGF,SAASE,iBACR,SACA,MACmE;CACnE,MAAM,mBACL,KAAK,qBAAqB,KAAA,IAAY,KAAA,IAAY,gBAAgB,KAAK,iBAAiB;AAEzF,QAAO;EACN,MAAM;GACL,GAAG;GACH,SAAS;IACR,WAAW,gBAAgB,KAAK,GAAG;IACnC,GAAI,qBAAqB,KAAA,IAAY,EAAE,GAAG,EAAE,kBAAkB;IAC9D;GACD;EACD,SAAS;EACT;;AAGF,eAAe,mBACd,MACA,EAAE,SAAS,WACiE;CAC5E,MAAM,WAAW,kBAAkB,SAAS,QAAQ;AACpD,KAAI,aAAa,KAAA,EAChB,QAAOA,iBAAe,SAAS,QAAQ;AAQxC,MALgB,MAAM,KAAK,OAAO,OAAO;EACxC,WAAW,gBAAgB,QAAQ,GAAG;EACtC,YAAY,KAAK;EACjB,GAAG;EACH,CAAC,EACU,QACX,QAAOA,iBAAe,SAAS,QAAQ;AAOxC,QAAOA,iBAAe;EAAE,GAAG;EAAS,kBAAkB,QAAQ;EAAkB,EAAE,QAAQ;;AAG3F,eAAe,UACd,MACA,SAC4E;CAC5E,MAAM,YACL,QAAQ,SAAS,KAAA,IAAY,KAAA,IAAY,MAAM,KAAK,SAAS,QAAQ,KAAK,SAAS;CACpF,MAAM,UAAU,MAAM,KAAK,OAAO,OAAO;EACxC,MAAM,QAAQ;EACd,aAAa,QAAQ;EACrB,YAAY,KAAK;EACjB,GAAI,cAAc,KAAA,IAAY,EAAE,GAAG,EAAE,WAAW;EAChD,GAAG,kBAAkB,QAAQ;EAC7B,GAAI,QAAQ,6BAA6B,KAAA,IACtC,EAAE,GACF,EAAE,0BAA0B,QAAQ,0BAA0B;EACjE,CAAC;AACF,KAAI,CAAC,QAAQ,QACZ,QAAO;AAGR,QAAO,mBAAmB,MAAM;EAAE,SAAS,QAAQ;EAAM;EAAS,CAAC;;AAGpE,eAAe,UACd,MACA,EAAE,SAAS,WACiE;CAC5E,MAAM,YACL,QAAQ,SAAS,KAAA,KACjB,mBAAmB,QAAQ,gBAAgB,QAAQ,eAAe,GAC/D,MAAM,KAAK,SAAS,QAAQ,KAAK,SAAS,GAC1C,KAAA;CACJ,MAAM,SAAS,MAAM,KAAK,OAAO,OAAO;EACvC,MAAM,QAAQ;EACd,aAAa,QAAQ;EACrB,WAAW,QAAQ,QAAQ;EAC3B,YAAY,KAAK;EACjB,GAAI,cAAc,KAAA,IAAY,EAAE,GAAG,EAAE,WAAW;EAChD,GAAG,kBAAkB,QAAQ;EAC7B,GAAI,QAAQ,6BAA6B,KAAA,IACtC,EAAE,GACF,EAAE,0BAA0B,QAAQ,0BAA0B;EACjE,GAAI,QAAQ,qBAAqB,KAAA,IAC9B,EAAE,GACF,EAAE,kBAAkB,QAAQ,kBAAkB;EACjD,CAAC;AACF,KAAI,CAAC,OAAO,QACX,QAAO;AAKR,QAAO;EAAE,MAAM;GAAE,GAAG;GAAS,SAAS,QAAQ;GAAS;EAAE,SAAS;EAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC/HzE,SAAgB,qBAAqB,MAAsD;CAC1F,MAAM,YAAgC;EACrC,GAAG;EACH,UAAU,iBAAiB,KAAK,SAAS;EACzC;AACD,QAAO;EACN,MAAM,OAAO,SAAS;AACrB,UAAO,eAAe,WAAW,QAAQ;;EAE1C,MAAM,OAAO,SAAS,SAAS;AAC9B,UAAO,eAAe,WAAW;IAAE;IAAS;IAAS,CAAC;;EAEvD;;AAGF,SAASC,iBACR,SACA,MAC2D;CAC3D,MAAM,EAAE,IAAI,gBAAgB;AAC5B,KAAI,gBAAgB,KAAA,EACnB,QAAO;EACN,KAAK,IAAI,SACR,uEACA,EAAE,YAAY,KAAK,CACnB;EACD,SAAS;EACT;AAGF,QAAO;EACN,MAAM;GACL,GAAG;GACH,SAAS;IACR,SAAS,gBAAgB,GAAG;IAC5B,cAAc,EAAE,SAAS,gBAAgB,YAAY,EAAE;IACvD;GACD;EACD,SAAS;EACT;;AAGF,eAAe,eACd,MACA,SACoE;CACpE,MAAM,YAAY,MAAM,KAAK,SAAS,QAAQ,KAAK,SAAS;CAC5D,MAAM,SAAS,MAAM,KAAK,OAAO,OAAO;EACvC,MAAM,QAAQ;EACd,aAAa,QAAQ;EACrB;EACA,YAAY,KAAK;EACjB,GAAI,QAAQ,UAAU,KAAA,IAAY,EAAE,OAAO,QAAQ,OAAO,GAAG,EAAE;EAC/D,CAAC;AACF,KAAI,CAAC,OAAO,QACX,QAAO;AAGR,QAAOA,iBAAe,SAAS,OAAO,KAAK;;AAG5C,eAAe,oBACd,MACA,SAKoE;CACpE,MAAM,EAAE,SAAS,SAAS,mBAAmB;AAC7C,KAAI,CAAC,eACJ,QAAO;EAAE,MAAM;GAAE,GAAG;GAAS,SAAS,QAAQ;GAAS;EAAE,SAAS;EAAM;CAGzE,MAAM,UAAU,MAAM,KAAK,OAAO,IAAI;EACrC,YAAY,QAAQ,QAAQ;EAC5B,YAAY,KAAK;EACjB,CAAC;AACF,KAAI,CAAC,QAAQ,QACZ,QAAO;AAGR,QAAOA,iBAAe,SAAS,QAAQ,KAAK;;AAG7C,eAAe,eACd,MACA,QAIoE;CACpE,MAAM,EAAE,SAAS,YAAY;CAC7B,MAAM,iBAAiB,mBAAmB,QAAQ,gBAAgB,QAAQ,eAAe;CACzF,MAAM,YAAY,iBAAiB,MAAM,KAAK,SAAS,QAAQ,KAAK,SAAS,GAAG,KAAA;CAEhF,MAAM,SAAS,MAAM,KAAK,OAAO,OAAO;EACvC,MAAM,QAAQ;EACd,aAAa,QAAQ;EACrB,YAAY,QAAQ,QAAQ;EAC5B,YAAY,KAAK;EACjB,GAAG,kBAAkB,QAAQ;EAC7B,GAAI,cAAc,KAAA,IAAY,EAAE,WAAW,GAAG,EAAE;EAChD,CAAC;AACF,KAAI,CAAC,OAAO,QACX,QAAO;AAGR,QAAO,oBAAoB,MAAM;EAAE;EAAS;EAAS;EAAgB,CAAC;;;;AC3NvE,MAAM,iBAAiB,KAAK;CAC3B,UAAU,EAAE,SAAS,KAAK;CAC1B,aAAa;CACb,WAVqB,KAAK;EAC1B,OAAO;EACP,YAAY;EACZ,QAAQ;EACR,WAAW;EACX,CAAC,CAKwB,OAAO;CAChC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCF,SAAgB,mBAAmB,OAA6B;CAC/D,MAAM,WAAW;EAChB,UAAU,EAAE,SAAS,MAAM,SAAS;EACpC,aAAa,MAAM;EACnB,WAAW,MAAM;EACjB;AACD,QAAO,KAAK,UAAU,UAAU,KAAA,GAAW,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6B9C,SAAgB,eACf,KACA,MAC+C;AAC/C,KAAI,QAAQ,KAAA,EACX,QAAO;EAAE,MAAM,KAAA;EAAW,SAAS;EAAM;CAG1C,MAAM,SAAS,UAAU,KAAK,KAAK;AACnC,KAAI,CAAC,OAAO,QACX,QAAO;CAGR,MAAM,YAAY,eAAe,OAAO,KAAK;AAC7C,KAAI,qBAAqB,UACxB,QAAO,SAAS,MAAM,uBAAuB,UAAU,UAAU;CAGlE,MAAM,YAAY,UAAU;AAC5B,QAAO;EACN,MAAM;GAAE,aAAa,UAAU;GAAa;GAAW,SAAS;GAAG;EACnE,SAAS;EACT;;AAGF,SAAS,UAAU,KAAa,MAA6C;AAC5E,KAAI;AACH,SAAO;GAAE,MAAM,KAAK,MAAM,IAAI;GAAE,SAAS;GAAM;UACvC,KAAK;AAEb,SAAO;GACN,KAAK;IAAE;IAAM,MAAM;IAAc,QAAQ,mBAF1B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IAEQ;GACvE,SAAS;GACT;;;AAIH,SAAS,SAAS,MAAc,QAA8D;AAC7F,QAAO;EACN,KAAK;GAAE;GAAM,MAAM;GAAc;GAAQ;EACzC,SAAS;EACT;;;;AC7HF,MAAM,kBAAkB;AACxB,MAAM,qBAAqB;AAC3B,MAAM,aAAa;AACnB,MAAM,mBAAmB;AACzB,MAAM,cAAc;AACpB,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;AACvB,MAAM,qBAA0C,IAAI,IAAI;CAAC;CAAK;CAAK;CAAK;CAAI,CAAC;AAC7E,MAAM,0BAA0B;AAChC,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmGjC,SAAgB,uBAAuB,MAAuC;CAC7E,MAAM,MAAsB;EAC3B,SAAS,KAAK,SAAS,WAAW,MAAM,KAAK,WAAW;EACxD,QAAQ,KAAK;EACb,QAAQ,KAAK,UAAU,KAAK;EAC5B,OAAO,KAAK,SAAS;EACrB,OAAO,KAAK;EACZ;AAED,QAAO;EACN,MAAM,KAAK,aAAa;GACvB,MAAM,OAAO,wBAAwB,YAAY;AACjD,OAAI,CAAC,KAAK,QACT,QAAO;AAGR,UAAO,SAAS,KAAK,KAAK,KAAK;;EAEhC,MAAM,MAAM,OAAO;GAClB,MAAM,OAAO,wBAAwB,MAAM,YAAY;AACvD,OAAI,CAAC,KAAK,QACT,QAAO;AAGR,UAAO,UAAU,KAAK,MAAM;;EAE7B;;AAGF,eAAe,aAAa,IAA2B;AACtD,OAAM,IAAI,SAAe,YAAY;AACpC,aAAW,SAAS,GAAG;GACtB;;AAGH,SAAS,UAAU,QAAgB,aAA6B;AAC/D,QAAO,QAAQ,OAAO,SAAS,YAAY;;AAG5C,SAAS,SAAS,aAA6B;AAC9C,QAAO,SAAS,YAAY;;AAG7B,SAAS,WAAW,OAAsC;AACzD,KAAI,OAAO,UAAU,YAAY,UAAU,KAC1C;CAGD,MAAM,SAAS;CACf,MAAM,UAAU,OAAO,OAAO,eAAe,WAAW,OAAO,aAAa,KAAA;CAC5E,MAAM,SAAS,OAAO,OAAO,eAAe,WAAW,OAAO,aAAa,KAAA;CAC3E,MAAM,OAAO,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU;AAEnE,QAAO;EAAE;EAAS,aADE,OAAO,iBAAiB;EACb;EAAQ;EAAM;;AAG9C,SAAS,cAAc,SAA2B;AACjD,QAAO,QAAQ,IAAI,cAAc,KAAK,QAAQ,QAAQ,IAAI,wBAAwB,KAAK;;AAGxF,SAAS,gBAAgB,QAAgB,SAA0B;CAClE,MAAM,aAAa,QAAQ,IAAI,cAAc;AAC7C,KAAI,eAAe,KAClB,QAAO,iBAAiB,OAAO,iBAAiB,WAAW;AAG5D,QAAO,iBAAiB,OAAO;;AAGhC,SAAS,aAAa,EAAE,MAAM,QAAQ,YAAqC;CAC1E,MAAM,EAAE,SAAS,WAAW;AAC5B,KAAI,WAAW,IACd,QAAO;EAAE;EAAM,MAAM;EAAc,QAAQ,QAAQ,OAAO;EAA2B;AAGtF,KAAI,WAAW,OAAO,cAAc,QAAQ,CAC3C,QAAO;EAAE;EAAM,MAAM;EAAc,QAAQ,gBAAgB,QAAQ,QAAQ;EAAE;AAG9E,KAAI,WAAW,OAAO,WAAW,IAChC,QAAO;EAAE;EAAM,MAAM;EAAc,QAAQ,gBAAgB,OAAO;EAAwB;AAG3F,QAAO;EAAE;EAAM,MAAM;EAAc,QAAQ,mBAAmB;EAAU;;AAGzE,SAAS,aAAa,OAAgB,MAA0B;AAE/D,QAAO;EAAE;EAAM,MAAM;EAAc,QAAQ,kBAD3B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;EACE;;AAGzE,SAAS,aAAa,OAAwB;CAC7C,MAAM,UAAU,IAAI,SAAS;AAC7B,SAAQ,IAAI,UAAU,8BAA8B;AACpD,SAAQ,IAAI,iBAAiB,UAAU,QAAQ;AAC/C,SAAQ,IAAI,cAAc,WAAW;AACrC,SAAQ,IAAI,wBAAwB,mBAAmB;AACvD,QAAO;;AAGR,eAAe,QAAQ,KAAwC;AAC9D,QAAO,IAAI,QAAQ,GAAG,gBAAgB,SAAS,IAAI,UAAU;EAC5D,SAAS,aAAa,IAAI,MAAM;EAChC,QAAQ;EACR,CAAC;;AAGH,SAAS,kBAAkB,QAAyB;AACnD,QAAO,mBAAmB,IAAI,OAAO;;AAGtC,SAAS,UAAU,SAAiB,QAA8B;CAEjE,MAAM,OADM,KAAK,IAAI,gBAAgB,kBAAkB,KAAK,QAAQ,GACjD;AACnB,QAAO,OAAO,QAAQ,GAAG;;AAG1B,eAAe,UAAU,OAAkB,WAAuD;CACjG,IAAI,WAAW,MAAM,WAAW;AAChC,MAAK,IAAI,UAAU,GAAG,UAAU,aAAa,WAAW,GAAG;AAC1D,MAAI,SAAS,MAAM,CAAC,kBAAkB,SAAS,OAAO,CACrD,QAAO;AAGR,QAAM,MAAM,MAAM,UAAU,SAAS,MAAM,OAAO,CAAC;AACnD,aAAW,MAAM,WAAW;;AAG7B,QAAO;;AAGR,eAAe,cACd,KACA,MACuD;CACvD,IAAI;AACJ,KAAI;AACH,aAAW,MAAM,UAAU,KAAK,YAAY,QAAQ,IAAI,CAAC;UACjD,KAAK;AACb,SAAO;GAAE,KAAK,aAAa,KAAK,KAAK;GAAE,SAAS;GAAO;;AAGxD,KAAI,CAAC,SAAS,GACb,QAAO;EACN,KAAK,aAAa;GAAE;GAAM,QAAQ,IAAI;GAAQ;GAAU,CAAC;EACzD,SAAS;EACT;AAIF,QAAO;EAAE,MADK,MAAM,SAAS,MAAM;EACd,SAAS;EAAM;;AAGrC,SAAS,SAAY,MAAc,QAAuC;AACzE,QAAO;EAAE,KAAK;GAAE;GAAM,MAAM;GAAc;GAAQ;EAAE,SAAS;EAAO;;AAGrE,eAAe,gBAAgB,EAC9B,OACA,SACA,MACA,SACgF;AAChF,KAAI,MAAM,OAAO,iBAChB,QAAO,SAAS,MAAM,yBAAyB,MAAM,KAAK,QAAQ;AAGnE,KAAI,MAAM,aAAa;AACtB,MAAI,MAAM,WAAW,KAAA,EACpB,QAAO,SAAS,MAAM,sCAAsC;EAG7D,MAAM,EAAE,WAAW;EACnB,IAAI;AACJ,MAAI;AACH,iBAAc,MAAM,UAAU,OAAO,YAAY,QAAQ,OAAO,CAAC;WACzD,KAAK;AACb,UAAO;IAAE,KAAK,aAAa,KAAK,KAAK;IAAE,SAAS;IAAO;;AAGxD,MAAI,CAAC,YAAY,GAChB,QAAO,SAAS,MAAM,0BAA0B,YAAY,SAAS;AAItE,SAAO,eADK,MAAM,YAAY,MAAM,EACT,KAAK;;AAGjC,QAAO,eAAe,MAAM,SAAS,KAAK;;AAG3C,eAAe,SACd,KACA,aACwD;CACxD,MAAM,OAAO,UAAU,IAAI,QAAQ,YAAY;CAC/C,MAAM,OAAO,MAAM,cAAc,KAAK,KAAK;AAC3C,KAAI,CAAC,KAAK,QACT,QAAO;CAGR,MAAM,QAAQ,KAAK,KAAK;CACxB,MAAM,QAAQ,WAAW,QAAQ,SAAS,YAAY,EAAE;AACxD,KAAI,UAAU,KAAA,EACb,QAAO;EAAE,MAAM,KAAA;EAAW,SAAS;EAAM;AAG1C,QAAO,gBAAgB;EAAE;EAAO,SAAS,IAAI;EAAS;EAAM,OAAO;EAAK,CAAC;;AAG1E,eAAe,UAAU,KAAqB,MAAiC;CAC9E,MAAM,UAAU,aAAa,IAAI,MAAM;AACvC,SAAQ,IAAI,gBAAgB,mBAAmB;AAC/C,QAAO,IAAI,QAAQ,GAAG,gBAAgB,SAAS,IAAI,UAAU;EAC5D;EACA;EACA,QAAQ;EACR,CAAC;;AAGH,eAAe,cAAc,KAAqB,QAAkC;AACnF,KAAI;EACH,MAAM,WAAW,MAAM,QAAQ,IAAI;EACnC,MAAM,OAAO,KAAK,MAAM,MAAM,SAAS,MAAM,CAAC;EAC9C,MAAM,QAAQ,QAAQ,IAAI,MAAM,QAAQ;AACxC,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,UAAU;SACzD;AACP,SAAO;;;;;;;;;;;;;;;;;;AAmBT,eAAe,sBAAsB,KAAqB,aAAoC;CAC7F,MAAM,SAAS,SAAS,YAAY;AAEpC,MAAK,IAAI,UAAU,GAAG,UAAU,yBAAyB,WAAW,GAAG;AACtE,MAAI,MAAM,cAAc,KAAK,OAAO,CACnC;AAGD,MAAI,UAAU,0BAA0B,EACvC,OAAM,IAAI,MAAM,2BAA2B,KAAK,QAAQ;;;AAK3D,eAAe,UACd,KACA,OACoC;CACpC,MAAM,OAAO,UAAU,IAAI,QAAQ,MAAM,YAAY;CACrD,MAAM,OAAO,KAAK,UAAU,EAC3B,OAAO,GAAG,SAAS,MAAM,YAAY,GAAG,EAAE,SAAS,mBAAmB,MAAM,EAAE,EAAE,EAChF,CAAC;CAEF,IAAI;AACJ,KAAI;AACH,aAAW,MAAM,UAAU,KAAK,YAAY,UAAU,KAAK,KAAK,CAAC;UACzD,KAAK;AACb,SAAO;GAAE,KAAK,aAAa,KAAK,KAAK;GAAE,SAAS;GAAO;;AAGxD,KAAI,SAAS,IAAI;AAChB,MAAI;AACH,SAAM,sBAAsB,KAAK,MAAM,YAAY;UAC5C;AAIR,SAAO;GAAE,MAAM,KAAA;GAAW,SAAS;GAAM;;AAG1C,KAAI,SAAS,WAAW,IACvB,QAAO,SAAS,MAAM,oCAAoC;AAG3D,QAAO;EACN,KAAK,aAAa;GAAE;GAAM,QAAQ,IAAI;GAAQ;GAAU,CAAC;EACzD,SAAS;EACT;;;;;;;;;;;;;;;;;;;;;;;ACnYF,SAAgB,4BAA0C;AACzD,QAAO,EACN,OAAO,IAGP;;;;;;;;;;AC8GF,MAAa,gCAAgC;CAC5C;CACA;CACA;CACA;;;;;;;;AAiHD,MAAa,yBAAyB;CACrC;CACA;CACA;CACA;CACA;CACA;CACA;;;;;;AAUD,MAAa,qBAAqB;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;;;;;;;;;;;;AAmPD,SAAgB,wBACf,QAC2D;CAC3D,MAAM,SAAmE,EAAE;AAC3E,MAAK,MAAM,SAAS,mBACnB,KAAI,SAAS,OACZ,QAAO,SAAS,OAAO;AAIzB,QAAO;;;;;;;;;;;;;;;AAgBR,MAAa,yBAAsC,cAAc,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC3axE,SAAgB,kBAAkB,MAAgD;AACjF,QAAO;EACN,MAAM,OAAO,SAAS;AACrB,UAAO,aAAa,MAAM,QAAQ;;EAEnC,MAAM,OAAO,UAAU,SAAS;AAC/B,UAAO,aAAa,MAAM,QAAQ;;EAEnC;;AAGF,SAAS,wBACR,YACA,SACoC;CACpC,MAAM,WAAW,8BAA8B,QAC7C,aAAa,UAAU;EACvB,MAAM,QAAQ,QAAQ;AACtB,SAAO,UAAU,KAAA,IAAY,cAAc;GAAE,GAAG;IAAc,QAAQ;GAAO;IAE9E,EAAE,CACF;AAED,KAAI,OAAO,KAAK,SAAS,CAAC,WAAW,EACpC;AAGD,QAAO;EAAE,GAAG;EAAU,SAAS,QAAQ;EAAS;EAAY;;AAG7D,SAAS,aAAa,UAAgD;AACrE,KAAI,SAAS,SAAS,SAAS,CAC9B,QAAO;AAGR,KAAI,SAAS,SAAS,QAAQ,CAC7B,QAAO;;AAMT,eAAe,eACd,MACA,SACgD;CAChD,MAAM,SAAS,aAAa,QAAQ,SAAS;AAC7C,KAAI,WAAW,KAAA,EACd,QAAO;EACN,KAAK,IAAI,SACR,wCAAwC,QAAQ,SAAS,6BACzD,EAAE,YAAY,GAAG,CACjB;EACD,SAAS;EACT;CAGF,MAAM,OAAO,MAAM,KAAK,SAAS,QAAQ,SAAS;AAClD,QAAO,KAAK,OAAO,QAAQ;EAG1B,MAAM,WAAW,KAAK,KAAK;EAC3B;EACA,SAAS,QAAQ;EACjB,YAAY,KAAK;EACjB,CAAC;;AAGH,eAAe,aACd,MACA,SACiE;CACjE,MAAM,gBAAgB,MAAM,eAAe,MAAM,QAAQ;AACzD,KAAI,CAAC,cAAc,QAClB,QAAO;CAGR,MAAM,qBAAqB,wBAAwB,KAAK,YAAY,QAAQ;AAC5E,KAAI,uBAAuB,KAAA,GAAW;EACrC,MAAM,iBAAiB,MAAM,KAAK,OAAO,OAAO,mBAAmB;AACnE,MAAI,CAAC,eAAe,QACnB,QAAO;;AAIT,QAAO;EAAE,MAAM;GAAE,GAAG;GAAS,SAAS,cAAc;GAAM;EAAE,SAAS;EAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC9E5E,SAAgB,qBAAqB,MAAsD;AAC1F,QAAO;EACN,MAAM,OAAO,SAAS;AACrB,UAAO,kBAAkB;IAAE;IAAM;IAAS,CAAC;;EAE5C,MAAM,OAAO,UAAU,SAAS;AAC/B,UAAO,kBAAkB;IAAE;IAAM;IAAS,CAAC;;EAE5C;;AAGF,SAAS,eACR,SACA,aACmC;AACnC,QAAO;EACN,GAAG;EACH,SAAS,EAAE,aAAa,gBAAgB,YAAY,EAAE;EACtD;;AAGF,SAAS,gBAAgB,SAAyD;CACjF,MAAM,OAAO,uBAAuB,QAClC,aAAa,SAAS;EACtB,MAAM,YAAY,QAAQ;AAC1B,SAAO,cAAc,KAAA,IAAY,cAAc;GAAE,GAAG;IAAc,OAAO;GAAW;IAErF,EAAE,YAAY,QAAQ,YAAY,CAClC;AAOD,QAAO;EAAE,GAJR,6BAA6B,UAC1B;GAAE,GAAG;GAAM,yBAAyB,QAAQ;GAAyB,GACrE;EAEmB,GAAG,wBAAwB,QAAQ;EAAE;;AAG7D,SAAS,gBAAgB,KAAqB,SAA+C;AAC5F,KAAI,eAAe,YAAY,IAAI,eAAe,IACjD,QAAO,IAAI,SACV,YAAY,QAAQ,WAAW,SAAS,QAAQ,IAAI,oCACpD,EAAE,YAAY,KAAK,CACnB;AAGF,QAAO;;AAGR,SAAS,uBAAuB,SAAwC;AACvE,KAAI,uBAAuB,MAAM,SAAS,QAAQ,UAAU,KAAA,EAAU,CACrE,QAAO;AAGR,KAAI,6BAA6B,QAChC,QAAO;AAGR,QAAO,mBAAmB,MAAM,UAAU,SAAS,QAAQ;;AAG5D,eAAe,gBACd,MACA,SACoD;CACpD,MAAM,SAAS,uBAAuB,QAAQ,GAC3C,MAAM,KAAK,UAAU,OAAO,gBAAgB,QAAQ,CAAC,GACrD,MAAM,KAAK,UAAU,IAAI,EAAE,YAAY,QAAQ,YAAY,CAAC;AAE/D,KAAI,CAAC,OAAO,QACX,QAAO;EAAE,KAAK,gBAAgB,OAAO,KAAK,QAAQ;EAAE,SAAS;EAAO;CAGrE,MAAM,EAAE,gBAAgB,OAAO;AAC/B,KAAI,gBAAgB,KAAA,EACnB,QAAO;EACN,KAAK,IAAI,SACR,mCAAmC,QAAQ,WAAW,wBACtD,EAAE,YAAY,KAAK,CACnB;EACD,SAAS;EACT;AAGF,QAAO;EAAE,MAAM,EAAE,aAAa;EAAE,SAAS;EAAM;;AAGhD,eAAe,kBACd,QACoE;CACpE,MAAM,EAAE,MAAM,YAAY;CAC1B,MAAM,iBAAiB,MAAM,gBAAgB,MAAM,QAAQ;AAC3D,KAAI,CAAC,eAAe,QACnB,QAAO;CAGR,MAAM,EAAE,gBAAgB,eAAe;AACvC,KAAI,QAAQ,gBAAgB,KAAA,GAAW;EACtC,MAAM,eAAe,MAAM,KAAK,OAAO,OAAO;GAC7C,aAAa,QAAQ;GACrB,SAAS;GACT,YAAY,QAAQ;GACpB,CAAC;AACF,MAAI,CAAC,aAAa,QACjB,QAAO;GAAE,KAAK,aAAa;GAAK,SAAS;GAAO;;AAIlD,QAAO;EAAE,MAAM,eAAe,SAAS,YAAY;EAAE,SAAS;EAAM;;;;;;;;;;;;;;;;;;ACtNrE,SAAgB,mBACf,MACA,QACmC;AACnC,KAAI,SAAS,KAAA,EACZ,QAAO;EAAE,MAAM;EAAM,SAAS;EAAM;AAMrC,QAAO;EAAE,KAAK;GAAE,uBAHqB,IAAI,MACxC,wCAAwC,UAAU,YAClD;GACsB,MAAM;GAAgB;EAAE,SAAS;EAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyChE,SAAgB,uBAAgC;AAC/C,QAAO,EAAE,OAAO,sBAAsB;;AAGvC,eAAe,qBAAqB,YAA2D;AAC9F,QAAO,IAAI,SAAS,YAAY;EAC/B,MAAM,QAAQ,MAAM,WAAW,SAAS,CAAC,GAAG,WAAW,KAAK,EAAE;GAC7D,KAAK;IAAE,GAAG,QAAQ;IAAK,GAAG,WAAW;IAAc;GACnD,OAAO;GACP,CAAC;AAEF,QAAM,KAAK,UAAU,UAAiC;AACrD,WAAQ;IAAE,KAAK;KAAE,OAAO;KAAO,MAAM;KAAgB;IAAE,SAAS;IAAO,CAAC;IACvE;AAEF,QAAM,KAAK,UAAU,MAAM,WAAW;AACrC,WAAQ,mBAAmB,QAAQ,KAAA,GAAW,UAAU,KAAA,EAAU,CAAC;IAClE;GACD;;;;;;;;;;AC7EH,SAAgB,yBAAyB,OAA0D;CAClG,MAAM,YAAoC,EAAE;AAC5C,KAAI,MAAM,WAAW,KAAA,EACpB,WAAU,qBAAqB,MAAM;AAGtC,KAAI,MAAM,gBAAgB,KAAA,EACzB,WAAU,0BAA0B,MAAM;AAG3C,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACyDR,eAAsB,iBACrB,YACA,SAC4C;CAC5C,MAAM,OAAO;EAAC,WAAW;EAAc;EAAS,WAAW;EAAY;AACvE,KAAI,WAAW,eAAe,KAAA,EAC7B,MAAK,KAAK,YAAY,WAAW,WAAW;CAG7C,MAAM,sBAAsB,yBAAyB,WAAW;CAEhE,MAAM,WAAW,MAAM,QAAQ,MAAM;EACpC;EACA,SAAS;EACT,cAAc;GAAE,GAAG;GAAqB,aAAa;GAAK;EAC1D,CAAC;AACF,KAAI,CAAC,SAAS,QACb,QAAO;EAAE,KAAK;GAAE,OAAO,SAAS,IAAI;GAAO,MAAM;GAAgB;EAAE,SAAS;EAAO;CAGpF,MAAM,WAAW,SAAS;AAC1B,KAAI,aAAa,EAChB,QAAO;EAAE,KAAK;GAAE;GAAU,MAAM;GAAe;EAAE,SAAS;EAAO;AAGlE,QAAO;EAAE,MAAM,KAAA;EAAW,SAAS;EAAM;;;;AC/F1C,MAAMC,qBAAmB;AAEzB,MAAMC,gBAAc,KAAK;CACxB,QAAQ;CACR,eAAe;CACf,SAAS;CACT,6BAA6BD;CAC7B,UAAU;CACV,aAAa;CACb,qBAAqBA;CACrB,CAAC;AAEF,SAASE,UAAQ,QAAqE;AACrF,QAAO,OAAO,QAAQ,OAAO,YAAY,EAAE,CAAC,CAAC,KAC3C,CAAC,KAAK,WAAW;EACjB,MAAM,OAAqC;GAC1C,KAAK,cAAc,IAAI;GACvB,MAAM,MAAM;GACZ,aAAa,MAAM;GACnB,0BAA0B,MAAM;GAChC,MAAM;GACN,OAAO,MAAM;GACb,kBAAkB,MAAM;GACxB;AACD,SAAO,MAAM,SAAS,KAAA,IAAY,OAAO;GAAE,GAAG;GAAM,MAAM,MAAM;GAAM;GAEvE;;AAGF,eAAeC,YACd,OACA,IACmE;CACnE,MAAM,OAAqC;EAC1C,KAAK,MAAM;EACX,MAAM,MAAM;EACZ,aAAa,MAAM;EACnB,0BAA0B,MAAM;EAChC,MAAM;EACN,OAAO,MAAM;EACb,kBAAkB,MAAM;EACxB;AAED,KAAI,MAAM,SAAS,KAAA,EAClB,QAAO;EAAE,MAAM;EAAM,SAAS;EAAM;CAGrC,MAAM,SAAS,MAAM,gBAAgB;EAAE,KAAK,MAAM;EAAK,MAAM,MAAM;EAAM,EAAE,GAAG;AAC9E,KAAI,CAAC,OAAO,QACX,QAAO;AAGR,QAAO;EACN,MAAM;GAAE,GAAG;GAAM,MAAM,MAAM;GAAM,gBAAgB,OAAO;GAAM;EAChE,SAAS;EACT;;AAGF,SAASC,uBACR,SACA,SACwB;AACxB,QAAO;EACN,GAAI,QAAQ,gBAAgB,QAAQ,cAAc,EAAE,GAAG,CAAC,cAAc;EACtE,GAAI,QAAQ,OAAO,aAAa,QAAQ,OAAO,WAAW,EAAE,GAAG,CAAC,OAAO;EACvE,GAAI,gBAAgB,QAAQ,gBAAgB,QAAQ,eAAe,GAChE,EAAE,GACF,CAAC,iBAAiB;EACrB,GAAI,QAAQ,SAAS,QAAQ,OAAO,EAAE,GAAG,CAAC,OAAO;EACjD,GAAI,QAAQ,UAAU,QAAQ,QAAQ,EAAE,GAAG,CAAC,QAAQ;EACpD,GAAI,QAAQ,6BAA6B,KAAA,KACzC,QAAQ,6BAA6B,QAAQ,2BAC1C,EAAE,GACF,CAAC,2BAA2B;EAC/B,GAAI,QAAQ,qBAAqB,KAAA,KACjC,QAAQ,qBAAqB,QAAQ,mBAClC,EAAE,GACF,CAAC,mBAAmB;EACvB;;AAGF,SAASC,cACR,SACA,SACU;AACV,QAAOD,uBAAqB,SAAS,QAAQ,CAAC,WAAW;;AAG1D,SAAS,mBACR,SACA,SACuC;AACvC,KAAI,QAAQ,mBAAmB,KAAA,KAAa,QAAQ,mBAAmB,KAAA,EACtE,QAAO;EACN,KAAK;GACJ,KAAK,QAAQ;GACb,MAAM;GACN,SAAS,sBAAsB,QAAQ,IAAI;GAC3C;EACD,SAAS;EACT;AAGF,QAAO;EAAE,MAAM,KAAA;EAAW,SAAS;EAAM;;;;;;;AAQ1C,MAAa,uBAA+D;CAC3E;CACA,sBAAA;CACA,aAAA;CACA,aAAA;CACA,SAAA;CACA,MAAM;CACN,WAAA;CACA;;;ACvHD,MAAME,gBAAc,KAAK;CACxB,QAAQ;CACR,eAAe;CACf,QAAQ;CACR,UAAU;CACV,aAAa;CACb,CAAC;AAEF,SAASC,UAAQ,QAA6D;AAC7E,QAAO,OAAO,QAAQ,OAAO,UAAU,EAAE,CAAC,CAAC,KAA2B,CAAC,KAAK,WAAW;AACtF,SAAO;GACN,KAAK,cAAc,IAAI;GACvB,MAAM,MAAM;GACZ,aAAa,MAAM;GACnB,MAAM,MAAM;GACZ,MAAM;GACN,OAAO,MAAM;GACb;GACA;;AAGH,eAAeC,YACd,OACA,IAC2D;CAC3D,MAAM,SAAS,MAAM,gBAAgB;EAAE,KAAK,MAAM;EAAK,MAAM,MAAM;EAAM,EAAE,GAAG;AAC9E,KAAI,CAAC,OAAO,QACX,QAAO;AAGR,QAAO;EACN,MAAM;GACL,KAAK,MAAM;GACX,MAAM,MAAM;GACZ,aAAa,MAAM;GACnB,MAAM,MAAM;GACZ,gBAAgB,OAAO;GACvB,MAAM;GACN,OAAO,MAAM;GACb;EACD,SAAS;EACT;;AAGF,SAASC,uBACR,SACA,SACwB;AACxB,QAAO;EACN,GAAI,QAAQ,gBAAgB,QAAQ,cAAc,EAAE,GAAG,CAAC,cAAc;EACtE,GAAI,QAAQ,KAAK,aAAa,QAAQ,KAAK,WAAW,EAAE,GAAG,CAAC,OAAO;EACnE,GAAI,gBAAgB,QAAQ,gBAAgB,QAAQ,eAAe,GAChE,EAAE,GACF,CAAC,iBAAiB;EACrB,GAAI,QAAQ,SAAS,QAAQ,OAAO,EAAE,GAAG,CAAC,OAAO;EACjD,GAAI,QAAQ,UAAU,QAAQ,QAAQ,EAAE,GAAG,CAAC,QAAQ;EACpD;;AAGF,SAASC,cACR,SACA,SACU;AACV,QAAOD,uBAAqB,SAAS,QAAQ,CAAC,WAAW;;;;;;;AAQ1D,MAAa,eAA+C;CAC3D,sBAAA;CACA,aAAA;CACA,aAAA;CACA,SAAA;CACA,MAAM;CACN,WAAA;CACA;;;AC3ED,MAAME,gBAAc,KAAK;CACxB,gBAAgB;CAChB,gBAAgB;CAChB,YAAY;CACZ,WAAW;CACX,eAAe;CACf,CAAC,CAAC,gBAAgB,SAAS;AAE5B,SAASC,UAAQ,QAA0D;AAC1E,QAAO,OAAO,QAAQ,OAAO,UAAU,EAAE,CAAC,CAAC,KAAwB,CAAC,KAAK,WAAW;AACnF,SAAO;GACN,KAAK,cAAc,IAAI;GACvB,aAAa,MAAM;GACnB,aAAa,MAAM;GACnB,UAAU,MAAM;GAChB,MAAM;GACN,SAAS,gBAAgB,MAAM,QAAQ;GACvC,YAAY,MAAM;GAClB;GACA;;AAGH,eAAeC,YACd,OACA,IACwD;CACxD,MAAM,OAAO,MAAM,UAAU;EAAE,KAAK,MAAM;EAAK,UAAU,MAAM;EAAU,EAAE,GAAG;AAC9E,KAAI,CAAC,KAAK,QACT,QAAO;AAGR,QAAO;EACN,MAAM;GACL,KAAK,MAAM;GACX,aAAa,MAAM;GACnB,aAAa,MAAM;GACnB,UAAU,YAAY,MAAM,UAAU,KAAK,KAAK,CAAC;GACjD,UAAU,MAAM;GAChB,MAAM;GACN,SAAS,MAAM;GACf,YAAY,MAAM;GAClB;EACD,SAAS;EACT;;AAGF,SAASC,uBACR,SACA,SACwB;AACxB,QAAO;EACN,GAAI,QAAQ,aAAa,QAAQ,WAAW,EAAE,GAAG,CAAC,WAAW;EAC7D,GAAI,QAAQ,aAAa,QAAQ,WAAW,EAAE,GAAG,CAAC,WAAW;EAC7D,GAAI,QAAQ,YAAY,QAAQ,UAAU,EAAE,GAAG,CAAC,UAAU;EAC1D,GAAG,8BAA8B,QAAQ,UAAU;GAClD,MAAM,eAAe,QAAQ;AAC7B,UAAO,iBAAiB,KAAA,KAAa,iBAAiB,QAAQ;IAC7D;EACF;;AAGF,SAASC,cAAY,SAA4B,SAAiD;AACjG,QAAOD,uBAAqB,SAAS,QAAQ,CAAC,WAAW;;;;;;;AAQ1D,MAAa,YAAyC;CACrD,sBAAA;CACA,aAAA;CACA,aAAA;CACA,SAAA;CACA,MAAM;CACN,WAAA;CACA;;;ACzED,MAAM,mBAAmB;AAOzB,MAAM,wBALa,KAAK;CACvB,OAAO;CACP,KAAK;CACL,CAAC,CAAC,gBAAgB,SAAS,CAEa,GAAG,YAAY;AAExD,MAAM,cAAc,KAAK;CACxB,mBAAmB;CACnB,mBAAmB;CACnB,sBAAsB;CACtB,gBAAgB;CAChB,uBAAuB;CACvB,sBAAsB;CACtB,kBAAkB;CAClB,4BAA4B;CAC5B,0BAA0B;CAC1B,kBAAkB;CAClB,qBAAqB;CACrB,sBAAsB;CACtB,cAAc;CACd,qBAAqB;CACrB,cAAc;CACd,sBAAsB;CACtB,CAAC,CAAC,gBAAgB,SAAS;AAE5B,SAAS,QAAQ,QAA6D;CAC7E,MAAM,QAAQ,OAAO;AACrB,KAAI,UAAU,KAAA,EACb,QAAO,EAAE;CAGV,MAAM,OAA6B;EAClC,KAAK;EACL,gBAAgB,MAAM;EACtB,gBAAgB,MAAM;EACtB,aAAa,MAAM;EACnB,MAAM;EACN,eAAe,MAAM;EACrB,eAAe,MAAM;EACrB,YAAY,gBAAgB,MAAM,WAAW;EAC7C,kBAAkB,MAAM;EACxB,WAAW,MAAM;EACjB,GAAG,wBAAwB,MAAM;EACjC;AAED,QAAO,CACN,6BAA6B,QAC1B;EAAE,GAAG;EAAM,yBAAyB,MAAM;EAAyB,GACnE,KACH;;AAGF,SAAS,iBAAiB,OAAmD;CAC5E,MAAM,OAA6B;EAClC,KAAK,MAAM;EACX,gBAAgB,MAAM;EACtB,gBAAgB,MAAM;EACtB,aAAa,MAAM;EACnB,MAAM;EACN,eAAe,MAAM;EACrB,eAAe,MAAM;EACrB,YAAY,MAAM;EAClB,kBAAkB,MAAM;EACxB,WAAW,MAAM;EACjB,GAAG,wBAAwB,MAAM;EACjC;AAED,QAAO,6BAA6B,QACjC;EAAE,GAAG;EAAM,yBAAyB,MAAM;EAAyB,GACnE;;AAGJ,eAAe,UACd,OACA,KAC2D;AAC3D,QAAO;EAAE,MAAM,iBAAiB,MAAM;EAAE,SAAS;EAAM;;AAGxD,SAAS,gBAAgB,GAA2B,GAAoC;AACvF,KAAI,MAAM,KAAA,EACT,QAAO,MAAM,KAAA;AAGd,KAAI,MAAM,KAAA,EACT,QAAO;AAGR,QAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE;;AAG3C,SAAS,qBACR,SACA,SACwB;AACxB,QAAO;EACN,GAAI,QAAQ,eAAe,QAAQ,aAAa,EAAE,GAAG,CAAC,aAAa;EACnE,GAAG,uBAAuB,QAAQ,SAAS;GAC1C,MAAM,mBAAmB,QAAQ;AACjC,UAAO,qBAAqB,KAAA,KAAa,qBAAqB,QAAQ;IACrE;EACF,GAAI,QAAQ,gBAAgB,KAAA,KAAa,QAAQ,gBAAgB,QAAQ,cACtE,EAAE,GACF,CAAC,cAAc;EAClB,GAAI,6BAA6B,WACjC,QAAQ,4BAA4B,QAAQ,0BACzC,CAAC,0BAA0B,GAC3B,EAAE;EACL,GAAG,mBAAmB,QACpB,UAAU,SAAS,WAAW,CAAC,gBAAgB,QAAQ,QAAQ,QAAQ,OAAO,CAC/E;EACD;;AAGF,SAAS,YACR,SACA,SACU;AACV,QAAO,qBAAqB,SAAS,QAAQ,CAAC,WAAW;;;;;;;;;;;;;;;;;;;;;ACpH1D,MAAa,sBAAoC;CAChD,kBAAkB;CAClB,UAAU;CACV,OAAO;CACP,UDwH2D;EAC3D;EACA;EACA;EACA;EACA,MAAM;EACN;EACA;CC9HA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACgED,SAAgB,KACf,SACA,SAC2B;CAC3B,MAAM,eAAe,IAAI,IAAI,QAAQ,KAAK,UAAU,CAACE,eAAa,MAAM,EAAE,MAAM,CAAC,CAAC;AAClF,QAAO,QAAQ,KAAK,UAAU,aAAa,OAAO,aAAa,IAAIA,eAAa,MAAM,CAAC,CAAC,CAAC;;AAG1F,SAASA,eAAa,UAAyE;AAC9F,QAAO,GAAG,SAAS,KAAK,GAAG,SAAS;;AAGrC,SAAS,aACR,SACA,SACY;AACZ,KAAI,YAAY,KAAA,EACf,QAAO;EAAE,KAAK,QAAQ;EAAK;EAAS,MAAM;EAAU;CAIrD,MAAM,gBADS,oBAAoB,QAAQ,MACd,qBAAqB,SAAS,QAAQ;AACnE,KAAI,cAAc,WAAW,EAC5B,QAAO;EAAE,KAAK,QAAQ;EAAK,MAAM,QAAQ;EAAM,MAAM;EAAQ;AAG9D,QAAO;EAAE,KAAK,QAAQ;EAAK;EAAe;EAAS;EAAS,MAAM;EAAU;;;;;;;;;ACjH7E,MAAa,wBAAwB;AAErC,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4B5B,SAAgB,wBAAwB,OAAe,QAAyB;AAE/E,SADiB,UAAA,cACD,QAAQ,sBAAsB,QAAQ,SAAS;AAC9D,MAAI,SAAS,QACZ,QAAO,MAAM,aAAa;AAG3B,MAAI,SAAS,QACZ,QAAO,WAAW,MAAM;AAGzB,SAAO;GACN;;AAGH,SAAS,WAAW,OAAuB;AAC1C,QAAO,MAAM,OAAO,EAAE,CAAC,aAAa,GAAG,MAAM,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC8LtD,SAAgB,cAAc,QAA6D;AAI1F,QAHgB,OAAO,OAAO,oBAAoB,CAGnC,SAAS,WAAW,OAAO,QAAQ,OAAO,CAAC;;;;;;;;;;;;;;;ACrN3D,MAAa,wBAAwB;AA4ErC,MAAM,0BAA0B;CAC/B;CACA;CACA;CACA;CACA;AAED,MAAM,mBAAmB,CAAC,eAAe,cAAc;;;;;;;;;;;AA2BvD,SAAgB,mBAAmB,KAAqB;AACvD,QAAO,WAAW,SAAS,CAAC,OAAO,IAAI,CAAC,OAAO,MAAM,CAAC,MAAM,GAAG,EAAE;;;;;;;;;;AAWlE,SAAgB,2BAA2B,KAAqB;AAC/D,QAAO,GAAG,sBAAsB,GAAG,mBAAmB,IAAI;;;;;;;;;;;;;;;;;;;;;;AAuB3D,SAAgB,eAAe,QAAwB,QAA0C;CAChG,MAAM,mBAAmB,QAAQ;CACjC,MAAM,sBAAsB,QAAQ;CACpC,MAAM,SAAS,aAAa;EAC3B,YAAY,OAAO;EACnB,UAAU;EACV,aAAa,qBAAqB;EAClC,CAAC;CACF,MAAM,SAAS,aAAa;EAC3B,YAAY,OAAO;EACnB,UAAU;EACV,aAAa,qBAAqB;EAClC,CAAC;CACF,MAAM,WAAW,eAAe;EAC/B,YAAY,OAAO;EACnB,UAAU;EACV,aAAa,qBAAqB;EAClC,CAAC;AAEF,KAAI,WAAW,OAAO,UAAU,WAAW,OAAO,UAAU,aAAa,OAAO,SAC/E,QAAO;AAGR,QAAO;EACN,GAAG;EACH,GAAI,WAAW,KAAA,IAAY,EAAE,GAAG,EAAE,QAAQ;EAC1C,GAAI,WAAW,KAAA,IAAY,EAAE,GAAG,EAAE,QAAQ;EAC1C,GAAI,aAAa,KAAA,IAAY,EAAE,GAAG,EAAE,UAAU;EAC9C;;;;;;;;;;;;;;;;;;;;;AAsBF,SAAgB,4BACf,QACA,qBACqC;CACrC,MAAM,SAAS,OAAO,QAAQ,OAAO,UAAU,EAAE,CAAC,CAChD,QACC,CAAC,KAAK,WACN,MAAM,aAAa,QAAQ,qBAAqB,SAAS,SAAS,KACnE,CACA,KAAK,CAAC,KAAK,WAAgC;AAC3C,SAAO;GACN,KAAK,cAAc,IAAI;GACvB,mBAAmB,sBAAsB,MAAM;GAC/C,MAAM;GACN;GACA;CACH,MAAM,WAAW,OAAO,QAAQ,OAAO,YAAY,EAAE,CAAC,CACpD,QACC,CAAC,KAAK,WACN,MAAM,aAAa,QAAQ,qBAAqB,WAAW,SAAS,KACrE,CACA,KAAK,CAAC,KAAK,WAAgC;AAC3C,SAAO;GACN,KAAK,cAAc,IAAI;GACvB,mBAAmB,yBAAyB,KAAK,MAAM;GACvD,MAAM;GACN;GACA;AAEH,QAAO,CAAC,GAAG,QAAQ,GAAG,SAAS;;AAGhC,SAAS,sBACR,kBACA,QAC2D;AAC3D,KAAI,qBAAqB,KAAA,KAAa,OAAO,qBAAqB,UACjE,QAAO;AAGR,QAAO,OAAO,YAAY,OAAO,KAAK,UAAU,CAAC,OAAO,iBAAiB,OAAO,CAAC,CAAC;;;;;;;;;;;;;;;;;;AAsBnF,SAAS,yBACR,QACuB;CACvB,MAAM,oBAAoB,OAAO,MAAM,UAAU,UAAU,KAAA,EAAU;AACrE,KAAI,sBAAsB,KAAA,KAAa,sBAAsB,MAC5D;CAGD,MAAM,YAAqC,EAAE;AAC7C,MAAK,MAAM,SAAS,QAAQ;AAC3B,MAAI,OAAO,UAAU,SACpB;AAGD,OAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,MAAM,CACjD,KAAI,EAAE,SAAS,WACd,WAAU,SAAS;;AAKtB,QAAO;;AAGR,SAAS,eAGP,QAIgD;CACjD,MAAM,EAAE,YAAY,oBAAoB,gBAAgB;AACxD,QAAO,OAAO,QAAQ,WAAW,CAAC,KAAK,CAAC,KAAK,WAAW;AACvD,SAAO;GACN;GACA;GACA,UAAU,yBAAmC;IAC5C,cAAc;IACd,MAAM;IACN;IACA,CAAC;GACF;GACA;;AAGH,SAAS,iBAGP,QAA8F;CAC/F,MAAM,EAAE,YAAY,oBAAoB,aAAa,WAAW;AAChE,KAAI,eAAe,KAAA,EAClB;CAGD,MAAM,WAAW,eAAgC;EAChD;EACA;EACA;EACA,CAAC;AAEF,KAAI,SAAS,OAAO,SAAS,KAAK,aAAa,KAAA,EAAU,CACxD,QAAO;AAGR,QAAO,OAAO,YACb,SAAS,KAAK,SAAS;AACtB,SAAO,KAAK,aAAa,KAAA,IACrB,CAAC,KAAK,KAAK,KAAK,MAAM,GACtB,CACD,KAAK,KACL,OAAO;GAAE,KAAK,KAAK;GAAK,OAAO,KAAK;GAAO,UAAU,KAAK;GAAU,CAAC,CACrE;GACF,CACF;;AAGF,SAAS,WAAW,OAAsB,UAAmD;AAC5F,QAAO;EACN,GAAG;EACH,MAAM,SAAS,QAAA;EACf,aAAa,SAAS,eAAA;EACtB,MAAM,SAAS,QAAQ,EAAE,SAAA,+BAA6B;EACtD,GAAI,MAAM,UAAU,KAAA,IAAY,EAAE,GAAG,EAAE,OAAO,SAAS,SAAA,OAAyB;EAChF;;AAGF,SAAS,aACR,QAC2B;CAC3B,MAAM,EAAE,YAAY,UAAU,gBAAgB;AAC9C,QAAO,iBAA0D;EAChE;EACA,oBAAoB,sBAAsB,UAAU,wBAAwB;EAC5E;EACA,SAAS,SAAS,WAAW,KAAK,OAAO,KAAK,SAAS;EACvD,CAAC;;AAGH,SAAS,YACR,OACA,UACqB;AACrB,QAAO;EACN,GAAG;EACH,aAAa,SAAS,eAAA;EACtB,aAAa,SAAS,eAAe,MAAM;EAC3C;;AAGF,SAAS,aACR,QAC2B;CAC3B,MAAM,EAAE,YAAY,UAAU,gBAAgB;AAC9C,QAAO,iBAA4D;EAClE;EACA,oBAAoB,sBAAsB,UAAU,iBAAiB;EACrE;EACA,SAAS,SAAS,YAAY,KAAK,OAAO,KAAK,SAAS;EACxD,CAAC;;AAGH,SAAS,cAAc,QAAuD;CAC7E,MAAM,EAAE,KAAK,OAAO,aAAa;AACjC,QAAO;EACN,GAAG;EACH,MAAM,SAAS,QAAQ,2BAA2B,IAAI;EACtD,aAAa,SAAS,eAAA;EACtB,MAAM,SAAS,QAAQ,EAAE,SAAA,+BAA6B;EACtD,GAAI,MAAM,UAAU,KAAA,IAAY,EAAE,GAAG,EAAE,OAAO,SAAS,SAAA,OAAyB;EAChF;;AAGF,SAAS,eACR,QAC6B;CAC7B,MAAM,EAAE,YAAY,UAAU,gBAAgB;AAC9C,QAAO,iBAA0E;EAChF;EACA,oBAAoB,sBAAsB,UAAU,wBAAwB;EAC5E;EACA,QAAQ;EACR,CAAC;;AAGH,SAAS,sBAAsB,OAA+B;AAC7D,QACC,MAAM,SAAA,mBACN,MAAM,gBAAA,MACN,MAAM,KAAK,aAAA,iCACV,MAAM,UAAU,KAAA,KAAa,MAAM,UAAA;;AAItC,SAAS,yBAAyB,KAAa,OAAuC;AAQrF,QACC,EAFA,MAAM,SAAS,2BAA2B,IAAI,IAAI,MAAM,SAAA,qBAGxD,MAAM,gBAAA,MACL,MAAM,SAAS,KAAA,KAAa,MAAM,KAAK,aAAA,iCACvC,MAAM,UAAU,KAAA,KAAa,MAAM,UAAA;;;;;;;;;;;;;;;;;;;AC1UtC,SAAgB,wBACf,QACA,aACoD;CACpD,MAAM,QAAQ,OAAO,aAAa;AAClC,KAAI,UAAU,KAAA,EACb,QAAO;EAAE,KAAK,mBAAmB,QAAQ,YAAY;EAAE,SAAS;EAAO;CAGxE,MAAM,SAAS,cAAc,QAAQ,MAAM;CAC3C,MAAM,iBAAiB,mBAAmB,QAAQ,YAAY;AAC9D,KAAI,mBAAmB,KAAA,EACtB,QAAO;EAAE,KAAK;EAAgB,SAAS;EAAO;CAG/C,MAAM,kBAAkB,oBAAoB,QAAQ,YAAY;AAChE,KAAI,oBAAoB,KAAA,EACvB,QAAO;EAAE,KAAK;EAAiB,SAAS;EAAO;CAGhD,MAAM,qBAAqB,uBAAuB,QAAQ,YAAY;AACtE,KAAI,uBAAuB,KAAA,EAC1B,QAAO;EAAE,KAAK;EAAoB,SAAS;EAAO;AAGnD,QAAO;EAAE,MAAM;GAAE;GAAO;GAAQ;EAAE,SAAS;EAAM;;;;;;;;;;;AAYlD,SAAgB,yBAAyB,OAAuD;AAC/F,QAAO;EACN,GAAI,MAAM,SAAS,EAAE,QAAQ,sBAAsB,MAAM,OAAO,EAAE,GAAG,EAAE;EACvE,GAAI,MAAM,SAAS,EAAE,QAAQ,sBAAsB,MAAM,OAAO,EAAE,GAAG,EAAE;EACvE,GAAI,MAAM,WAAW,EAAE,UAAU,sBAAsB,MAAM,SAAS,EAAE,GAAG,EAAE;EAC7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgFF,SAAgB,kBACf,QACA,aACiD;CACjD,MAAM,eAAe,wBAAwB,QAAQ,YAAY;AACjE,KAAI,CAAC,aAAa,QACjB,QAAO;CAGR,MAAM,EAAE,OAAO,WAAW,aAAa;AACvC,QAAO;EAAE,MAAM,gBAAgB;GAAE;GAAQ;GAAO;GAAQ,CAAC;EAAE,SAAS;EAAM;;AAG3E,SAAS,mBACR,QACA,aACuC;CACvC,MAAM,EAAE,WAAW;AACnB,KAAI,WAAW,KAAA,EACd;CAGD,MAAM,aAAqD;AAC3D,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,WAAW,EAAE;AACtD,MAAI,MAAM,SAAS,KAAA,EAClB,QAAO;GAAE;GAAK;GAAa,MAAM;GAAuB,cAAc;GAAQ;AAG/E,MAAI,MAAM,gBAAgB,KAAA,EACzB,QAAO;GACN;GACA;GACA,MAAM;GACN,cAAc;GACd;AAGF,MAAI,MAAM,SAAS,KAAA,EAClB,QAAO;GAAE;GAAK;GAAa,MAAM;GAAuB,cAAc;GAAQ;;;AAOjF,SAAS,WACR,SACA,MACW;AAaX,QAAO,KAAK,SAAS,QAAQ,EAAE,CAAC;;AAGjC,SAAS,iBACR,SACA,MACuC;AACvC,KAAI,YAAY,KAAA,EAIf,QAAO;AAGR,QAAO;EACN,GAAK,QAAQ,EAAE;EACf,GAAG,OAAO,YACT,OAAO,QAAQ,QAAQ,CAAC,KAAK,CAAC,KAAK,aAAa;AAC/C,UAAO,CAAC,KAAK,WAAqB,SAAS,OAAO,KAAK,CAAC;IACvD,CACF;EACD;;AAGF,SAAS,cACR,SACA,MACoC;AACpC,KAAI,YAAY,KAAA,KAAa,SAAS,KAAA,EACrC;AASD,QAAO,KAAK,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC;;AAGvC,SAAS,cACR,SACwD;AACxD,KAAI,YAAY,KAAA,EACf;AAGD,QAAO,OAAO,YACb,OAAO,QAAQ,QAAQ,CAAC,KAAK,CAAC,KAAK,gBAAgB;EAClD,MAAM,EAAE,UAAU,WAAW,GAAG,SAAS;AACzC,SAAO,CAAC,KAAK,KAAK;GACjB,CACF;;AAGF,SAAS,cAAc,QAAgB,OAAyC;CAC/E,MAAM,SAAS,iBAAgC,cAAc,MAAM,OAAO,EAAE,OAAO,OAAO;CAC1F,MAAM,SAAS,iBAAqC,cAAc,MAAM,OAAO,EAAE,OAAO,OAAO;CAC/F,MAAM,WAAW,iBAChB,cAAc,MAAM,SAAS,EAC7B,OAAO,SACP;CACD,MAAM,WAAW,cAAc,MAAM,UAAU,OAAO,SAAS;CAC/D,MAAM,QAAQ,MAAM,SAAS,OAAO;CAEpC,MAAM,EACL,QAAQ,aACR,UAAU,eACV,UAAU,eACV,GAAG,SACA;AAEJ,QAAO;EACN,GAAG;EACH,GAAI,WAAW,KAAA,IAAY,EAAE,GAAG,EAAE,QAAQ;EAC1C,GAAI,WAAW,KAAA,IAAY,EAAE,GAAG,EAAE,QAAQ;EAC1C,GAAI,aAAa,KAAA,IAAY,EAAE,GAAG,EAAE,UAAU;EAC9C,GAAI,UAAU,KAAA,IAAY,EAAE,GAAG,EAAE,OAAO;EACxC,GAAI,aAAa,KAAA,IAAY,EAAE,GAAG,EAAE,UAAU;EAC9C;;AAGF,SAAS,mBAAmB,QAAgB,aAA8C;AACzF,QAAO;EACN,UAAU,OAAO,KAAK,OAAO,aAAa;EAC1C;EACA,MAAM;EACN;;AAGF,SAAS,uBACR,WACA,aAC2C;CAC3C,MAAM,EAAE,aAAa;AACrB,KAAI,aAAa,KAAA,EAChB;AAQD,KADkD,SACpC,eAAe,KAAA,EAC5B,QAAO;EAAE;EAAa,MAAM;EAA2B,cAAc;EAAc;;AAMrF,SAAS,oBACR,WACA,aACwC;CACxC,MAAM,EAAE,WAAW;AACnB,KAAI,WAAW,KAAA,EACd;CAOD,MAAM,aAA0D;AAChE,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,WAAW,EAAE;AACtD,MAAI,MAAM,YAAY,KAAA,EACrB,QAAO;GACN;GACA;GACA,MAAM;GACN,cAAc;GACd;AAGF,MAAI,MAAM,aAAa,KAAA,EACtB,QAAO;GACN;GACA;GACA,MAAM;GACN,cAAc;GACd;;;AAOJ,SAAS,sBACR,SACkC;CAClC,MAAM,QAA+B,EAAE;AACvC,MAAK,MAAM,CAAC,KAAK,eAAe,OAAO,QAAQ,QAAQ,CACtD,KAAI,WAAW,aAAa,KAAA,EAC3B,OAAM,OAAO,WAAW;AAI1B,QAAO;;AAGR,SAAS,cAAc,QAAgB,OAA6C;AACnF,KAAI,OAAO,mBAAmB,YAAY,MACzC;CAGD,MAAM,EAAE,UAAU;AAClB,KAAI,UAAU,KAAA,KAAa,UAAU,GACpC;AAGD,QAAO,wBAAwB,OAAO,OAAO,mBAAmB,OAAO;;AAGxE,SAAS,oBACR,UACA,QACoC;AACpC,KAAI,aAAa,KAAA,KAAa,WAAW,KAAA,KAAa,SAAS,gBAAgB,KAAA,EAC9E,QAAO;AAGR,QAAO;EAAE,GAAG;EAAU,aAAa,SAAS,SAAS;EAAa;;AAGnE,SAAS,kBACR,QACA,QACiD;AACjD,KAAI,WAAW,KAAA,KAAa,WAAW,KAAA,EACtC,QAAO;AAGR,QAAO,OAAO,YACb,OAAO,QAAQ,OAAO,CAAC,KAAK,CAAC,KAAK,WAAW;AAC5C,MAAI,MAAM,gBAAgB,KAAA,EACzB,QAAO,CAAC,KAAK,MAAM;AAGpB,SAAO,CAAC,KAAK;GAAE,GAAG;GAAO,aAAa,SAAS,MAAM;GAAa,CAAC;GAClE,CACF;;AAGF,SAAS,gBAAgB,QAIN;CAClB,MAAM,EAAE,QAAQ,OAAO,WAAW;CAClC,MAAM,WAAW,eAAe,QAAQ;EACvC,UAAU,MAAM;EAChB,aAAa,yBAAyB,MAAM;EAC5C,CAAC;CACF,MAAM,SAAS,cAAc,QAAQ,MAAM;CAC3C,MAAM,SAAS,kBAAkB,SAAS,QAAQ,OAAO;CACzD,MAAM,WAAW,oBAAoB,SAAS,UAAU,OAAO;AAE/D,QAAO;EACN,GAAG;EACH,GAAI,WAAW,KAAA,IAAY,EAAE,GAAG,EAAE,QAAQ;EAC1C,GAAI,aAAa,KAAA,IAAY,EAAE,GAAG,EAAE,UAAU;EAC9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AChUF,eAAsB,SACrB,KACA,UACA,WAC4E;CAC5E,MAAM,QAAQ,KAAK,KAAK;CACxB,MAAM,EAAE,WAAW,QAAQ,WAAW,sBAAsB,KAAK,UAAU;CAC3E,MAAM,QAAQ,MAAM,iBAAiB;EAAE;EAAQ;EAAQ;EAAU;EAAW,CAAC;CAC7E,MAAM,MAAM,KAAK,KAAK;CAEtB,MAAM,EAAE,SAAS,aAAa,kBAAkB,MAAM,KAAK,SAAS,KAAK,QAAQ,CAAC;AAClF,kBAAiB;EAAE;EAAK;EAAU;EAAW;EAAO;EAAW;EAAO,CAAC;CAEvE,MAAM,CAAC,MAAM,GAAG,QAAQ;AACxB,KAAI,SAAS,KAAA,EACZ,QAAO;EAAE,MAAM;EAAS,SAAS;EAAM;AAGxC,QAAO;EAAE,KAAK;GAAE;GAAS,UAAU,CAAC,MAAM,GAAG,KAAK;GAAE;EAAE,SAAS;EAAO;;AAGvE,SAAS,cACR,KACA,OAC2C;AAC3C,QAAO;EAAE,KAAK;GAAE;GAAK;GAAO,MAAM;GAAiB;EAAE,SAAS;EAAO;;AAGtE,SAAS,aAAa,KAAkB,UAA0D;AACjG,QAAO,IAAI,SACV,yCAAyC,IAAI,aAAa,SAAS,SAAS,QAAQ,SAAS,UAC7F,EAAE,YAAY,GAAG,CACjB;;AAGF,eAAe,SACd,IACA,QACoD;AACpD,KAAI,GAAG,SAAS,UAAU;EACzB,MAAM,UAAU,MAAM,OAAO,OAAO,GAAG,QAAQ;AAC/C,SAAO,QAAQ,UAAU,UAAU,cAAc,GAAG,KAAK,QAAQ,IAAI;;AAGtE,KAAI,OAAO,WAAW,KAAA,EACrB,QAAO;EAAE,KAAK;GAAE,KAAK,GAAG;GAAK,MAAM;GAAqB;EAAE,SAAS;EAAO;AAG3E,KAAI,GAAG,QAAQ,SAAS,GAAG,QAAQ,KAClC,QAAO,cACN,GAAG,KACH,aAAa,GAAG,KAAK;EAAE,QAAQ,GAAG,QAAQ;EAAM,UAAU,GAAG,QAAQ;EAAM,CAAC,CAC5E;CAGF,MAAM,UAAU,MAAM,OAAO,OAAO,GAAG,SAAoC,GAAG,QAAQ;AACtF,QAAO,QAAQ,UAAU,UAAU,cAAc,GAAG,KAAK,QAAQ,IAAI;;AAGtE,eAAe,eACd,IACA,UACoD;AAIpD,SAAQ,GAAG,QAAQ,MAAnB;EACC,KAAK,mBACJ,QAAO,SAAS,IAA0B,SAAS,iBAAiB;EAErE,KAAK,WACJ,QAAO,SAAS,IAAkB,SAAS,SAAS;EAErD,KAAK,QACJ,QAAO,SAAS,IAAe,SAAS,MAAM;EAE/C,KAAK,WACJ,QAAO,SAAS,IAAkB,SAAS,SAAS;;;AAKvD,eAAe,WACd,IACA,UACoD;AACpD,KAAI;AACH,SAAO,MAAM,eAAe,IAAI,SAAS;UACjC,KAAK;AACb,SAAO;GAAE,KAAK;IAAE,KAAK,GAAG;IAAK,OAAO;IAAK,MAAM;IAAmB;GAAE,SAAS;GAAO;;;AAKtF,SAAS,qBAAqB,OAA4C;CACzE,MAAM,EAAE,KAAK,aAAa,UAAU;AACpC,SAAQ,MAAM,MAAd;EACC,KAAK,mBACJ,QAAO;GACN;GACA;GACA,MAAM;GACN,QAAQ;GACR,SAAS,MAAM;GACf,cAAc;GACd;EAEF,KAAK,WACJ,QAAO;GACN;GACA;GACA,MAAM;GACN,QAAQ;GACR,SAAS,MAAM;GACf,cAAc;GACd;EAEF,KAAK,QACJ,QAAO;GACN;GACA;GACA,MAAM;GACN,QAAQ;GACR,SAAS,MAAM;GACf,cAAc;GACd;EAEF,KAAK,WACJ,QAAO;GACN;GACA;GACA,MAAM;GACN,QAAQ;GACR,SAAS,MAAM;GACf,cAAc;GACd;;;AAKJ,SAAS,gBAAgB,OAA0C;CAClE,MAAM,EAAE,aAAa,IAAI,YAAY;AACrC,KAAI,CAAC,QAAQ,QACZ,QAAO;EACN,KAAK,GAAG;EACR;EACA,OAAO,QAAQ;EACf,MAAM;EACN,QAAQ,GAAG;EACX,cAAc,GAAG,QAAQ;EACzB;AAGF,KAAI,GAAG,SAAS,SACf,QAAO;EACN,KAAK,GAAG;EACR,eAAe,GAAG;EAClB;EACA,MAAM;EACN,QAAQ;EACR,cAAc,GAAG,QAAQ;EACzB;AAGF,QAAO,qBAAqB;EAAE,KAAK,GAAG;EAAK;EAAa,OAAO,QAAQ;EAAM,CAAC;;AAG/E,eAAe,kBAAkB,OAAqD;CACrF,MAAM,EAAE,IAAI,UAAU,cAAc;AACpC,KAAI,cAAc,KAAA,EACjB,WAAU,SAAS,KAAK;EACvB,KAAK,GAAG;EACR,aAAa,UAAU;EACvB,MAAM;EACN,QAAQ,GAAG;EACX,cAAc,GAAG,QAAQ;EACzB,CAAC;CAGH,MAAM,UAAU,MAAM,WAAW,IAAI,SAAS;AAC9C,KAAI,cAAc,KAAA,EACjB,WAAU,SAAS,KAClB,gBAAgB;EAAE,aAAa,UAAU;EAAa;EAAI;EAAS,CAAC,CACpE;AAGF,QAAO;EAAE;EAAI;EAAS;;AAGvB,eAAe,iBAAiB,OAAmE;CAClG,MAAM,cAAkC,EAAE;AAC1C,MAAK,MAAM,MAAM,MAAM,OACtB,aAAY,KACX,MAAM,kBAAkB;EAAE;EAAI,UAAU,MAAM;EAAU,WAAW,MAAM;EAAW,CAAC,CACrF;CAGF,MAAM,cAAc,MAAM,QAAQ,IACjC,MAAM,OAAO,IAAI,OAAO,OAAO;AAC9B,SAAO,kBAAkB;GAAE;GAAI,UAAU,MAAM;GAAU,WAAW,MAAM;GAAW,CAAC;GACrF,CACF;AACD,QAAO,CAAC,GAAG,aAAa,GAAG,YAAY;;AAGxC,SAAS,iBAAiB,OAAgC;AACzD,KAAI,MAAM,cAAc,KAAA,EACvB;CAGD,MAAM,UAAU,MAAM,MAAM,QAC1B,SAAS,KAAK,QAAQ,WAAW,KAAK,GAAG,SAAS,SACnD,CAAC;CACF,MAAM,UAAU,MAAM,MAAM,QAC1B,SAAS,KAAK,QAAQ,WAAW,KAAK,GAAG,SAAS,SACnD,CAAC;AACF,OAAM,UAAU,SAAS,KAAK;EAC7B;EACA,YAAY,MAAM,MAAM,MAAM;EAC9B,aAAa,MAAM,UAAU;EAC7B,QAAQ,MAAM,SAAS;EACvB,MAAM;EACN,MAAM,MAAM;EACZ;EACA,CAAC;;AAGH,SAAS,kBAAkB,UAGzB;AAGD,QAAO;EAAE,SAFO,SAAS,SAAS,YAAa,QAAQ,UAAU,CAAC,QAAQ,KAAK,GAAG,EAAE,CAAE;EAEpE,UADD,SAAS,SAAS,YAAa,QAAQ,UAAU,EAAE,GAAG,CAAC,QAAQ,IAAI,CAAE;EAC1D;;AAG7B,SAAS,SACR,IACA,WACO;AACP,KAAI,cAAc,KAAA,EACjB;AAGD,WAAU,SAAS,KAAK;EACvB,KAAK,GAAG;EACR,aAAa,UAAU;EACvB,MAAM;EACN,cAAc,GAAG;EACjB,CAAC;;AAGH,SAAS,sBACR,KACA,WAKC;CACD,MAAM,SAA2B,EAAE;CACnC,MAAM,SAA2B,EAAE;CACnC,IAAI,YAAY;AAChB,MAAK,MAAM,MAAM,IAChB,KAAI,GAAG,SAAS,QAAQ;AACvB,eAAa;AACb,WAAS,IAAI,UAAU;YACb,GAAG,QAAQ,SAAS,WAC9B,QAAO,KAAK,GAAG;KAEf,QAAO,KAAK,GAAG;AAIjB,QAAO;EAAE;EAAW;EAAQ;EAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC5ZrC,SAAgB,qBACf,MACuE;CACvE,MAAM,SAAS,KAAK,OAAO,kBAAkB;AAC7C,KAAI,WAAW,KAAA,EACd,QAAO,eAAe;CAGvB,MAAM,gBAAgB,KAAK,OAAO,UAAU;AAC5C,KAAI,kBAAkB,KAAA,EACrB,QAAO,mBAAmB;AAG3B,QAAO;EACN,MAAM,iBAAiB;GACtB;GACA,UAAU,KAAK;GACf,YAAY,gBAAgB,cAAc;GAC1C,CAAC;EACF,SAAS;EACT;;AAGF,SAAS,gBAAgE;AACxE,QAAO;EACN,KAAK;GACJ,MAAM;GACN,SAAS;GACT,UAAU;GACV;EACD,SAAS;EACT;;AAGF,SAAS,oBAAiE;AACzE,QAAO;EACN,KAAK;GACJ,MAAM;GACN,MAAM;GACN,SAAS;GACT;EACD,SAAS;EACT;;AAGF,SAAS,iBAAiB,QAAgD;CACzE,MAAM,EAAE,QAAQ,UAAU,eAAe;CACzC,MAAM,oBAAoB,IAAI,wBAAwB,EAAE,QAAQ,CAAC;CACjE,MAAM,aAAa,IAAI,iBAAiB,EAAE,QAAQ,CAAC;CACnD,MAAM,SAAS,IAAI,aAAa,EAAE,QAAQ,CAAC;CAC3C,MAAM,YAAY,IAAI,gBAAgB,EAAE,QAAQ,CAAC;AAEjD,QAAO;EACN,kBAAkB,6BAA6B;GAC9C,QAAQ;GACR;GACA;GACA,CAAC;EACF,UAAU,qBAAqB;GAAE,QAAQ;GAAY;GAAU;GAAY,CAAC;EAC5E,OAAO,kBAAkB;GAAE,QAAQ;GAAQ;GAAU;GAAY,CAAC;EAClE,UAAU,qBAAqB;GAAE;GAAQ;GAAW,CAAC;EACrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AClFF,eAAsB,aACrB,QACA,UAC0E;CAC1E,MAAM,UAAuC,EAAE;CAC/C,MAAM,KAAK,EAAE,UAAU;AACvB,MAAK,MAAM,SAAS,QAAQ;EAM3B,MAAM,aAAa,MADJ,oBAAoB,MAAM,MACT,UAAU,OAAO,GAAG;AACpD,MAAI,CAAC,WAAW,QACf,QAAO;AAGR,UAAQ,KAAK,WAAW,KAAK;;AAG9B,QAAO;EAAE,MAAM;EAAS,SAAS;EAAM;;;;AC5BxC,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BxB,SAAgB,eACf,MACsE;AACtE,KAAI,kBAAkB,KAAK,YAAY,CACtC,QAAO,mBAAmB,KAAK,aAAa,KAAK;AAGlD,QAAO;EACN,KAAK;GACJ,SAAS,KAAK,YAAY;GAC1B,MAAM;GACN,MAAM;GACN;EACD,SAAS;EACT;;AAGF,SAAS,mBACR,aACA,MAC4C;CAC5C,MAAM,QAAQ,KAAK,OAAO,uBAAuB,IAAI,KAAK,OAAO,eAAe;AAChF,KAAI,UAAU,KAAA,EACb,QAAO;EACN,KAAK;GACJ,MAAM;GACN,SAAS;GACT,UAAU;GACV;EACD,SAAS;EACT;AAGF,QAAO;EACN,MAAM,uBAAuB;GAC5B,OAAO,KAAK;GACZ,QAAQ,YAAY;GACpB;GACA,CAAC;EACF,SAAS;EACT;;;;;;;;;;;;;;;;;;;;ACzFF,SAAgB,sBACf,SACA,SACuC;CACvC,MAAM,YAAY,2BAA2B,QAAQ;AACrD,KAAI,cAAc,KAAA,EACjB,QAAO;CAGR,MAAM,eAAe,IAAI,IAAI,QAAQ,KAAK,UAAU,CAAC,aAAa,MAAM,EAAE,MAAM,CAAC,CAAC;AAClF,MAAK,MAAM,SAAS,SAAS;EAC5B,MAAM,UAAU,aAAa,IAAI,aAAa,MAAM,CAAC;AACrD,MAAI,YAAY,KAAA,EACf;EAID,MAAM,QADS,oBAAoB,MAAM,MACpB,qBAAqB,SAAS,MAAM;AACzD,MAAI,UAAU,KAAA,KAAa,CAAC,MAAM,QACjC,QAAO;;AAIT,QAAO;EAAE,MAAM,KAAA;EAAW,SAAS;EAAM;;AAG1C,SAAS,aAAa,UAAyE;AAC9F,QAAO,GAAG,SAAS,KAAK,GAAG,SAAS;;AAGrC,SAAS,2BACR,SACmD;CACnD,MAAM,6BAAa,IAAI,KAA0B;AACjD,MAAK,MAAM,SAAS,SAAS;AAC5B,MAAI,MAAM,SAAS,mBAClB;EAGD,MAAM,QAAQ,WAAW,IAAI,MAAM,KAAK;AACxC,MAAI,UAAU,KAAA,GAAW;AACxB,cAAW,IAAI,MAAM,MAAM,MAAM,IAAI;AACrC;;AAGD,SAAO;GACN,KAAK;IACJ,MAAM,CAAC,OAAO,MAAM,IAAI;IACxB,MAAM;IACN,SAAS,uBAAuB,MAAM,SAAS,MAAM,IAAI,mCAAmC,MAAM,KAAK;IACvG,cAAc,MAAM;IACpB;GACD,SAAS;GACT;;;;;AC5EH,MAAM,6BAA6B;;;;;;;;;;;;;AAcnC,SAAgB,yBAAyB,KAAqB;AAC7D,QAAO,GAAG,6BAA6B,IAAI;;;;ACJ5C,MAAM,gBAAgB;AACtB,MAAM,YAAY,GAAG,cAAc;AACnC,MAAM,aAAa,GAAG,cAAc;AAEpC,MAAM,sBAAsB;;;;;;gBAMZ,cAAc;;;;;;;;;;;;;;;;;;;;;;;YAuBlB,UAAU;;;AAItB,MAAM,oBACL;AAQD,SAAS,cAAc,OAAyB;AAC/C,QAAO,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS;;AAMpE,MAAM,4BAA4B;;;;;;;;AASlC,SAAgB,0BAAyC;AACxD,QAAO;;AAGR,SAAS,wBAAwB,SAAyB;CACzD,MAAM,qBAAqB,YAAY,KAAK,QAAQ,EAAE,yBAAyB,QAAQ,IAAI,CAAC,CAAC;AAC7F,eAAc,KAAK,oBAAoB,iBAAiB,EAAE,oBAAoB;AAK9E,eACC,KAAK,oBAAoB,UAAU,EACnC,KAAK,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,EAAE,CAAC,CAC9C;AACD,QAAO;;AAGR,eAAe,iBAAiB,YAA6C;CAC5E,MAAM,EAAE,KAAK,eAAe,iBAAiB;AAC7C,QAAO,IAAI,SAAS,SAAS,WAAW;AACvC,WACC,KACA;GAAC;GAAO;GAAe;GAAa,EACpC;GAAE,UAAU;GAAQ,SAAS;GAA2B,GACvD,OAAO,WAAW;AAClB,OAAI,iBAAiB,OAAO;AAC3B,WAAO,MAAM;AACb;;AAGD,WAAQ,OAAO;IAEhB;GACA;;AAGH,SAAS,qBAAqB,QAAyC;AACtE,KAAI,OAAO,WAAW,WAAW,CAKhC,OAAM,IAAI,MAAM,OAAO,MAAM,WAAW,OAAO,CAAC;CAMjD,MAAM,SAAS,KAAK,MAAM,OAAO,MAAM,UAAU,OAAO,CAAC;AAEzD,KAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,OAAO,CACzE,OAAM,IAAI,UAAU,8CAA8C;AAGnE,QAAO;;AAGR,eAAe,qBACd,SACgE;CAChE,MAAM,eAAe,QAAQ,IAAI;CACjC,MAAM,OAAO,iBAAiB,KAAA,KAAa,aAAa,SAAS,IAAI,eAAe;CACpF,MAAM,qBAAqB,wBAAwB,QAAQ,QAAQ,CAAC;AACpE,KAAI;AAMH,SAAO;GAAE,MAAM,qBALA,MAAM,iBAAiB;IACrC,KAAK;IACL,eAAe,KAAK,oBAAoB,iBAAiB;IACzD,cAAc,SAAS,QAAQ;IAC/B,CAAC,CACyC;GAAE,SAAS;GAAM;UACpD,KAAK;AACb,MAAI,cAAc,IAAI,CACrB,QAAO;GACN,KAAK;IAAE,MAAM;IAAmB,MAAM;IAAkB;GACxD,SAAS;GACT;AAIF,SAAO;GAAE,KAAK;IAAE,MAAM;IAAoB,SAD1B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;IACb;GAAE,SAAS;GAAO;WAC5D;AACT,SAAO,oBAAoB,EAAE,WAAW,MAAM,CAAC;;;;;;;;;;;;;;ACtGjD,eAAsB,eACrB,MACA,SACuC;CACvC,MAAM,MAAM,SAAS,OAAO,QAAQ,KAAK;CACzC,MAAM,aACL,SAAS,eAAe,KAAA,IAAY,KAAA,IAAY,kBAAkB,KAAK,QAAQ,WAAW;AAC3F,KAAI,eAAe,KAAA,KAAa,CAAC,eAAe,WAAW,CAC1D,QAAO;EAAE,KAAK;GAAE,MAAM;GAAgB,cAAc;GAAK;EAAE,SAAS;EAAO;CAG5E,IAAI;AACJ,KAAI;AACH,aAAW,MAAMC,WAAuC;GACvD,MAAM;GACN;GACA,SAAS,iBAAiB;IACzB,kBAAkB;IAClB,YAAY;IACZ,WAAW,KAAK;IAChB,CAAC;GACF,GAAI,eAAe,KAAA,IAAY,EAAE,GAAG,EAAE,YAAY;GAClD,CAAC;UACM,KAAK;AACb,SAAO;GAAE,KAAK,mBAAmB,KAAK,IAAI;GAAE,SAAS;GAAO;;AAG7D,KAAI,SAAS,gBAAgB,KAAA,EAC5B,QAAO;EAAE,KAAK;GAAE,MAAM;GAAgB,cAAc;GAAK;EAAE,SAAS;EAAO;AAG5E,QAAO,eAAe,SAAS,QAAQ,SAAS,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0C7D,eAAsBC,aACrB,SACuC;AACvC,QAAO,eAAe,EAAE,WAAW,yBAAyB,EAAE,EAAE,QAAQ;;AAGzE,SAAS,kBAAkB,KAAa,YAA4B;AACnE,QAAO,WAAW,WAAW,GAAG,aAAa,KAAK,KAAK,WAAW;;AAGnE,SAAS,eAAe,MAAuB;AAC9C,KAAI;AACH,SAAO,SAAS,KAAK,CAAC,QAAQ;SACvB;AACP,SAAO;;;;;;;;;;;;;;;;;;;;;AAsBT,SAAS,iBAAiB,QAAgB,KAAiC;AAC1E,KAAI,OAAO,SAAS,QAAQ,EAAE;EAC7B,MAAM,YAAY,WAAW,OAAO,GAAG,SAASC,QAAY,KAAK,OAAO;AACxE,SAAO,WAAW,UAAU,GAAG,YAAY,KAAA;;AAG5C,KAAI,WAAW,KAAK;AAKnB,MACC,yBAAyB,MAAM,cAC9B,WAAW,KAAK,KAAK,kBAAkB,YAAY,CAAC,CACpD,CAED;EAGD,MAAM,YAAY,KAAK,KAAK,qBAAqB;AACjD,SAAO,WAAW,UAAU,GAAG,YAAY,KAAA;;;AAM7C,MAAM,2BAA2B;CAChC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;;;;;;;AAmBD,IAAM,iBAAN,cAA6B,MAAM;CAClC;CAEA,YAAY,aAA0B;AACrC,SAAO;AACP,OAAK,cAAc;;;;;;;;;;;;;;;;;AAkBrB,SAAS,eAAe,QAAgB,SAAoD;CAC3F,MAAM,EAAE,kBAAkB,QAAQ;AAClC,KAAI,WAAW,OAAO,qBAAqB,KAAA,EAC1C,QAAO,iBAAiB,SAAS,QAAQ,GAAG,mBAAmB,KAAA;AAGhE,QAAO,iBAAiB,QAAQ,IAAI;;AAGrC,SAAS,6BAA6B,KAA0B,YAAiC;AAChG,KAAI,IAAI,SAAS,iBAChB,QAAO;EAAE,MAAM,IAAI;EAAM,MAAM;EAAsB;EAAY;AAGlE,QAAO;EAAE,MAAM;EAAe,SAAS,IAAI;EAAS;EAAY;;AAGjE,SAAS,iBACR,MAI2C;AAC3C,QAAO,OAAO,QAAQ,eAAe;EACpC,MAAM,MAAM,WAAW,OAAO,KAAK;EACnC,MAAM,WAAW,eAAe,QAAQ;GAAE,kBAAkB,KAAK;GAAkB;GAAK,CAAC;AACzF,MAAI,aAAa,KAAA,EAChB;EAGD,MAAM,SAAS,MAAM,KAAK,UAAU,SAAS;AAC7C,MAAI,CAAC,OAAO,QACX,OAAM,IAAI,eAAe,6BAA6B,OAAO,KAAK,SAAS,CAAC;AAG7E,SAAO;GACN,aAAa;GACb,QAAQ,OAAO;GACf,YAAY;GACZ;GACA;;;AAIH,MAAM,uBAAuB;AAK7B,MAAM,uBAAuB;AAE7B,SAAS,2BAA2B,KAAkC;AACrE,KAAI,EAAE,eAAe,UAAU,IAAI,UAAU,KAAA,EAC5C;AAGD,MAAK,MAAM,WAAW,IAAI,MAAM,MAAM,KAAK,EAAE;EAC5C,MAAM,OAAO,QAAQ,WAAW;AAChC,MAAI,CAAC,KAAK,WAAW,MAAM,CAC1B;EAGD,MAAM,QAAQ,qBAAqB,KAAK,KAAK;AAC7C,MAAI,UAAU,KACb,QAAO,MAAM;;;AAOhB,SAAS,mBAAmB,KAAiC;CAC5D,IAAI;AACJ,KAAI;AACH,YAAU,YAAY,IAAI;SACnB;AACP;;CAGD,MAAM,QAAQ,QAAQ,UAAU,CAAC,MAAM,UAAU,MAAM,WAAW,kBAAkB,CAAC;AACrF,QAAO,UAAU,KAAA,IAAY,KAAA,IAAY,KAAK,KAAK,MAAM;;AAG1D,SAAS,mBAAmB,KAAc,KAA0B;AACnE,KAAI,eAAe,eAClB,QAAO,IAAI;CAGZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;CAChE,MAAM,YAAY,2BAA2B,IAAI;AACjD,KAAI,cAAc,KAAA,EACjB,QAAO;EAAE,MAAM;EAAwB;EAAS,YAAY;EAAW;AAGxE,QAAO;EAAE,MAAM;EAAe;EAAS,YAAY,mBAAmB,IAAI,IAAI;EAAK;;;;;;;;;;;;;ACpMpF,SAAgB,wBAAwB,OAAoC;AAC3E,QAAO,UAAU,KAAA,KAAa,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuEzC,eAAsB,OAAO,SAAoE;AAChG,KAAI,QAAQ,aAAa,KAAA,EACxB,QAAO,WAAW,SAAS,QAAQ,SAAS;AAG7C,KAAI,CAAC,wBAAwB,iBAAiB,QAAQ,CAAC,cAAc,CAAC,CACrE,QAAO,WAAW,SAAS,2BAA2B,CAAC;AAGxD,QAAO,6BAA6B,QAAQ;;AAG7C,SAAS,uBAAuB,MAAkC;AACjE,QAAO,QAAQ,IAAI;;AAGpB,SAAS,iBAAiB,SAA8D;AACvF,QAAO,QAAQ,UAAU;;AAG1B,eAAe,WAAW,SAA8D;AACvF,KAAI,QAAQ,WAAW,KAAA,EACtB,QAAO;EAAE,MAAM,QAAQ;EAAQ,SAAS;EAAM;CAI/C,MAAM,SAAS,OADA,QAAQ,cAAcC,eACR;AAC7B,KAAI,CAAC,OAAO,QACX,QAAO;EAAE,KAAK;GAAE,OAAO,OAAO;GAAK,MAAM;GAAoB;EAAE,SAAS;EAAO;AAGhF,QAAO;EAAE,MAAM,OAAO;EAAM,SAAS;EAAM;;AAG5C,SAAS,cACR,SACA,QACiC;AACjC,KAAI,QAAQ,cAAc,KAAA,EACzB,QAAO;EAAE,MAAM,QAAQ;EAAW,SAAS;EAAM;CAGlD,MAAM,cAAc,mBAAmB,QAAQ,QAAQ,YAAY;AACnE,KAAI,CAAC,YAAY,QAChB,QAAO;EAAE,KAAK,YAAY;EAAK,SAAS;EAAO;AAGhD,QAAO,eAAe;EACrB,OAAO,QAAQ;EACf,QAAQ,iBAAiB,QAAQ;EACjC,aAAa,YAAY;EACzB,CAAC;;AAGH,SAAS,aAAa,QAAiE;AACtF,KAAI,OAAO,QAAQ,aAAa,KAAA,EAC/B,QAAO;EAAE,MAAM,OAAO,QAAQ;EAAU,SAAS;EAAM;AAGxD,QAAO,qBAAqB;EAC3B,QAAQ,OAAO;EACf,QAAQ,iBAAiB,OAAO,QAAQ;EACxC,UAAU,OAAO;EACjB,CAAC;;AAGH,eAAe,YAAY,SAAwE;CAClG,MAAM,SAAS,MAAM,WAAW,QAAQ;AACxC,KAAI,CAAC,OAAO,QACX,QAAO;CAGR,MAAM,WAAW,kBAAkB,OAAO,MAAM,QAAQ,YAAY;AACpE,KAAI,CAAC,SAAS,QACb,QAAO;EAAE,KAAK,SAAS;EAAK,SAAS;EAAO;CAG7C,MAAM,YAAY,SAAS;CAC3B,MAAMC,aAAW,QAAQ,YAAYC;CACrC,MAAM,YAAY,cAAc,SAAS,UAAU;AACnD,KAAI,CAAC,UAAU,QACd,QAAO;CAGR,MAAM,WAAW,aAAa;EAAE,QAAQ;EAAW;EAAS,UAAA;EAAU,CAAC;AACvE,KAAI,CAAC,SAAS,QACb,QAAO;AAGR,QAAO;EACN,MAAM;GAAE,QAAQ;GAAW,UAAA;GAAU,UAAU,SAAS;GAAM,WAAW,UAAU;GAAM;EACzF,SAAS;EACT;;AAGF,SAAS,eACR,KACA,SACsC;CACtC,MAAM,wBAAQ,IAAI,KAAmC;AACrD,MAAK,MAAM,YAAY,IACtB,OAAM,IAAI,GAAG,SAAS,KAAK,GAAG,SAAS,OAAO,SAAS;AAGxD,MAAK,MAAM,YAAY,QACtB,OAAM,IAAI,GAAG,SAAS,KAAK,GAAG,SAAS,OAAO,SAAS;AAGxD,QAAO,CAAC,GAAG,MAAM,QAAQ,CAAC;;AAG3B,SAAS,cAAc,QAAsC;CAC5D,MAAM,mBAAmB,OAAO,QAAQ,UACrC,OAAO,QAAQ,OACf,OAAO,QAAQ,IAAI;AACtB,QAAO;EACN,aAAa,OAAO;EACpB,WAAW,eAAe,OAAO,gBAAgB,iBAAiB;EAClE,SAAS;EACT;;AAGF,SAAS,SAAS,QAA2D;AAE5E,KAAI,CAAC,OAAO,QAAQ,QACnB,QAAO;EACN,KAAK;GACJ,OAAO,OAAO,QAAQ;GACtB,MAAM;GACN,cAAc,OAAO;GACrB;EACD,SAAS;EACT;AAGF,KAAI,CAAC,OAAO,QAAQ,QACnB,QAAO;EAAE,KAAK;GAAE,OAAO,OAAO,QAAQ;GAAK,MAAM;GAAe;EAAE,SAAS;EAAO;AAGnF,QAAO;EAAE,MAAM,OAAO;EAAQ,SAAS;EAAM;;AAG9C,eAAe,aACd,aACA,MAC6C;CAC7C,MAAM,UAAU,MAAM,aAAa,cAAc,KAAK,OAAO,EAAE,KAAK,SAAS;AAC7E,KAAI,CAAC,QAAQ,QACZ,QAAO;EAAE,KAAK;GAAE,OAAO,QAAQ;GAAK,MAAM;GAAsB;EAAE,SAAS;EAAO;CAGnF,MAAM,QAAQ,MAAM,KAAK,UAAU,KAAK,YAAY;AACpD,KAAI,CAAC,MAAM,QACV,QAAO;EAAE,KAAK;GAAE,OAAO,MAAM;GAAK,MAAM;GAAmB;EAAE,SAAS;EAAO;CAG9E,MAAM,iBAAiB,MAAM,MAAM,aAAa,EAAE;CAClD,MAAM,YAAY,sBAAsB,QAAQ,MAAM,eAAe;AACrE,KAAI,CAAC,UAAU,QACd,QAAO;EAAE,KAAK;GAAE,OAAO,UAAU;GAAK,MAAM;GAAsB;EAAE,SAAS;EAAO;CAIrF,MAAM,UAAU,MAAM,SADV,KAAK,QAAQ,MAAM,eAAe,EACV,KAAK,UAAU;EAAE;EAAa,UAAU,KAAK;EAAU,CAAC;CAC5F,MAAM,SAAS,cAAc;EAAE;EAAS;EAAa;EAAgB,CAAC;CAEtE,MAAM,UAAU,MAAM,KAAK,UAAU,MAAM,OAAO;AAClD,KAAI,QAAQ,QACX,MAAK,SAAS,KAAK;EAAE;EAAa,MAAM;EAAgB,CAAC;AAG1D,QAAO,SAAS;EAAE;EAAS;EAAQ;EAAS,CAAC;;AAG9C,eAAe,UACd,SACA,UAC6C;CAC7C,MAAM,WAAW,MAAM,YAAY,QAAQ;AAC3C,KAAI,CAAC,SAAS,QACb,QAAO;AAGR,QAAO,aAAa,QAAQ,aAAa;EAAE,GAAG,SAAS;EAAM;EAAU,CAAC;;AAGzE,SAAS,kBAAkB,QAAuC;CACjE,MAAM,EAAE,aAAa,UAAU,WAAW;AAC1C,KAAI,OAAO,SAAS;AACnB,WAAS,KAAK;GACb;GACA,MAAM;GACN,eAAe,OAAO,KAAK,UAAU;GACrC,CAAC;AACF;;AAGD,UAAS,KAAK;EAAE;EAAa,OAAO,OAAO;EAAK,MAAM;EAAiB,CAAC;;AAGzE,eAAe,WACd,SACA,UAC6C;CAC7C,MAAM,SAAS,MAAM,UAAU,SAAS,SAAS;AACjD,mBAAkB;EAAE,aAAa,QAAQ;EAAa;EAAU;EAAQ,CAAC;AACzE,QAAO;;AAGR,eAAe,6BACd,SAC6C;CAC7C,MAAM,WAAW,MAAM,YAAY,QAAQ;CAE3C,MAAM,WAAW,6BADG,SAAS,UAAU,SAAS,KAAK,SAAS,QAAQ,OACZ;AAE1D,KAAI,CAAC,SAAS,SAAS;AACtB,oBAAkB;GAAE,aAAa,QAAQ;GAAa;GAAU,QAAQ;GAAU,CAAC;AACnF,SAAO;;CAGR,MAAM,SAAS,MAAM,aAAa,QAAQ,aAAa;EAAE,GAAG,SAAS;EAAM;EAAU,CAAC;AACtF,mBAAkB;EAAE,aAAa,QAAQ;EAAa;EAAU;EAAQ,CAAC;AACzE,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;AC/WR,SAAgB,WAAW,QAAwC;AAClE,QAAO;EACN,aAAa,OAAO;EACpB,WAAW,iBAAiB,OAAO;EACnC,SAAS;EACT;;AAGF,SAAS,iBAAiB,QAAkE;CAC3F,MAAM,EAAE,OAAO,YAAY;AAC3B,QAAO;EACN,KAAK;EACL,gBAAgB,MAAM;EACtB,gBAAgB,MAAM;EACtB,aAAa,MAAM;EACnB,MAAM;EACN,eAAe,MAAM;EACrB;EACA,eAAe,MAAM;EACrB,YAAY,gBAAgB,MAAM,WAAW;EAC7C,kBAAkB,MAAM;EACxB,WAAW,MAAM;EACjB;;AAGF,SAAS,cAAc,KAAa,MAAqD;AACxF,QAAO;EACN,KAAK,cAAc,IAAI;EACvB,aAAa,KAAK,MAAM;EACxB,aAAa,KAAK,MAAM;EACxB,UAAU,KAAK;EACf,UAAU,KAAK,MAAM;EACrB,MAAM;EACN,SAAS,KAAK;EACd,SAAS,gBAAgB,KAAK,QAAQ;EACtC,YAAY,KAAK,MAAM;EACvB;;AAGF,SAAS,aACR,MACA,gBACmC;AACnC,QAAO;EACN,KAAK,KAAK;EACV,MAAM,KAAK,MAAM;EACjB,aAAa,KAAK,MAAM;EACxB,MAAM,KAAK,MAAM;EACjB;EACA,MAAM;EACN,SAAS,KAAK;EACd,OAAO,KAAK,MAAM;EAClB;;AAGF,SAAS,gBACR,MACA,wBAC2C;CAC3C,MAAM,OAAiD;EACtD,KAAK,KAAK;EACV,MAAM,KAAK,MAAM;EACjB,aAAa,KAAK,MAAM;EACxB,0BAA0B,KAAA;EAC1B,MAAM;EACN,SAAS,KAAK;EACd,OAAO,KAAK,MAAM;EAClB,kBAAkB,KAAA;EAClB;AAED,KAAI,KAAK,MAAM,SAAS,KAAA,KAAa,KAAK,yBAAyB,KAAA,EAClE,QAAO;AAGR,QAAO;EACN,GAAG;EACH,MAAM,KAAK,MAAM;EACjB,gBAAgB,uBAAuB,IAAI,KAAK,IAAI,IAAI,KAAK;EAC7D;;AAGF,SAAS,iBAAiB,QAA+D;CACxF,MAAM,EAAE,QAAQ,qBAAqB,2BAA2B;CAChE,MAAM,oBACL,OAAO,aAAa,KAAA,IACjB,EAAE,GACF,CACA,iBAAiB;EAChB,OAAO,OAAO,SAAS;EACvB,SAAS,OAAO,SAAS;EACzB,CAAC,CACF;CAEJ,MAAM,iBAAsD,CAAC,GAAG,OAAO,OAAO,SAAS,CAAC,CAAC,KACvF,CAAC,KAAK,WAAW,cAAc,KAAK,MAAM,CAC3C;CAED,MAAM,gBAAqD,OAAO,OAAO,KAAK,UAAU;AACvF,SAAO,aACN,OACA,oBAAoB,IAAI,MAAM,IAAI,IAAI,MAAM,qBAC5C;GACA;CAEF,MAAM,mBAAwD,OAAO,SAAS,KAAK,UAAU;AAC5F,SAAO,gBAAgB,OAAO,uBAAuB;GACpD;AAEF,QAAO;EAAC,GAAG;EAAmB,GAAG;EAAgB,GAAG;EAAe,GAAG;EAAiB;;;;;;;;;;;;;;;ACvJxF,SAAgB,yBAAyB,OAAqC;CAC7E,MAAM,cAAoC;EAAE,MAAM;EAAO,OAAO,KAAA;EAAW;AAC3E,KAAI,CAAC,MAAM,WAAW,IAAI,CACzB,QAAO;CAGR,MAAM,aAAa,MAAM,QAAQ,IAAI;AACrC,KAAI,aAAa,EAChB,QAAO;CAGR,MAAM,eAAe,MAAM,MAAM,aAAa,EAAE;CAChD,MAAM,OAAO,aAAa,WAAW;AACrC,KAAI,SAAS,aACZ,QAAO;AAGR,KAAI,SAAS,GACZ,QAAO;AAGR,QAAO;EAAE;EAAM,OAAO,MAAM,MAAM,GAAG,WAAW;EAAE;;;;;;;;;;;;;;;;;;AC3BnD,SAAgB,wBAAwB,MAAiD;CACxF,MAAM,eAAe,CACpB,KAAK,UAAU,MAAM,aACrB,GAAG,CAAC,GAAG,KAAK,OAAO,QAAQ,CAAC,CAAC,KAAK,EAAE,YAAY,MAAM,YAAY,CAClE,CAAC,QAAQ,gBAAuC,gBAAgB,KAAA,EAAU;CAC3E,MAAM,SAAS,IAAI,IAClB,aAAa,KAAK,gBAAgB,yBAAyB,YAAY,CAAC,MAAM,CAC9E;AACD,KAAI,OAAO,SAAS,EACnB;CAGD,MAAM,CAAC,SAAS;AAChB,QAAO,OAAO,aAAa;;;;AC3B5B,MAAM,wBAAwB;AA8B9B,MAAM,wBAA2D;CAChE;EACC,eAAe;EACf,cAAc;EACd,WAAW,SAAS,CAAC,GAAG,KAAK,OAAO,MAAM,CAAC;EAC3C;CACD;EACC,eAAe;EACf,cAAc;EACd,WAAW,SAAS,KAAK,OAAO,KAAK,EAAE,UAAU,IAAI;EACrD;CACD;EACC,eAAe;EACf,cAAc;EACd,WAAW,SAAS,KAAK,SAAS,KAAK,EAAE,UAAU,IAAI;EACvD;CACD;;;;;;;;;;;AAYD,SAAgB,+BACf,QACkC;CAClC,MAAM,cAAc,OAAO,QAAQ;AACnC,QAAO,CAAC,GAAG,OAAO,MAAM,SAAS,CAAC,CAAC,SAAS,CAAC,MAAM,UAA2C;EAC7F,MAAM,UAA4B;GAAE,iBAAiB;GAAM;GAAM,SAAS;GAAa;AACvF,SAAO,CACN,GAAG,0BAA0B,QAAQ,EACrC,GAAG,sBAAsB,SAAS,SAAS,uBAAuB,SAAS,KAAK,CAAC,CACjF;GACA;;AAGH,SAAS,eACR,iBACA,aACwB;CACxB,MAAM,iBAAiB,IAAI,IAAI,gBAAgB;CAC/C,MAAM,aAAa,IAAI,IAAI,YAAY;CACvC,MAAM,oBAAoB,gBAAgB,QAAQ,QAAQ,CAAC,WAAW,IAAI,IAAI,CAAC;CAC/E,MAAM,gBAAgB,YAAY,QAAQ,QAAQ,CAAC,eAAe,IAAI,IAAI,CAAC;AAC3E,QAAO,CAAC,GAAG,mBAAmB,GAAG,cAAc;;AAGhD,SAAS,uBAAuB,OAA+C;AAC9E,QAAO;EACN,aAAa,gBAAgB,MAAM,gBAAgB,GAAG,MAAM;EAC5D,MAAM;EACN,YAAY,GAAG,MAAM,gBAAgB,GAAG,MAAM;EAC9C,MAAM;EACN;;AAGF,SAAS,uBACR,SACA,MACkC;AAClC,QAAO,eAAe,KAAK,SAAS,QAAQ,KAAK,EAAE,KAAK,SAAS,QAAQ,QAAQ,CAAC,CAAC,KACjF,QAAQ;AACR,SAAO,uBAAuB;GAC7B,gBAAgB,GAAG,KAAK,cAAc,GAAG;GACzC,iBAAiB,QAAQ;GACzB,eAAe,GAAG,KAAK,eAAe;GACtC,CAAC;GAEH;;AAGF,SAAS,0BAA0B,SAA4D;AAG9F,KAF+B,QAAQ,KAAK,aAAa,KAAA,OAC9B,QAAQ,QAAQ,aAAa,KAAA,GAEvD,QAAO,EAAE;AAGV,QAAO,CACN,uBAAuB;EACtB,gBAAgB;EAChB,iBAAiB,QAAQ;EACzB,eAAe;EACf,CAAC,CACF;;;;;;;;;;;;;;;;;;ACjDF,SAAgB,gBACf,QACyC;CACzC,MAAM,EAAE,OAAO,QAAQ,gBAAgB;AACvC,KAAI,YAAY,OAAO,SAAS,EAC/B;CAGD,MAAM,SAAqC,CAAC,GAAG,MAAM,SAAS,CAAC,CAAC,KAAK,CAAC,MAAM,UAAU;AACrF,SAAO;GAAE;GAAM,OAAO,OAAO,IAAI,KAAK;GAAE;GACvC;AACF,QAAO,OAAO,YACb,CAAC,GAAG,YAAY,OAAO,SAAS,CAAC,CAAC,KAAK,CAAC,UAAU,aAAa,CAC9D,UACA,oBAAoB,SAAS;EAAE,OAAO;EAAQ;EAAU,CAAC,CACzD,CAAC,CACF;;;;;;;;;;;;;;;AAgBF,SAAgB,mBACf,QACgD;CAChD,MAAM,EAAE,MAAM,OAAO,eAAe;AACpC,KAAI,KAAK,OAAO,SAAS,EACxB;AAGD,QAAO,OAAO,YACb,CAAC,GAAG,KAAK,OAAO,SAAS,CAAC,CAAC,KAAK,CAAC,UAAU,eAAe;AACzD,SAAO,CACN,UACA,uBAAuB;GACtB,MAAM;GACN;GACA,WAAW,aAAa;GACxB,CAAC,CACF;GACA,CACF;;AAGF,SAAS,oBACR,QAC4B;CAC5B,MAAM,SAAS,OAAO,MAAM,SAAS,EAAE,WAAW;EACjD,MAAM,QAAQ,KAAK,OAAO,IAAI,OAAO,SAAS,EAAE;AAChD,SAAO,UAAU,KAAA,IAAY,EAAE,GAAG,CAAC,MAAM,OAAO,OAAO;GACtD;CAEF,MAAM,CAAC,SAAS;AAChB,QAAO,OAAO,OAAO,UAAU,OAAO,GAAG,OAAO,MAAM,CAAC,GAAG,QAAQ,KAAA;;AAGnE,SAAS,mBACR,aACA,OACqB;AACrB,KAAI,gBAAgB,KAAA,KAAa,UAAU,KAAA,EAC1C,QAAO;AAGR,QAAO,yBAAyB,YAAY,CAAC;;AAG9C,SAAS,qBAAqB,QAAwD;CACrF,MAAM,SAAS,OAAO,MAAM,SAAS,EAAE,MAAM,YAA+C;EAC3F,MAAM,QAAQ,KAAK,OAAO,IAAI,OAAO,SAAS,EAAE;AAChD,SAAO,UAAU,KAAA,IAAY,EAAE,GAAG,CAAC,mBAAmB,MAAM,aAAa,MAAM,CAAC;GAC/E;CAEF,MAAM,CAAC,SAAS;AAChB,QAAO,OAAO,OAAO,UAAU,OAAO,GAAG,OAAO,MAAM,CAAC,GAAG,QAAQ,KAAA;;AAGnE,SAAS,oBACR,SACA,eAIa;CACb,MAAM,cAAc,oBAAoB;EAAE,GAAG;EAAe,OAAO;EAAe,CAAC;CACnF,MAAM,cAAc,qBAAqB,cAAc;CACvD,MAAM,aAAa,oBAAoB;EAAE,GAAG;EAAe,OAAO;EAAc,CAAC;AACjF,QAAO;EACN,UAAU,QAAQ,MAAM;EACxB,GAAI,gBAAgB,KAAA,KAAa,EAAE,aAAa;EAChD,GAAI,gBAAgB,KAAA,KAAa,EAAE,aAAa;EAChD,GAAI,eAAe,KAAA,KAAa,EAAE,YAAY;EAC9C;;AAGF,SAAS,uBAAuB,QAAyD;CACxF,MAAM,EAAE,MAAM,OAAO,cAAc;CACnC,MAAM,EAAE,aAAa,aAAa,gBAAgB,UAAU,eAAe,KAAK;CAChF,MAAM,cAAc,mBAAmB,gBAAgB,MAAM;AAC7D,QAAO;EACN,SAAS,KAAK;EACd,GAAI,aAAa,WAAW,YAAY,EAAE,UAAU;EACpD,GAAI,CAAC,OAAO,GAAG,WAAW,aAAa,YAAY,IAAI,EAAE,aAAa;EACtE,GAAI,CAAC,OAAO,GAAG,WAAW,aAAa,YAAY,IAAI,EAAE,aAAa;EACtE,GAAI,CAAC,OAAO,GAAG,WAAW,YAAY,WAAW,IAAI,EAAE,YAAY;EACnE;;;;;;;;;;;;;;;;;;;;ACxJF,SAAgB,kBACf,OACA,aACoD;AACpD,KAAI,YAAY,SAAS,WAAW,EACnC;CAGD,MAAM,SAAS,CAAC,GAAG,MAAM,QAAQ,CAAC;AAClC,QAAO,OAAO,YACb,YAAY,SAAS,KAAK,EAAE,KAAK,YAAY,CAC5C,KACA,sBAAsB,OAAO;EAAE,OAAO;EAAQ,YAAY;EAAK,CAAC,CAChE,CAAC,CACF;;;;;;;;;;;;;;;AAgBF,SAAgB,qBACf,MACA,cACkD;CAClD,MAAM,UAA+C,EAAE;AACvD,MAAK,MAAM,EAAE,KAAK,WAAW,KAAK,UAAU;EAC3C,MAAM,iBAAiB,yBAAyB,OAAO,eAAe,KAAK;AAC3E,MAAI,OAAO,KAAK,eAAe,CAAC,SAAS,EACxC,SAAQ,OAAO;;AAIjB,QAAO,OAAO,KAAK,QAAQ,CAAC,WAAW,IAAI,KAAA,IAAY;;AAGxD,SAAS,YACR,MACA,YACoC;AACpC,QAAO,KAAK,SAAS,MAAM,EAAE,UAAU,QAAQ,WAAW,EAAE;;AAG7D,SAAS,OACR,OACsC;AACtC,QAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;;AAG5C,SAAS,mBAAmB,QAAmC;AAC9D,KAAI,OAAO,UAAU,OACpB,QAAO,OAAO,GAAG,OAAO,OAAO,KAAK,GAAG,UAAU,OAAO,OAAO,MAAM,GAAG,SAAS;AAGlF,QAAO,OAAO,GAAG,OAAO,MAAM,OAAO,MAAM;;AAG5C,SAAS,8BACR,QACuC;CACvC,MAAM,SAAS,OAAO,MAAM,SAAS,SAAkD;EACtF,MAAM,QAAQ,YAAY,MAAM,OAAO,WAAW;AAClD,SAAO,UAAU,KAAA,IAAY,EAAE,GAAG,CAAC,MAAM,OAAO,OAAO;GACtD;CAEF,MAAM,CAAC,SAAS;AAChB,QAAO,OAAO,OAAO,UAAU;AAC9B,SAAO,mBAAmB;GAAE,OAAO,OAAO;GAAO,MAAM;GAAO,OAAO;GAAO,CAAC;GAC5E,GACC,QACA,KAAA;;AAGJ,SAAS,sBACR,cACA,eAIwB;CACxB,MAAM,OAAO,8BAA8B;EAAE,GAAG;EAAe,OAAO;EAAQ,CAAC;CAC/E,MAAM,2BAA2B,8BAA8B;EAC9D,GAAG;EACH,OAAO;EACP,CAAC;CACF,MAAM,QAAQ,8BAA8B;EAAE,GAAG;EAAe,OAAO;EAAS,CAAC;CACjF,MAAM,qBAAqB,8BAA8B;EACxD,GAAG;EACH,OAAO;EACP,CAAC;AAEF,QAAO;EACN,MAAM,aAAa;EACnB,aAAa,aAAa;EAC1B,GAAI,SAAS,KAAA,KAAa,EAAE,MAAM;EAClC,GAAI,6BAA6B,KAAA,KAAa,EAAE,0BAA0B;EAC1E,GAAI,UAAU,KAAA,KAAa,EAAE,OAAO;EACpC,GAAI,uBAAuB,KAAA,KAAa,EAAE,kBAAkB,oBAAoB;EAChF;;AAGF,SAAS,yBACR,OACA,WACsB;AACtB,QAAO;EACN,GAAI,MAAM,SAAS,WAAW,QAAQ,EAAE,MAAM,MAAM,MAAM;EAC1D,GAAI,MAAM,gBAAgB,WAAW,eAAe,EAAE,aAAa,MAAM,aAAa;EACtF,GAAI,CAAC,OAAO,GAAG,WAAW,OAAO,UAAU,MAAM,OAAO,SAAS,IAAI,EAAE,MAAM,MAAM,MAAM;EACzF,GAAI,CAAC,OAAO,GAAG,WAAW,OAAO,MAAM,MAAM,IAAI,EAAE,OAAO,MAAM,OAAO;EACvE,GAAI,CAAC,OAAO,GAAG,WAAW,0BAA0B,MAAM,yBAAyB,IAAI,EACtF,0BAA0B,MAAM,0BAChC;EACD,GAAI,CAAC,OAAO,GAAG,WAAW,kBAAkB,MAAM,iBAAiB,IAAI,EACtE,kBAAkB,MAAM,kBACxB;EACD;;;;;;;;;;;;;AC9IF,SAAgB,wBACf,OACU;AAMV,QALoB,IAAI,IACvB,CAAC,GAAG,MAAM,QAAQ,CAAC,CAAC,SAAS,SAAS;AACrC,SAAO,KAAK,aAAa,KAAA,IAAY,EAAE,GAAG,CAAC,KAAK,SAAS,MAAM,WAAW;GACzE,CACF,CACkB,OAAO;;;;;;;;;;;;;;;;AAiB3B,SAAgB,kBACf,aACA,sBAC4B;CAC5B,MAAM,kBAAkB,YAAY,UAAU;AAC9C,KAAI,oBAAoB,KAAA,EACvB;AAGD,KAAI,CAAC,qBACJ,QAAO;CAGR,MAAM,EAAE,YAAY,oBAAoB,GAAG,iBAAiB;AAC5D,KAAI,OAAO,KAAK,aAAa,CAAC,WAAW,EACxC;AAGD,QAAO;;;;;;;;;;;AAYR,SAAgB,qBAAqB,SAA8D;CAClG,MAAM,eAAe,QAAQ,KAAK;AAClC,KAAI,iBAAiB,KAAA,EACpB;AAGD,KAAI,QAAQ,wBACX,QAAO,EAAE,YAAY,aAAa,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;ACItD,SAAgB,sBACf,QACwC;CACxC,MAAM,gBAAgB,YAAY,OAAO,OAAO,OAAO,mBAAmB;AAC1E,KAAI,CAAC,cAAc,QAClB,QAAO;CAGR,MAAM,UAAU,cAAc;AAC9B,QAAO;EACN,MAAM;GACL,QAAQ,YAAY;IACnB,OAAO,OAAO;IACd,aAAa,QAAQ;IACrB,aAAa,QAAQ;IACrB,CAAC;GACF,oBAAoB,QAAQ;GAC5B,UAAU,+BAA+B;IAAE,OAAO,OAAO;IAAO;IAAS,CAAC;GAC1E;EACD,SAAS;EACT;;AAGF,SAAS,YACR,OACA,SACwC;CACxC,MAAM,YAAY,CAAC,GAAG,MAAM,MAAM,CAAC;CACnC,MAAM,YAAY,YAAY,UAAU,WAAW,IAAI,UAAU,KAAK,KAAA;AACtE,KAAI,cAAc,KAAA,EACjB,QAAO;EAAE,KAAK;GAAE;GAAW,MAAM;GAA8B;EAAE,SAAS;EAAO;CAGlF,MAAM,OAAO,MAAM,IAAI,UAAU;AACjC,KAAI,SAAS,KAAA,EACZ,QAAO;EACN,KAAK;GAAE;GAAW,MAAM;GAA8B,SAAS;GAAW;EAC1E,SAAS;EACT;AAGF,QAAO;EAAE,MAAM;GAAE,MAAM;GAAW;GAAM;EAAE,SAAS;EAAM;;AAG1D,SAAS,gBACR,aAC4C;AAC5C,KAAI,YAAY,OAAO,WAAW,EACjC;AAGD,QAAO,OAAO,YAAY,YAAY,OAAO,KAAK,EAAE,KAAK,YAAY,CAAC,KAAK,MAAM,CAAC,CAAC;;AAGpF,SAAS,qBAAqB,OAAwC;AACrE,QAAO;EACN,MAAM,MAAM;EACZ,aAAa,MAAM;EACnB,MAAM,MAAM;EACZ,GAAI,MAAM,UAAU,KAAA,KAAa,EAAE,OAAO,MAAM,OAAO;EACvD;;AAGF,SAAS,sBACR,OACA,SAC+B;CAC/B,MAAM,UAA4B,EAAE;AACpC,KAAI,CAAC,OAAO,GAAG,QAAQ,MAAM,MAAM,KAAK,CACvC,SAAQ,OAAO,MAAM;AAGtB,KAAI,CAAC,OAAO,GAAG,QAAQ,aAAa,MAAM,YAAY,CACrD,SAAQ,cAAc,MAAM;AAG7B,KAAI,CAAC,OAAO,GAAG,QAAQ,KAAK,UAAU,MAAM,KAAK,SAAS,CACzD,SAAQ,OAAO,MAAM;AAGtB,KAAI,CAAC,OAAO,GAAG,QAAQ,OAAO,MAAM,MAAM,CACzC,SAAQ,QAAQ,MAAM;AAGvB,QAAO,OAAO,KAAK,QAAQ,CAAC,WAAW,IAAI,KAAA,IAAY;;AAGxD,SAAS,mBACR,MACA,SAC+C;CAC/C,MAAM,eAAe,IAAI,IACxB,SAAS,OAAO,KAAK,EAAE,KAAK,YAAY,CAAC,KAAK,MAAM,CAAC,CACrD;CACD,MAAM,UAA4C,EAAE;AACpD,MAAK,MAAM,EAAE,KAAK,WAAW,KAAK,QAAQ;EACzC,MAAM,eAAe,aAAa,IAAI,IAAI;EAC1C,MAAM,cACL,iBAAiB,KAAA,IACd,qBAAqB,MAAM,GAC3B,sBAAsB,OAAO,aAAa;AAC9C,MAAI,gBAAgB,KAAA,EACnB,SAAQ,OAAO;;AAIjB,QAAO,OAAO,KAAK,QAAQ,CAAC,WAAW,IAAI,KAAA,IAAY;;AAGxD,SAAS,sBAAsB,QAAkD;CAChF,MAAM,EAAE,SAAS,MAAM,UAAU;CACjC,MAAM,SAAS,mBAAmB,MAAM,QAAQ,QAAQ;CACxD,MAAM,SAAS,mBAAmB;EAAE;EAAM;EAAO,YAAY,QAAQ;EAAY,CAAC;CAClF,MAAM,WAAW,qBAAqB,MAAM,QAAQ,aAAa;CACjE,MAAM,WAAW,qBAAqB;EACrC;EACA,yBAAyB,QAAQ;EACjC,CAAC;AACF,QAAO;EACN,GAAI,UAAU,KAAA,KAAa,EAAE,OAAO;EACpC,GAAI,WAAW,KAAA,KAAa,EAAE,QAAQ;EACtC,GAAI,WAAW,KAAA,KAAa,EAAE,QAAQ;EACtC,GAAI,aAAa,KAAA,KAAa,EAAE,UAAU;EAC1C,GAAI,aAAa,KAAA,KAAa,EAAE,UAAU;EAC1C;;AAGF,SAAS,wBACR,OACA,SACmC;AACnC,QAAO,OAAO,YACb,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,UAAU,CAChC,MACA,sBAAsB;EAAE;EAAS;EAAM,OAAO,QAAQ,OAAO,IAAI,KAAK;EAAE,CAAC,CACzE,CAAC,CACF;;AAGF,SAAS,uBACR,OAC0C;AAC1C,QAAO,IAAI,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,UAAU,CAAC,MAAM,wBAAwB,KAAK,CAAC,CAAC,CAAC;;AAGxF,SAAS,uBACR,UACA,OAC4B;AAC5B,KAAI,aAAa,KAAA,KAAa,UAAU,KAAA,KAAa,SAAS,gBAAgB,KAAA,EAC7E,QAAO;AAGR,QAAO;EAAE,GAAG;EAAU,aAAa,yBAAyB,SAAS,YAAY,CAAC;EAAM;;AAGzF,SAAS,YAAY,QAAmC;CACvD,MAAM,EAAE,OAAO,aAAa,gBAAgB;CAC5C,MAAM,SAAS,uBAAuB,MAAM;CAC5C,MAAM,SAAS,gBAAgB;EAAE;EAAO;EAAQ;EAAa,CAAC;CAC9D,MAAM,WAAW,kBAAkB,OAAO,YAAY;CACtD,MAAM,2BAA2B,wBAAwB,MAAM;CAC/D,MAAM,eAAe,wBAAwB,OAAO;EACnD,yBAAyB;EACzB;EACA,SAAS;EACT,YAAY;EACZ,cAAc;EACd,CAAC;CACF,MAAM,SAAS,gBAAgB,YAAY;CAC3C,MAAM,WAAW,uBAChB,kBAAkB,aAAa,yBAAyB,EACxD,OAAO,IAAI,YAAY,CACvB;AAcD,QAboC;EACnC;EACA,GAAI,WAAW,KAAA,KAAa,EAAE,QAAQ;EACtC,GAAI,WAAW,KAAA,KAAa,EAAE,QAAQ;EACtC,GAAI,aAAa,KAAA,KAAa,EAAE,UAAU;EAC1C,GAAI,aAAa,KAAA,KAAa,EAAE,UAAU;EAC1C;;;;AClQF,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;AA2ElB,SAAgB,WAAW,WAA4D;AAUtF,QAAO;EAAE,QATM,UAAU,SAAS,aAA2C;AAC5E,OAAI,SAAS,SAAS,UACrB,QAAO,EAAE;GAGV,MAAM,SAAS,YAAY,SAAS;AACpC,UAAO,WAAW,KAAA,IAAY,EAAE,GAAG,CAAC,OAAO;IAC1C;EAEe,UAAU,EAAE;EAAE;;AAGhC,SAASC,aAAW,QAAmC;CACtD,MAAM,OAAO;EACZ,MAAM,OAAO;EACb,aAAa,OAAO;EACpB,MAAM,EAAE,SAAS,OAAO,cAAc;EACtC;AAED,QAAO,OAAO,UAAU,KAAA,IAAY,OAAO;EAAE,GAAG;EAAM,OAAO,OAAO;EAAO;;AAG5E,SAASC,kBAAgB,OAAkD;AAC1E,QAAO,OAAO,UAAU,SAAS,KAAK,MAAM,KAAK;;AAGlD,SAASC,aAAW,OAAoC;AACvD,QAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;;AAG5C,SAASC,YAAU,KAAkD;CACpE,MAAM,YAAY,IAAI;AACtB,QAAO,OAAO,cAAc,WAAW,YAAY,KAAA;;AAGpD,SAAS,eAAe,KAAsC;AAC7D,KAAI,CAACF,kBAAgB,IAAI,CACxB;CAGD,MAAM,OAAOC,aAAW,IAAI,QAAQ;CACpC,MAAM,cAAcA,aAAW,IAAI,eAAe;CAClD,MAAM,eAAeA,aAAW,IAAI,gBAAgB;CACpD,MAAM,kBAAkBA,aAAW,IAAI,gBAAgB;AACvD,KACC,SAAS,KAAA,KACT,gBAAgB,KAAA,KAChB,iBAAiB,KAAA,KACjB,oBAAoB,KAAA,KACpB,CAAC,YAAY,gBAAgB,CAE7B;AAGD,QAAO;EACN;EACA;EACA,cAAc,YAAY,gBAAgB;EAC1C;EACA,OAAOC,YAAU,IAAI;EACrB;;AAGF,SAASC,iBAAe,OAAoC;AAC3D,KAAI,OAAO,UAAU,SACpB,QAAO;AAGR,KAAI,OAAO,UAAU,MAAM,CAC1B,QAAO,OAAO,MAAM;;AAMtB,SAAS,gBAAgB,KAA0C;AAClE,KAAI,CAACH,kBAAgB,IAAI,CACxB;CAGD,MAAM,UAAUG,iBAAe,IAAI,WAAW;CAC9C,MAAM,cAAcA,iBAAe,IAAI,eAAe;AACtD,KAAI,YAAY,KAAA,KAAa,gBAAgB,KAAA,EAC5C;AAGD,QAAO;EAAE;EAAS;EAAa;;AAGhC,SAAS,YAAY,UAAqD;CACzE,MAAM,SAAS,eAAe,SAAS,OAAO;AAC9C,KAAI,WAAW,KAAA,EACd;CAGD,MAAM,UAAU,gBAAgB,SAAS,QAAQ;AACjD,KAAI,YAAY,KAAA,EACf;AAGD,QAAO;EACN,KAAK,cAAc,SAAS,IAAI;EAChC,OAAOJ,aAAW,OAAO;EACzB,sBAAsB,EAAE,SAAS,OAAO,cAAc;EACtD,YAAY,GAAG,UAAU,GAAG,SAAS;EACrC,SAAS;GACR,SAAS,gBAAgB,QAAQ,QAAQ;GACzC,cAAc,EAAE,SAAS,gBAAgB,QAAQ,YAAY,EAAE;GAC/D;EACD;;;;;ACzJF,MAAa,iBAA+B;CAAE,eAAe,EAAE;CAAE,UAAU,EAAE;CAAE;;;;;;;AAkC/E,SAAgBK,kBAAgB,OAAkD;AACjF,QAAO,OAAO,UAAU,SAAS,KAAK,MAAM,KAAK;;;;;;;;;;AAWlD,SAAgBC,iBAAe,OAAoC;AAClE,KAAI,OAAO,UAAU,SACpB,QAAO;AAGR,KAAI,OAAO,UAAU,MAAM,CAC1B,QAAO,OAAO,MAAM;;;;;;;;;;;;AAgBtB,SAAgB,cAAc,MAAoB,OAAmC;AACpF,QAAO;EACN,eAAe;GAAE,GAAG,KAAK;GAAe,GAAG,MAAM;GAAe;EAChE,UAAU,CAAC,GAAG,KAAK,UAAU,GAAG,MAAM,SAAS;EAC/C;;;;;;;;;AAUF,SAAgB,oBAAoB,MAAiD;AACpF,QAAO;EACN,aAAa,KAAK;EAClB,MAAM;EACN,YAAY,KAAK;EACjB,MAAM,KAAK;EACX;;;;;;;;;;AAWF,SAAgB,eAAe,YAAoB,QAAkC;AACpF,QAAO;EAAE,MAAM;EAAW;EAAY;EAAQ;;;;;;;;;;AAW/C,SAAgB,iBAAiB,YAAoB,MAAgC;AACpF,QAAO;EAAE;EAAM,MAAM;EAAa;EAAY;;;;ACnJ/C,MAAMC,6BAA2B;;;;;;AAOjC,MAAMC,mBAAkD;CACvD;EACC,OAAO;EACP,QAAQ;EACR;CACD;EACC,OAAO;EACP,QAAQ;EACR;CACD;EACC,OAAO;EACP,QAAQ;EACR;CACD;;;;;;;;;;;AAYD,SAAgB,uBACf,WACkC;AAClC,QAAO,UAAU,SAAS,aAAa;AACtC,MAAI,SAAS,SAASD,8BAA4B,CAACE,kBAAgB,SAAS,OAAO,CAClF,QAAO,EAAE;EAGV,MAAM,EAAE,KAAK,WAAW;AACxB,SAAOD,iBAAe,SAAS,SAAS;AACvC,OAAI,OAAO,KAAK,WAAW,KAAA,EAC1B,QAAO,EAAE;AAGV,UAAO,CACN,eAAe,GAAGD,2BAAyB,GAAG,IAAI,GAAG,KAAK,SAAS,KAAK,OAAO,CAC/E;IACA;GACD;;;;AC9CH,MAAMG,eAAa;AACnB,MAAM,kBAAkB;AACxB,MAAMC,6BAA2B;;;;;;;;;;;;;;;;;;AA0GjC,SAAgB,WAAW,WAA2D;CACrF,MAAM,UAAUC,eAAa,UAAU;CACvC,MAAM,UAAU,oBAAoB,QAAQ;CAC5C,MAAM,UAAU,sBAAsB,QAAQ;AAC9C,QAAO;EACN,SAAS,QAAQ;EACjB,UAAU;GAAC,GAAG,QAAQ;GAAU,GAAG;GAAS,GAAG,uBAAuB,UAAU;GAAC;EACjF;;AAGF,SAAS,oBAAoB,SAAwC;CACpE,MAAM,EAAE,qBAAqB,YAAY,WAAW;CACpD,MAAM,0BAAU,IAAI,KAA6B;CACjD,MAAM,WAAoC,EAAE;AAC5C,MAAK,MAAM,CAAC,KAAK,kBAAkB,QAAQ;EAC1C,MAAM,eAAe,WAAW,IAAI,IAAI;AACxC,MAAI,iBAAiB,KAAA,GAAW;AAC/B,YAAS,KAAK,cAAcF,cAAY,IAAI,CAAC;AAC7C;;EAGD,MAAM,SAAS,iBAAiB,eAAe,aAAa;AAC5D,MAAI,WAAW,KAAA,EACd;EAGD,MAAM,aAAa,uBAAuB;GACzC;GACA,gBAAgB,oBAAoB,IAAI,IAAI;GAC5C;GACA,SAASG,eAAa,cAAc;GACpC,CAAC;AACF,UAAQ,IAAI,KAAK,WAAW,MAAM;AAClC,WAAS,KAAK,GAAG,WAAW,SAAS;;AAGtC,QAAO;EAAE;EAAS;EAAU;;AAG7B,SAAS,sBAAsB,SAAwD;CACtF,MAAM,EAAE,qBAAqB,YAAY,WAAW;CACpD,MAAM,WAAoC,EAAE;AAC5C,MAAK,MAAM,CAAC,QAAQ,WACnB,KAAI,CAAC,OAAO,IAAI,IAAI,CACnB,UAAS,KAAK,cAAc,iBAAiB,IAAI,CAAC;AAIpD,MAAK,MAAM,CAAC,QAAQ,oBACnB,KAAI,CAAC,OAAO,IAAI,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAC3C,UAAS,KAAK,yBAAyB,IAAI,CAAC;AAI9C,QAAO;;AAGR,SAASD,eAAa,WAAwD;CAC7E,MAAM,yBAAS,IAAI,KAA6B;CAChD,MAAM,6BAAa,IAAI,KAA6B;CACpD,MAAM,sCAAsB,IAAI,KAA6B;AAC7D,MAAK,MAAM,YAAY,UACtB,SAAQ,SAAS,MAAjB;EACC,KAAKD;AACJ,uBAAoB,IAAI,SAAS,KAAK,SAAS;AAE/C;EAED,KAAK;AACJ,cAAW,IAAI,SAAS,KAAK,SAAS;AAEtC;EAED,KAAKD;AACJ,UAAO,IAAI,SAAS,KAAK,SAAS;AAElC;;AAMH,QAAO;EAAE;EAAqB;EAAY;EAAQ;;AAGnD,SAASI,aAAW,OAAoC;AACvD,QAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;;AAG5C,SAAS,oBAAoB,OAAoC;AAChE,KAAI,OAAO,UAAU,YAAY,CAAC,OAAO,UAAU,MAAM,IAAI,SAAS,EACrE;AAGD,QAAO;;AAGR,MAAM,eAA+C,CACpD;CACC,cAAc;CACd,aAAa;CACb,MAAMA;CACN,MAAM;CACN,EACD;CACC,cAAc;CACd,aAAa;CACb,MAAM;CACN,MAAM;CACN,CACD;AAED,MAAM,kBAAkD,CACvD;CACC,cAAc;CACd,aAAa;CACb,MAAMA;CACN,MAAM;CACN,CACD;AAED,SAAS,iBAAiB,SAAkD;AAC3E,QAAO,UAAU,eAAe,CAAC,GAAG,cAAc,GAAG,gBAAgB;;AAGtE,SAAS,wBACR,QACA,OACsB;AACtB,QAAO,MAAM,QAIX,aAAa,SAAS;EACtB,MAAM,QAAQ,KAAK,KAAK,OAAO,KAAK,aAAa;AACjD,MAAI,UAAU,KAAA,EACb,QAAO;AAGR,SAAO;GACN,OAAO;IAAE,GAAG,YAAY;KAAQ,KAAK,eAAe;IAAO;GAC3D,UAAU,CACT,GAAG,YAAY,UACf;IACC,cAAc,KAAK;IACnB,aAAa,KAAK;IAClB,MAAM,KAAK;IACX,CACD;GACD;IAEF;EAAE,OAAO,EAAE;EAAE,UAAU,EAAE;EAAE,CAC3B;;AAGF,SAAS,uBAAuB,QAA6D;CAC5F,MAAM,EAAE,KAAK,gBAAgB,QAAQ,YAAY;AACjD,KAAI,mBAAmB,KAAA,KAAa,CAACC,kBAAgB,eAAe,OAAO,CAC1E,QAAO;EAAE,OAAO;EAAQ,UAAU,EAAE;EAAE;CAGvC,MAAM,WAAW,wBAAwB,eAAe,QAAQ,iBAAiB,QAAQ,CAAC;AAM1F,QAAO;EACN,OAN6B;GAC7B,GAAG;GACH,OAAO;IAAE,GAAG,OAAO;IAAO,GAAG,SAAS;IAAO;GAC7C;EAIA,UAAU,SAAS,SAAS,KAAK,SAAS;AACzC,UAAO,oBAAoB;IAC1B,aAAa,UAAU,IAAI,GAAG,KAAK;IACnC,YAAY,GAAGJ,2BAAyB,GAAG,IAAI,GAAG,KAAK;IACvD,MAAM,KAAK;IACX,CAAC;IACD;EACF;;AAGF,SAASE,eAAa,UAAmC;AACxD,KAAI,CAACE,kBAAgB,SAAS,OAAO,CACpC,QAAO;AAGR,QAAO,SAAS,OAAO,eAAe;;AAGvC,SAASC,iBAAe,OAAoC;AAC3D,KAAI,OAAO,UAAU,SACpB,QAAO;AAGR,KAAI,OAAO,UAAU,MAAM,CAC1B,QAAO,OAAO,MAAM;;AAMtB,SAAS,iBAAiB,UAAuD;CAChF,MAAM,EAAE,YAAY;AACpB,KAAI,CAACD,kBAAgB,QAAQ,CAC5B;CAGD,MAAM,UAAUC,iBAAe,QAAQ,WAAW;AAClD,KAAI,YAAY,KAAA,EACf;AAGD,QAAO,EAAE,SAAS;;AAGnB,SAAS,oBAAoB,UAA0D;CACtF,MAAM,EAAE,WAAW;AACnB,KAAI,CAACD,kBAAgB,OAAO,CAC3B;CAGD,MAAM,EAAE,UAAU,aAAa;AAC/B,KAAI,OAAO,aAAa,YAAY,OAAO,aAAa,YAAY,CAAC,YAAY,SAAS,CACzF;AAGD,QAAO;EAAE;EAAU;EAAU;;AAG9B,SAAS,qBAAqB,UAA2D;CACxF,MAAM,EAAE,YAAY;AACpB,KAAI,CAACA,kBAAgB,QAAQ,CAC5B;CAGD,MAAM,EAAE,YAAY;AACpB,KAAI,OAAO,YAAY,SACtB;AAGD,QAAO,EAAE,SAAS;;AAGnB,SAAS,iBACR,eACA,cAC6B;CAC7B,MAAM,eAAe,iBAAiB,cAAc;CACpD,MAAM,aAAa,oBAAoB,aAAa;CACpD,MAAM,cAAc,qBAAqB,aAAa;AACtD,KAAI,iBAAiB,KAAA,KAAa,eAAe,KAAA,KAAa,gBAAgB,KAAA,EAC7E;AAGD,QAAO;EACN,OAAO,EAAE,UAAU,WAAW,UAAU;EACxC,UAAU,WAAW;EACrB,SAAS,EAAE,eAAe,YAAY,SAAS;EAC/C,SAAS,aAAa;EACtB;;AAGF,SAAS,cAAc,MAAc,KAA+B;AACnE,QAAO;EACN,MAAM;EACN,MAAM;EACN,YAAY,GAAG,KAAK,GAAG;EACvB;;AAGF,SAAS,yBAAyB,KAA+B;AAChE,QAAO;EACN,MAAM;EACN,MAAM;EACN,YAAY,GAAGJ,2BAAyB,GAAG;EAC3C;;;;AC7XF,MAAM,eAAe;AACrB,MAAM,oBAAoB;;;;;;;;;;;;;;;;;AAkF1B,SAAgB,aAAa,WAA8D;CAC1F,MAAM,EAAE,cAAc,aAAa,aAAa,UAAU;CAC1D,MAAM,SAAS,SAAS,SAAS,aAA8C;EAE9E,MAAM,QAAQ,eAAe,UADR,aAAa,IAAI,SAAS,IAAI,CACC;AACpD,SAAO,UAAU,KAAA,IAAY,EAAE,GAAG,CAAC,MAAM;GACxC;CAEF,MAAM,cAAc,IAAI,IAAI,SAAS,KAAK,aAAa,SAAS,IAAI,CAAC;AAKrE,QAAO;EAAE,UAAU;EAAQ,UAJV,CAAC,GAAG,aAAa,MAAM,CAAC,CACvC,QAAQ,QAAQ,CAAC,YAAY,IAAI,IAAI,CAAC,CACtC,KAAK,QAAQ,kBAAkB,IAAI,CAAC;EAED;;AAGtC,SAAS,aAAa,WAGpB;CACD,MAAM,WAAkC,EAAE;CAC1C,MAAM,+BAAe,IAAI,KAA6B;AACtD,MAAK,MAAM,YAAY,UACtB,KAAI,SAAS,SAAS,aACrB,UAAS,KAAK,SAAS;UACb,SAAS,SAAS,kBAC5B,cAAa,IAAI,SAAS,KAAK,SAAS;AAI1C,QAAO;EAAE;EAAc;EAAU;;AAGlC,SAAS,WACR,QACA,MACwB;CACxB,MAAM,OAA8B;EACnC,MAAM,OAAO;EACb,aAAa,OAAO;EACpB;CACD,MAAM,YAAY,OAAO,UAAU,KAAA,IAAY,OAAO;EAAE,GAAG;EAAM,OAAO,OAAO;EAAO;AACtF,QAAO,SAAS,KAAA,IAAY,YAAY;EAAE,GAAG;EAAW;EAAM;;AAG/D,SAAS,WAAW,OAAoC;AACvD,QAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;;AAG5C,SAAS,UAAU,KAAkD;CACpE,MAAM,YAAY,IAAI;AACtB,QAAO,OAAO,cAAc,WAAW,YAAY,KAAA;;AAGpD,SAAS,kBAAkB,KAAyC;AACnE,KAAI,CAACM,kBAAgB,IAAI,CACxB;CAGD,MAAM,OAAO,WAAW,IAAI,QAAQ;CACpC,MAAM,cAAc,WAAW,IAAI,eAAe;AAClD,KAAI,SAAS,KAAA,KAAa,gBAAgB,KAAA,EACzC;AAGD,QAAO;EAAE;EAAM;EAAa,OAAO,UAAU,IAAI;EAAE;;AAGpD,SAAS,mBAAmB,KAA6C;AACxE,KAAI,CAACA,kBAAgB,IAAI,CACxB;CAOD,MAAM,YAAYC,iBAAe,IAAI,WAAW;AAChD,KAAI,cAAc,KAAA,EACjB;AAGD,QAAO,EAAE,WAAW;;AAGrB,SAAS,sBAAsB,KAA6C;AAC3E,KAAI,CAACD,kBAAgB,IAAI,CACxB;CAGD,MAAM,WAAW,WAAW,IAAI,YAAY;CAC5C,MAAM,WAAW,WAAW,IAAI,YAAY;AAC5C,KAAI,aAAa,KAAA,KAAa,aAAa,KAAA,KAAa,CAAC,YAAY,SAAS,CAC7E;AAGD,QAAO;EAAE,UAAU,YAAY,SAAS;EAAE;EAAU;;AAGrD,SAAS,uBAAuB,KAAiD;AAChF,KAAI,CAACA,kBAAgB,IAAI,CACxB;CAGD,MAAM,UAAUC,iBAAe,IAAI,WAAW;AAC9C,KAAI,YAAY,KAAA,EACf;AAGD,QAAO,EAAE,SAAS;;AAGnB,SAAS,qBAAqB,UAAwD;CACrF,MAAM,SAAS,sBAAsB,SAAS,OAAO;CACrD,MAAM,UAAU,uBAAuB,SAAS,QAAQ;AACxD,KAAI,WAAW,KAAA,KAAa,YAAY,KAAA,EACvC;AAGD,QAAO;EACN,MAAM,EAAE,SAAS,OAAO,UAAU;EAClC,kBAAkB,QAAQ;EAC1B,sBAAsB,EAAE,SAAS,OAAO,UAAU;EAClD;;AAGF,SAAS,eACR,UACA,cAC+B;CAC/B,MAAM,SAAS,kBAAkB,SAAS,OAAO;AACjD,KAAI,WAAW,KAAA,EACd;CAGD,MAAM,UAAU,mBAAmB,SAAS,QAAQ;AACpD,KAAI,YAAY,KAAA,EACf;CAGD,MAAM,YAAY,iBAAiB,KAAA,IAAY,KAAA,IAAY,qBAAqB,aAAa;CAC7F,MAAM,iBACL,cAAc,KAAA,IACX;EAAE,kBAAkB,KAAA;EAAW,WAAW,gBAAgB,QAAQ,UAAU;EAAE,GAC9E;EACA,kBAAkB,gBAAgB,UAAU,iBAAiB;EAC7D,WAAW,gBAAgB,QAAQ,UAAU;EAC7C;CAEJ,MAAM,OAAyB;EAC9B,KAAK,cAAc,SAAS,IAAI;EAChC,OAAO,WAAW,QAAQ,WAAW,KAAK;EAC1C,YAAY,GAAG,aAAa,GAAG,SAAS;EACxC,SAAS;EACT;AAED,QAAO,cAAc,KAAA,IAClB,OACA;EAAE,GAAG;EAAM,sBAAsB,UAAU;EAAsB;;AAGrE,SAAS,kBAAkB,KAA+B;AACzD,QAAO;EACN,MAAM;EACN,MAAM;EACN,YAAY,GAAG,kBAAkB,GAAG;EACpC;;;;ACxPF,MAAMC,oBAAkB;AACxB,MAAMC,kCAAgC;AACtC,MAAM,yBAAyB;AAC/B,MAAM,yBAAyB;AAC/B,MAAM,kBACL;;;;;AAMD,MAAM,iBAAkD;CACvD;EAAE,OAAO;EAAS,QAAQ;EAAiD;CAC3E;EAAE,OAAO;EAAa,QAAQ;EAAiD;CAC/E;EAAE,OAAO;EAAS,QAAQ;EAAiD;CAC3E;EACC,OAAO;EACP,QAAQ;EACR;CACD;EACC,OAAO;EACP,QAAQ;EACR;CACD;EAAE,OAAO;EAAc,QAAQ;EAA2C;CAC1E;EAAE,OAAO;EAAiB,QAAQ;EAA8C;CAChF;;;;;;;;;;;;;;;;AAiBD,SAAgB,4BACf,WACe;AACf,QAAO;EACN,eAAe,EAAE;EACjB,UAAU,CAAC,GAAG,sBAAsB,UAAU,EAAE,GAAG,gBAAgB,UAAU,CAAC;EAC9E;;AAGF,SAAS,gBACR,WACkC;CAClC,MAAM,aAAa,UAAU,MAAM,aAAa,SAAS,SAASD,kBAAgB;AAClF,KACC,eAAe,KAAA,KACf,CAACE,kBAAgB,WAAW,OAAO,IACnC,WAAW,OAAO,eAAe,KAAA,EAEjC,QAAO,EAAE;AAGV,QAAO,CAAC,eAAe,gCAAgC,gBAAgB,CAAC;;AAGzE,SAAS,oBAAoB,QAAkE;AAC9F,QAAO,eAAe,SAAS,SAAS;AACvC,MAAI,OAAO,KAAK,WAAW,KAAA,EAC1B,QAAO,EAAE;AAGV,SAAO,CAAC,eAAe,qCAAqC,KAAK,SAAS,KAAK,OAAO,CAAC;GACtF;;AAGH,SAAS,oBAAoB,QAAkE;AAC9F,QAAO,OAAO,KAAK,OAAO,CACxB,QAAQ,QAAQ,IAAI,WAAW,uBAAuB,IAAI,OAAO,SAAS,KAAA,EAAU,CACpF,KAAK,QACL,eAAe,qCAAqC,OAAO,uBAAuB,CAClF;;AAGH,SAAS,sBACR,WACkC;CAClC,MAAM,SAAS,UAAU,MAAM,aAAa,SAAS,SAASD,gCAA8B;AAC5F,KAAI,WAAW,KAAA,KAAa,CAACC,kBAAgB,OAAO,OAAO,CAC1D,QAAO,EAAE;AAGV,QAAO,CAAC,GAAG,oBAAoB,OAAO,OAAO,EAAE,GAAG,oBAAoB,OAAO,OAAO,CAAC;;;;AC5FtF,MAAM,aAAa;AACnB,MAAM,2BAA2B;AAOjC,SAAS,oBAAoB,UAAwD;AACpF,KAAI,CAACC,kBAAgB,SAAS,OAAO,CACpC;CAGD,MAAM,EAAE,SAAS,SAAS;AAC1B,QAAO,OAAO,SAAS,WAAW;EAAE,KAAK,SAAS;EAAK;EAAM,GAAG,KAAA;;AAGjE,SAAS,aAAa,UAAmC;AACxD,KAAI,SAAS,SAAS,cAAc,CAACA,kBAAgB,SAAS,OAAO,CACpE,QAAO;AAGR,QAAO,SAAS,OAAO,eAAe;;AAGvC,SAAS,UAAU,WAAiE;AACnF,QAAO,UAAU,OAAO,aAAa,CAAC,KAAK,aAAa,SAAS,IAAI;;AAGtE,SAAS,qBAAqB,OAAuC;AACpE,QAAO;EACN,eAAe,EAAE,aAAa,MAAM,MAAM;EAC1C,UAAU,CACT,oBAAoB;GACnB,aAAa;GACb,YAAY,GAAG,yBAAyB,GAAG,MAAM,IAAI;GACrD,MAAM;GACN,CAAC,CACF;EACD;;AAGF,MAAM,4BAA0C;CAC/C,eAAe,EAAE;CACjB,UAAU,CACT,iBACC,mBACA,gGACA,CACD;CACD;;;;;;;;;;AAWD,SAAgB,gBAAgB,WAAwD;CACvF,MAAM,SAAS,UAAU,UAAU;AACnC,KAAI,OAAO,UAAU,EACpB,QAAO;CAGR,MAAM,CAAC,YAAY;CACnB,MAAM,sBAAsB,UAAU,UAAU,aAAa;AAC5D,SAAO,SAAS,SAAS,4BAA4B,SAAS,QAAQ;GACrE;AACF,KAAI,wBAAwB,KAAA,EAC3B,QAAO;CAGR,MAAM,QAAQ,oBAAoB,oBAAoB;AACtD,QAAO,UAAU,KAAA,IAAY,iBAAiB,qBAAqB,MAAM;;;;AC7E1E,MAAM,uBAAuB;AAE7B,MAAM,iBACL;;;;;;;;;;;;;;;;;;AAmBD,SAAgB,mBAAmB,WAAwD;CAC1F,MAAM,WAAW,UACf,QACC,aAAa,SAAS,SAAS,wBAAwB,mBAAmB,SAAS,CACpF,CACA,KAAK,aACL,eAAe,GAAG,qBAAqB,GAAG,SAAS,OAAO,eAAe,CACzE;AAEF,KAAI,SAAS,WAAW,EACvB,QAAO;AAGR,QAAO;EAAE,eAAe,EAAE;EAAE;EAAU;;AAGvC,SAAS,mBAAmB,UAAmC;AAC9D,KAAI,CAACC,kBAAgB,SAAS,OAAO,CACpC,QAAO;CAGR,MAAM,EAAE,aAAa,SAAS;AAC9B,QAAO,OAAO,aAAa;;;;ACzC5B,MAAM,mBAAmB;AAEzB,MAAM,8BAAyE;CAC9E,cAAc;CACd,gBAAgB;CAChB,cAAc;CACd,cAAc;CACd,aAAa;CACb,eAAe;CACf,kBAAkB;CAClB,eAAe;CACf;;;;;;;;;;AAkBD,SAAgB,gBAAgB,WAAwD;AACvF,QAAO,UACL,QAAQ,aAAa,SAAS,SAAS,iBAAiB,CACxD,QACC,aAAa,aAAa,cAAc,aAAa,cAAc,SAAS,CAAC,EAC9E,eACA;;AAGH,SAAS,cAAc,OAAsC;AAC5D,QAAO;EACN,eAAe,GAAG,MAAM,QAAQ;GAAE,OAAO,MAAM;GAAO,KAAK,MAAM;GAAK,EAAE;EACxE,UAAU,CACT,oBAAoB;GACnB,aAAa,YAAY,MAAM;GAC/B,YAAY,MAAM;GAClB,MAAM;GACN,CAAC,CACF;EACD;;AAGF,SAAS,cAAc,UAAwC;CAC9D,MAAM,aAAa,GAAG,iBAAiB,GAAG,SAAS;CACnD,MAAM,QAAQ,4BAA4B,SAAS;AACnD,KAAI,UAAU,KAAA,EACb,QAAO;EACN,eAAe,EAAE;EACjB,UAAU,CAAC,eAAe,YAAY,8BAA8B,SAAS,MAAM,CAAC;EACpF;AAGF,KAAI,CAACC,kBAAgB,SAAS,OAAO,CACpC,QAAO;CAGR,MAAM,EAAE,OAAO,QAAQ,SAAS;AAChC,KAAI,OAAO,UAAU,YAAY,OAAO,QAAQ,SAC/C,QAAO;AAGR,QAAO,cAAc;EAAE;EAAO;EAAY;EAAO;EAAK,CAAC;;;;AC/DxD,MAAM,kBAAkB;AACxB,MAAM,6BAA6B;AACnC,MAAM,gCAAgC;AACtC,MAAM,qBAAqB;AAE3B,MAAM,0BAEF;CACH,UAAU;CACV,SAAS;CACT,OAAO;CACP,QAAQ;CACR;;;;;;;;;;;;;AA0CD,SAAgB,aACf,WACiC;CACjC,MAAM,aAAa,UAAU,MAAM,aAAa,SAAS,SAAS,gBAAgB;AAClF,KAAI,eAAe,KAAA,EAClB;CAGD,MAAM,UAAU,sBAAsB,WAAW;AACjD,KAAI,YAAY,KAAA,EACf;CAGD,MAAM,YAAY,yBAAyB,UAAU;AAcrD,QAAO;EACN,OAboC,UAAU,QAC7C,aAAa,cAAc;GAAE,GAAG;GAAa,GAAG,SAAS;GAAe,GACzE,EAAE,YAAY,QAAQ,SAAS,CAC/B;EAWA,SATwC,UAAU,QACjD,aAAa,cAAc;GAAE,GAAG;GAAa,GAAG,SAAS;GAAiB,GAC3E,EAAE,aAAa,gBAAgB,QAAQ,aAAa,EAAE,CACtD;EAOA,UALgB,UAAU,SAAS,aAAa,SAAS,SAAS;EAMlE;;AAGF,SAAS,yBACR,WAC8B;AAC9B,QAAO;EACN,oBAAoB,UAAU;EAC9B,mBAAmB,UAAU;EAC7B,cAAc,UAAU;EACxB,yBAAyB,UAAU;EACnC,gBAAgB,UAAU;EAC1B,gBAAgB,UAAU;EAC1B,mBAAmB,UAAU;EAC7B,4BAA4B,UAAU;EACtC;;AAGF,SAAS,eAAe,OAAoC;AAC3D,KAAI,OAAO,UAAU,SACpB,QAAO;AAGR,KAAI,OAAO,UAAU,MAAM,CAC1B,QAAO,OAAO,MAAM;;AAMtB,SAAS,sBAAsB,UAAyD;CACvF,MAAM,MAAM,SAAS;AACrB,KAAI,CAACC,kBAAgB,IAAI,CACxB;CAGD,MAAM,UAAU,eAAe,IAAI,WAAW;CAC9C,MAAM,eAAe,eAAe,IAAI,gBAAgB;AACxD,KAAI,YAAY,KAAA,KAAa,iBAAiB,KAAA,EAC7C;AAGD,QAAO;EAAE;EAAS;EAAc;;AAGjC,MAAM,wBAAwB;AAE9B,SAAS,kBAAkB,KAA4B;CACtD,MAAM,OAAO,OAAO,QAAQ,WAAW,wBAAwB,OAAO,KAAA;AACtE,KAAI,SAAS,KAAA,EACZ,QAAO;EACN,eAAe,EAAE;EACjB,UAAU,CACT,eACC,uBACA,kCAAkC,OAAO,IAAI,GAC7C,CACD;EACD;AAGF,QAAO;EACN,eAAe,GAAG,OAAO,MAAM;EAC/B,UAAU,CACT,oBAAoB;GACnB,aAAa,YAAY;GACzB,YAAY;GACZ,MAAM;GACN,CAAC,CACF;EACD;;AAGF,SAAS,oBAAoB,WAAwD;CACpF,MAAM,SAAS,UAAU,MAAM,aAAa,SAAS,SAAS,8BAA8B;AAC5F,KAAI,WAAW,KAAA,KAAa,CAACA,kBAAgB,OAAO,OAAO,CAC1D,QAAO;CAGR,MAAM,EAAE,oBAAoB,OAAO;AACnC,KAAI,CAAC,MAAM,QAAQ,gBAAgB,CAClC,QAAO;AAGR,QAAO,gBAAgB,QACrB,aAAa,QAAQ,cAAc,aAAa,kBAAkB,IAAI,CAAC,EACxE,eACA;;AAGF,MAAM,2BAAyC;CAC9C,eAAe,EAAE;CACjB,UAAU,CACT,oBAAoB;EACnB,aAAa;EACb,YAAY;EACZ,MAAM;EACN,CAAC,CACF;CACD;AAED,SAAS,6BAA6B,OAA6B;AAClE,QAAO;EACN,eAAe,EAAE,yBAAyB,OAAO;EACjD,UAAU,CACT,oBAAoB;GACnB,aAAa;GACb,YAAY;GACZ,MAAM;GACN,CAAC,CACF;EACD;;AAGF,MAAM,gCAA8C;CACnD,eAAe,EAAE;CACjB,UAAU,CACT,eACC,2CACA,wCACA,CACD;CACD;AAED,SAAS,aAAa,WAA+D;CACpF,MAAM,aAAa,UAAU,MAAM,aAAa,SAAS,SAAS,2BAA2B;AAC7F,KAAI,eAAe,KAAA,KAAa,CAACA,kBAAgB,WAAW,OAAO,CAClE;CAGD,MAAM,EAAE,aAAa,WAAW;AAChC,QAAO,OAAO,aAAa,YAAY,WAAW,KAAA;;AAGnD,SAAS,yBAAyB,WAAwD;AACzF,KAAI,aAAa,UAAU,KAAK,KAAA,EAC/B,QAAO;AAGR,QAAO;;AAGR,SAAS,cAAc,WAAwD;CAC9E,MAAM,QAAQ,UAAU,MAAM,aAAa,SAAS,SAAS,mBAAmB;AAChF,KAAI,UAAU,KAAA,KAAa,CAACA,kBAAgB,MAAM,OAAO,CACxD,QAAO;CAGR,MAAM,EAAE,YAAY,MAAM;AAC1B,KAAI,OAAO,YAAY,UACtB,QAAO;AAGR,QAAO;EACN,eAAe,EAAE,kBAAkB,SAAS;EAC5C,UAAU,CACT,oBAAoB;GACnB,aAAa;GACb,YAAY;GACZ,MAAM;GACN,CAAC,CACF;EACD;;AAGF,SAAS,mBAAmB,WAAwD;CACnF,MAAM,SAAS,UAAU,MAAM,aAAa,SAAS,SAAS,8BAA8B;AAC5F,KAAI,WAAW,KAAA,KAAa,CAACA,kBAAgB,OAAO,OAAO,CAC1D,QAAO;CAGR,MAAM,EAAE,qBAAqB,uBAAuB,OAAO;AAC3D,KAAI,wBAAwB,MAC3B,QAAO;AAGR,KAAI,wBAAwB,QAAQ,OAAO,uBAAuB,SACjE,QAAO;AAGR,QAAO,6BAA6B,mBAAmB;;;;;;;;;;AClRxD,MAAM,wBAA0D;CAC/D,YAAY;CACZ,YAAY;CACZ,OAAO;CACP,WAAW;CACX,qBAAqB;CACrB,0BAA0B;CAC1B,YAAY;CACZ,cAAc;CACd;;;;;;;;;;AAWD,SAAgB,gBACf,WACkC;AAClC,QAAO,UAAU,SAAS,aAA8C;EACvE,MAAM,YAAY,sBAAsB,SAAS;AACjD,MAAI,cAAc,KAAA,EACjB,QAAO,EAAE;AAGV,SAAO,CACN;GACC,MAAM;GACN,YAAY,GAAG,SAAS,KAAK,GAAG,SAAS;GACzC,QAAQ,GAAG,UAAU;GACrB,CACD;GACA;;;;;;;;;;;;;ACGH,SAAgB,gBAAgB,WAAiE;CAChG,MAAM,iBAAiB,aAAa,UAAU;CAC9C,MAAM,WACL,mBAAmB,KAAA,IAChB,KAAA,IACA;EAAE,OAAO,eAAe;EAAO,SAAS,eAAe;EAAS;CAEpE,MAAM,eAAe,WAAW,UAAU;CAC1C,MAAM,eAAe,WAAW,UAAU;CAC1C,MAAM,iBAAiB,aAAa,UAAU;CAE9C,MAAM,WAAW;EAChB,GAAI,gBAAgB,YAAY,EAAE;EAClC,GAAG,aAAa;EAChB,GAAG,aAAa;EAChB,GAAG,eAAe;EAClB,GAAG,gBAAgB,UAAU;EAC7B;AAED,QAAO;EACN,QAAQ,aAAa;EACrB,QAAQ,aAAa;EACrB,UAAU,eAAe;EACzB;EACA;EACA;;;;AClEF,MAAM,qBAAqB,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;;AAwBhC,SAAgB,WAAW,KAAa,YAAyD;CAChG,IAAI;AACJ,KAAI;AAEH,aAAW,aADI,UAAU,IAAI,CACE;UACvB,KAAK;AACb,SAAO,gBAAgB,KAAK,WAAW;;CAGxC,MAAM,gBAAgB,aAAa,SAAS,WAAW;AACvD,KAAI,CAAC,cAAc,QAClB,QAAO;CAGR,IAAI;AACJ,KAAI;AACH,iBAAe,kBAAkB,SAAS,gBAAgB;UAClD,KAAK;AACb,SAAO,gBAAgB,KAAK,WAAW;;AAGxC,QAAO;EACN,MAAM;GAAE;GAAc,SAAS,cAAc;GAAM;EACnD,SAAS;EACT;;AAGF,SAAS,aAAa,KAAyC;CAC9D,MAAM,QAAQ,OAAO,QAAQ,WAAW,MAAM,OAAO,IAAI;AACzD,KAAI,UAAU,IACb,QAAO;EACN,KAAK;GACJ;GACA,MAAM;GACN,WAAW;GACX;EACD,SAAS;EACT;AAGF,QAAO;EAAE,MAAM;EAAK,SAAS;EAAM;;AAGpC,SAAS,gBAAgB,OAAkD;AAC1E,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;AAG5E,SAAS,SAAS,OAAwB;AACzC,KAAI,UAAU,KACb,QAAO;AAGR,KAAI,MAAM,QAAQ,MAAM,CACvB,QAAO;AAGR,QAAO,OAAO;;AAGf,SAAS,aAAa,OAAyC;AAC9D,KAAI,CAAC,gBAAgB,MAAM,CAC1B,OAAM,IAAI,UAAU,wBAAwB,SAAS,MAAM,GAAG;AAG/D,QAAO;;AAGR,SAAS,YAAY,OAAwC;AAC5D,KAAI,CAAC,MAAM,QAAQ,MAAM,CACxB,OAAM,IAAI,UAAU,uBAAuB,SAAS,MAAM,GAAG;AAG9D,QAAO;;AAGR,SAAS,aAAa,OAAgB,OAAuB;AAC5D,KAAI,OAAO,UAAU,SACpB,OAAM,IAAI,UAAU,YAAY,MAAM,uBAAuB,SAAS,MAAM,GAAG;AAGhF,QAAO;;AAGR,SAAS,WAAW,OAAyB;AAC5C,KAAI,UAAU,KACb;AAGD,KAAI,MAAM,QAAQ,MAAM,CACvB,QAAO,MAAM,IAAI,WAAW;AAG7B,KAAI,gBAAgB,MAAM,CACzB,QAAO,OAAO,YACb,OAAO,QAAQ,MAAM,CAAC,KAAK,CAAC,KAAK,WAAW,CAAC,KAAK,WAAW,MAAM,CAAC,CAAC,CACrE;AAGF,QAAO;;AAGR,SAAS,cAAc,OAAgC;CACtD,MAAM,WAAW,aAAa,MAAM;CACpC,MAAM,KAAK,aAAa,SAAS,OAAO,KAAK;CAC7C,MAAM,kBAAkB,GAAG,QAAQ,IAAI;AACvC,KAAI,mBAAmB,KAAK,oBAAoB,GAAG,SAAS,EAC3D,OAAM,IAAI,MAAM,0CAA0C,GAAG,GAAG;CAGjE,MAAM,OAAO,GAAG,MAAM,GAAG,gBAAgB;CACzC,MAAM,MAAM,GAAG,MAAM,kBAAkB,EAAE;CAGzC,MAAM,SAAS,WADM,aAAa,SAAS,UAAU,CACd,MAAM;CAE7C,MAAM,aAAa,SAAS;CAC5B,MAAM,UAAU,gBAAgB,WAAW,GAAG,WAAW,WAAW,MAAM,GAAG,KAAA;AAM7E,QAAO;EAAE;EAAK,cAJO,YAAY,SAAS,gBAAgB,CAAC,KAAK,QAAQ;AACvE,UAAO,aAAa,KAAK,aAAa;IACrC;EAE0B;EAAQ;EAAM;EAAS;;AAGpD,SAAS,kBAAkB,KAAuE;CACjG,MAAM,qBAAqB,aAAa,IAAI;AAC5C,QAAO,OAAO,YACb,OAAO,QAAQ,mBAAmB,CAAC,KAAK,CAAC,MAAM,WAAW,CACzD,MACA,YAAY,MAAM,CAAC,IAAI,cAAc,CACrC,CAAC,CACF;;AAGF,SAAS,gBAAgB,KAAc,YAAyD;AAE/F,QAAO;EACN,KAAK;GAAE,MAAM;GAAoB,MAAM;GAAY,QAFrC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GAEH;EAC3D,SAAS;EACT;;;;ACvKF,MAAM,qBAAqB;;;;;;;;;;;;;;;AA4B3B,SAAgB,gBAAgB,SAAyC;AACxE,KAAI,QAAQ,iBAAiB,OAC5B,QAAO,GAAG,cAAc,QAAQ,OAAO,CAAC;AAIzC,QAAO;EACN;EACA;EACA,+BAJY,oBAAoB,QAAQ,QAAQ,EAAE,CAId;EACpC;EACA,CAAC,KAAK,KAAK;;AAGb,SAAS,YAAY,OAAgB,OAAuB;AAC3D,KAAI,MAAM,QAAQ,MAAM,CACvB,QAAO,mBAAmB,OAAO,MAAM;AAGxC,KAAI,UAAU,QAAQ,OAAO,UAAU,SACtC,QAAO,oBAAoB,OAAO,MAAM;AAGzC,KAAI,OAAO,UAAU,SACpB,QAAO,KAAK,UAAU,MAAM;AAG7B,QAAO,OAAO,MAAM;;AAGrB,SAAS,oBAAoB,OAAe,OAAuB;CAClE,MAAM,UAAU,OAAO,QAAQ,MAAM,CAAC,QAAQ,GAAG,gBAAgB,eAAe,KAAA,EAAU;AAC1F,KAAI,QAAQ,WAAW,EACtB,QAAO;CAGR,MAAM,cAAc,IAAK,OAAO,QAAQ,EAAE;CAC1C,MAAM,cAAc,IAAK,OAAO,MAAM;AAKtC,QAAO,MAJO,QAAQ,KAAK,CAAC,KAAK,gBAAgB;AAEhD,SAAO,GAAG,cADU,mBAAmB,KAAK,IAAI,GAAG,MAAM,KAAK,UAAU,IAAI,CACxC,IAAI,YAAY,YAAY,QAAQ,EAAE;GACzE,CACiB,KAAK,MAAM,CAAC,IAAI,YAAY;;AAGhD,SAAS,mBAAmB,OAA+B,OAAuB;AACjF,KAAI,MAAM,WAAW,EACpB,QAAO;CAGR,MAAM,cAAc,IAAK,OAAO,QAAQ,EAAE;CAC1C,MAAM,cAAc,IAAK,OAAO,MAAM;AAEtC,QAAO,MADO,MAAM,KAAK,SAAS,GAAG,cAAc,YAAY,MAAM,QAAQ,EAAE,GAAG,CAC/D,KAAK,MAAM,CAAC,IAAI,YAAY;;;;ACnFhD,MAAM,eAAiC;CACtC,gBAAgB;CAChB,cAAc;CACd,eAAe;CACf,mBAAmB;CACnB;;;;;;;;;;;;AAaD,SAAgB,kBAAkB,UAA6D;AAC9F,QAAO,SAAS,QAA0B,aAAa,YAAY;AAClE,SAAO;GACN,GAAG;IACF,GAAG,QAAQ,KAAK,SAAS,YAAY,GAAG,QAAQ,KAAK,UAAU;GAChE;IACC,aAAa;;;;;;;;;;;;;;;;;ACqEjB,eAAsB,oBACrB,QACiC;AAajC,QAAO,qBAZQ,MAAM,QAAQ,IAC5B,CAAC,GAAG,OAAO,MAAM,SAAS,CAAC,CAAC,IAAI,OAAO,CAAC,aAAa,YAAY;AAOhE,SAAO,CAAC,aANO,MAAM,gBAAgB;GACpC,iBAAiB;GACjB;GACA,UAAU,OAAO;GACjB,oBAAoB,OAAO;GAC3B,CAAC,CAC0B;GAC3B,CACF,CAEkC;;AAGpC,SAAS,qBACR,QACwB;AACxB,QAAO;EACN,yBAAyB,IAAI,IAC5B,OAAO,KAAK,CAAC,aAAa,UAAU,CAAC,aAAa,KAAK,WAAW,CAAC,CACnE;EACD,4BAA4B,IAAI,IAC/B,OAAO,KAAK,CAAC,aAAa,UAAU,CAAC,aAAa,KAAK,cAAc,CAAC,CACtE;EACD,UAAU,OAAO,SAAS,GAAG,UAAU,KAAK,SAAS;EACrD;;AAGF,SAAS,cAAc,OAAqC;AAC3D,QAAO;EACN,KAAK,MAAM;EACX,UAAU,MAAM,MAAM,KAAK;EAC3B,YAAY,MAAM;EAClB;;AAGF,SAAS,mBAAmB,OAAuD;AAClF,KAAI,MAAM,MAAM,SAAS,KAAA,EACxB,QAAO,EAAE;AAGV,QAAO,CACN;EACC,KAAK,MAAM;EACX,UAAU,MAAM,MAAM,KAAK;EAC3B,YAAY,MAAM;EAClB,CACD;;AAGF,eAAe,iBACd,UACA,MACiC;AACjC,KAAI;AAEH,SAAO,YAAY,MAAM,UADX,MAAM,SAAS,KAAK,CACO,CAAC;SACnC;AACP;;;AAIF,SAAS,0BAA0B,QAAsD;AACxF,QAAO;EACN,MAAM,+BAA+B,OAAO,aAAa;EACzD,MAAM;EACN,YAAY,GAAG,OAAO,gBAAgB,GAAG,OAAO;EAChD;;AAGF,eAAe,gBAAgB,QAAiD;CAC/E,MAAM,yBAAS,IAAI,KAA8C;CACjE,MAAM,WAAoC,EAAE;AAC5C,MAAK,MAAM,SAAS,OAAO,SAAS;EACnC,MAAM,WAAW,KAAK,OAAO,oBAAoB,MAAM,SAAS;EAChE,MAAM,aAAa,MAAM,iBAAiB,OAAO,UAAU,SAAS;AACpE,MAAI,eAAe,KAAA,EAClB,UAAS,KACR,0BAA0B;GACzB,iBAAiB,OAAO;GACxB,YAAY,MAAM;GAClB,cAAc;GACd,CAAC,CACF;MAED,QAAO,IAAI,MAAM,KAAK,EAAE,SAAS,YAAY,CAAC;;AAIhD,QAAO;EAAE;EAAQ;EAAU;;AAG5B,eAAe,gBAAgB,QAA+D;CAC7F,MAAM,WAAW,MAAM,gBAAgB;EACtC,SAAS,OAAO,OAAO,OAAO,IAAI,cAAc;EAChD,iBAAiB,OAAO;EACxB,UAAU,OAAO;EACjB,oBAAoB,OAAO;EAC3B,CAAC;CACF,MAAM,cAAc,MAAM,gBAAgB;EACzC,SAAS,OAAO,OAAO,SAAS,QAAQ,mBAAmB;EAC3D,iBAAiB,OAAO;EACxB,UAAU,OAAO;EACjB,oBAAoB,OAAO;EAC3B,CAAC;AACF,QAAO;EACN,YAAY,SAAS;EACrB,eAAe,YAAY;EAC3B,UAAU,CAAC,GAAG,SAAS,UAAU,GAAG,YAAY,SAAS;EACzD;;;;ACxLF,MAAM,qBAAqB,IAAI,IAAI,CAAC,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8G9C,eAAsB,mBACrB,MACiD;CACjD,MAAMC,aAAW,KAAK,YAAYC;CAElC,IAAI;AACJ,KAAI;AACH,UAAQ,MAAMD,WAAS,KAAK,cAAc;UAClC,KAAK;AACb,MAAI,cAAc,IAAI,CACrB,QAAO;GACN,KAAK;IAAE,MAAM;IAAqB,MAAM,KAAK;IAAe;GAC5D,SAAS;GACT;AAGF,QAAM;;CAIP,MAAM,SAAS,WADH,IAAI,YAAY,QAAQ,CAAC,OAAO,MAAM,EACnB,KAAK,cAAc;AAClD,KAAI,CAAC,OAAO,QACX,QAAO;AAGR,QAAO,eAAe;EACrB,cAAc,KAAK;EACnB,oBAAoB,KAAK;EACzB,UAAA;EACA,OAAO,OAAO;EACd,eAAe,KAAK;EACpB,CAAC;;AAGH,MAAM,+BAAqE,IAAI,KAAK;AAcpF,SAAS,yBACR,QACyC;AACzC,QAAO,OAAO,YACb,CAAC,GAAG,OAAO,MAAM,SAAS,CAAC,CAAC,KAAK,CAAC,MAAM,YAAoC;AAC3E,SAAO,CACN,MACA,WAAW;GACV,aAAa;GACb;GACA,qBAAqB,OAAO,wBAAwB,IAAI,KAAK,IAAI;GACjE,wBACC,OAAO,2BAA2B,IAAI,KAAK,IAAI;GAChD,CAAC,CACF;GACA,CACF;;AAGF,SAAS,iBAAiB,SAA2B,iBAA2C;AAC/F,QAAO;EAAE,GAAG;EAAS,YAAY,GAAG,gBAAgB,GAAG,QAAQ;EAAc;;AAG9E,SAAS,oBACR,OACkC;AAClC,QAAO,CAAC,GAAG,MAAM,SAAS,CAAC,CAAC,SAAS,CAAC,MAAM,UAAU;AACrD,SAAO,KAAK,SAAS,KAAK,YAAY,iBAAiB,SAAS,KAAK,CAAC;GACrE;;AAGH,SAAS,YAAY,QAA8B,WAAoC;CACtF,MAAM,EACL,yBACA,4BACA,UAAU,iBACP,OAAO;CACX,MAAM,WAAW;EAChB,GAAG,oBAAoB,OAAO,MAAM;EACpC,GAAG,OAAO;EACV,GAAG;EACH;AACD,QAAO;EACN,QAAQ;EACR,mBAAmB,gBAAgB;GAClC,QAAQ;GACR,cAAc,OAAO;GACrB,CAAC;EACF,qBAAqB,yBAAyB;GAC7C,OAAO,OAAO;GACd;GACA;GACA,CAAC;EACF,SAAS,kBAAkB,SAAS;EACpC;EACA;;AAGF,SAAS,eAAe,QAAqE;CAC5F,MAAM,YAAY,eAAe,OAAO,QAAQ,yBAAyB;AACzE,KAAI,CAAC,UAAU,QACd,QAAO;EACN,KAAK;GACJ,OAAO,UAAU;GACjB,MAAM;GACN,QAAQ;GACR;EACD,SAAS;EACT;AAGF,QAAO;EAAE,MAAM,YAAY,QAAQ,UAAU,KAAK;EAAE,SAAS;EAAM;;AAGpE,eAAe,eACd,QACiD;CACjD,MAAM,YAAY,OAAO,KAAK,OAAO,MAAM,aAAa;CACxD,MAAM,QAAoD,IAAI,IAC7D,UAAU,KAAK,SAAS,CAAC,MAAM,gBAAgB,OAAO,MAAM,aAAa,SAAS,EAAE,CAAC,CAAC,CAAC,CACvF;CACD,MAAM,aAAa,sBAAsB;EACxC;EACA,oBAAoB,OAAO;EAC3B,CAAC;AACF,KAAI,CAAC,WAAW,QACf,QAAO;CAGR,MAAM,oBAAoB,MAAM,oBAAoB;EACnD;EACA,UAAU,OAAO;EACjB,oBAAoB,QAAQ,OAAO,cAAc;EACjD,CAAC;AACF,QAAO,eAAe;EACrB,QAAQ,WAAW,KAAK;EACxB,cAAc,OAAO;EACrB,mBAAmB,WAAW,KAAK;EACnC;EACA;EACA,CAAC;;AAGH,SAAS,cAAc,KAAuB;AAC7C,QACC,OAAO,QAAQ,YACf,QAAQ,QACR,UAAU,OACV,OAAO,IAAI,SAAS,YACpB,mBAAmB,IAAI,IAAI,KAAK"}
1
+ {"version":3,"file":"migrate-mantle-state-F4zdhxV4.mjs","names":["OPTIONAL_BOOLEAN","socialLinkOrUndefined","toCurrentState","toCurrentState","OPTIONAL_BOOLEAN","entrySchema","flatten","normalize","changedFieldsBetween","fieldsEqual","entrySchema","flatten","normalize","changedFieldsBetween","fieldsEqual","entrySchema","flatten","normalize","changedFieldsBetween","fieldsEqual","compositeKey","c12LoadConfig","loadConfig","resolvePath","defaultLoadConfig","readFile","nodeReadFile","buildEntry","isObjectPayload","readString","readPrice","coerceRobloxId","isObjectPayload","coerceRobloxId","PLACE_CONFIGURATION_KIND","BLOCKED_FIELDS","isObjectPayload","PLACE_KIND","PLACE_CONFIGURATION_KIND","bucketByKind","isStartPlace","readString","isObjectPayload","coerceRobloxId","isObjectPayload","coerceRobloxId","EXPERIENCE_KIND","EXPERIENCE_CONFIGURATION_KIND","isObjectPayload","isObjectPayload","isObjectPayload","isObjectPayload","isObjectPayload","readFile","nodeReadFile"],"sources":["../src/cli/clack-port.ts","../src/cli/render.ts","../src/core/resolve-state-config.ts","../src/types/ids.ts","../src/core/environment.ts","../src/core/kinds/hash.ts","../src/core/redacted-icon.ts","../src/core/kinds/read-bytes.ts","../src/core/icons.ts","../src/core/validate-universe-xor.ts","../src/core/schema.ts","../src/adapters/clack-progress-adapter.ts","../src/core/derive-price-fields.ts","../src/core/plan-follow-up-patch.ts","../src/adapters/developer-product-driver.ts","../src/adapters/game-pass-driver.ts","../src/core/state-file.ts","../src/adapters/gist-state-adapter.ts","../src/adapters/no-op-progress-adapter.ts","../src/core/resources.ts","../src/adapters/place-driver.ts","../src/adapters/universe-driver.ts","../src/cli/default-spawner.ts","../src/cli/credential-environment-overrides.ts","../src/cli/dispatch-override.ts","../src/core/kinds/developer-product.ts","../src/core/kinds/game-pass.ts","../src/core/kinds/place.ts","../src/core/kinds/universe.ts","../src/core/kinds/index.ts","../src/core/diff.ts","../src/core/display-name-prefix.ts","../src/core/flatten.ts","../src/core/redact-resources.ts","../src/core/select-environment.ts","../src/shell/apply-ops.ts","../src/shell/build-default-registry.ts","../src/shell/build-desired.ts","../src/shell/build-state-port.ts","../src/core/assert-all-reconcilable.ts","../src/shell/load-config-internal.ts","../src/adapters/lute-luau-evaluator.ts","../src/shell/load-config.ts","../src/shell/deploy.ts","../src/core/migrate/build-state.ts","../src/core/migrate/extract-display-name-prefix.ts","../src/core/migrate/environment-label.ts","../src/core/migrate/factorize-environments-warnings.ts","../src/core/migrate/factorize-places.ts","../src/core/migrate/factorize-products.ts","../src/core/migrate/factorize-universe.ts","../src/core/migrate/factorize-environments.ts","../src/core/migrate/fold-passes.ts","../src/core/migrate/fold-universe-shared.ts","../src/core/migrate/fold-blocked-place-fields.ts","../src/core/migrate/fold-places.ts","../src/core/migrate/fold-products.ts","../src/core/migrate/fold-blocked-experience-fields.ts","../src/core/migrate/fold-display-name.ts","../src/core/migrate/fold-experience-icon.ts","../src/core/migrate/fold-social-links.ts","../src/core/migrate/fold-universe.ts","../src/core/migrate/fold-unsupported.ts","../src/core/migrate/fold-environment.ts","../src/core/migrate/parse-state.ts","../src/core/migrate/serialize-config.ts","../src/core/migrate/summarize-warnings.ts","../src/shell/recompute-icon-hashes.ts","../src/shell/migrate-mantle-state.ts"],"sourcesContent":["import { cancel, intro, log, outro } from \"@clack/prompts\";\n\nimport type { ClackPort } from \"./render.ts\";\n\n/**\n * Construct a `ClackPort` whose methods delegate to `@clack/prompts`. The\n * resulting port writes to `process.stdout` via clack's defaults. Kept in\n * its own module so consumers that never need the clack-backed rendering\n * (programmatic deploys, custom adapters) do not pull `@clack/prompts`\n * into their bundle.\n *\n * @example\n *\n * ```ts\n * import { createClackPort } from \"@bedrock-rbx/core\";\n *\n * const port = createClackPort();\n *\n * expect(typeof port.logSuccess).toBe(\"function\");\n * ```\n *\n * @returns A port whose six methods each invoke the matching clack helper.\n */\nexport function createClackPort(): ClackPort {\n\treturn {\n\t\tcancel: (message) => {\n\t\t\tcancel(message);\n\t\t},\n\t\tintro: (message) => {\n\t\t\tintro(message);\n\t\t},\n\t\tlogError: (message) => {\n\t\t\tlog.error(message);\n\t\t},\n\t\tlogMessage: (message) => {\n\t\t\tlog.message(message);\n\t\t},\n\t\tlogSuccess: (message) => {\n\t\t\tlog.success(message);\n\t\t},\n\t\toutro: (message) => {\n\t\t\toutro(message);\n\t\t},\n\t};\n}\n","import { PermissionError } from \"@bedrock-rbx/ocale\";\n\nimport type { ConfigError } from \"../core/config-error.ts\";\nimport type { MigrateError, MigrationSummary } from \"../core/migrate/migration-report.ts\";\nimport type { StateError } from \"../core/state.ts\";\nimport type { ApplyError } from \"../shell/apply-ops.ts\";\nimport type { BuildDesiredError } from \"../shell/build-desired.ts\";\nimport type { MissingCredentialError, UnsupportedBackendError } from \"../shell/build-state-port.ts\";\nimport type { DeployError } from \"../shell/deploy.ts\";\nimport type { SpawnOverrideError } from \"./dispatch-override.ts\";\nimport type { ParseMigrateError } from \"./parse-migrate-options.ts\";\nimport type { ParseOptionsError } from \"./parse-options.ts\";\n\n/**\n * Output port the CLI renders through. Mirrors the subset of `@clack/prompts`\n * the bedrock CLI uses today; tests inject a fake to assert what was rendered.\n *\n * @example\n *\n * ```ts\n * import type { ClackPort } from \"@bedrock-rbx/core\";\n *\n * const lines: Array<string> = [];\n * const port: ClackPort = {\n * cancel: (message) => lines.push(`cancel: ${message}`),\n * intro: (message) => lines.push(`intro: ${message}`),\n * logError: (message) => lines.push(`error: ${message}`),\n * logMessage: (message) => lines.push(`log: ${message}`),\n * logSuccess: (message) => lines.push(`ok: ${message}`),\n * outro: (message) => lines.push(`outro: ${message}`),\n * };\n *\n * port.logSuccess(\"done\");\n *\n * expect(lines).toEqual([\"ok: done\"]);\n * ```\n */\nexport interface ClackPort {\n\t/** End an interactive flow with a cancellation marker. */\n\tcancel(message: string): void;\n\t/** Open a framed section with a title (used for command intros). */\n\tintro(message: string): void;\n\t/** Render a single error line inside an open frame. */\n\tlogError(message: string): void;\n\t/** Render a single neutral line inside an open frame. */\n\tlogMessage(message: string): void;\n\t/** Render a single success line inside an open frame. */\n\tlogSuccess(message: string): void;\n\t/** Close the current framed section with a final message. */\n\toutro(message: string): void;\n}\n\n/** Inputs for {@link renderStateWriteError}. */\ninterface StateWriteErrorRender {\n\t/** Environment whose state could not be written. */\n\treadonly environment: string;\n\t/** The state-error returned by the adapter. */\n\treadonly err: StateError;\n}\n\n/** Inputs for {@link renderMigrationSummary}. */\ninterface MigrationSummaryRender {\n\t/** Path to the Markdown report on disk. Pointed at from the action-required and review-needed lines. */\n\treadonly reportPath: string;\n\t/** Aggregate counts from a `MigrationReport`. */\n\treadonly summary: MigrationSummary;\n}\n\n/** Inputs for {@link renderOverrideError}. */\ninterface OverrideErrorRender {\n\t/** Environment whose override spawn produced the error. */\n\treadonly environment: string;\n\t/** The spawn-override error returned by `dispatchOverride`. */\n\treadonly err: SpawnOverrideError;\n}\n\n/**\n * Render a `DeployError` to the supplied `ClackPort`. Most variants emit a\n * single error line; `applyFailed` emits one line per failing op in the\n * aggregate (in Phase 1 then Phase 2 input order). Wrapped variants\n * (`applyFailed`, `buildDesiredFailed`, `configLoadFailed`,\n * `stateReadFailed`, `stateWriteFailed`) surface the inner cause's\n * actionable detail (file path, resource key, parser message, HTTP failure,\n * validator issue) so the reader does not have to inspect the full cause to\n * act.\n * @param err - The deploy error to describe.\n * @param port - The output port the diagnostic is written to.\n */\nexport function renderDeployError(err: DeployError, port: ClackPort): void {\n\tif (err.kind === \"applyFailed\") {\n\t\tfor (const failure of err.cause.failures) {\n\t\t\tport.logError(`apply failed for '${failure.key}': ${applyCauseDetail(failure)}`);\n\t\t}\n\n\t\treturn;\n\t}\n\n\tport.logError(deployErrorMessage(err));\n}\n\n/**\n * Render a `ParseOptionsError` to the supplied `ClackPort` as a single\n * error line. Each variant names the offending flag so the diagnostic\n * pinpoints what the caller needs to change.\n * @param err - The parse error to describe.\n * @param port - The output port the diagnostic is written to.\n */\nexport function renderParseError(err: ParseOptionsError, port: ClackPort): void {\n\tport.logError(parseErrorMessage(err));\n}\n\n/**\n * Render a `SpawnOverrideError` to the supplied `ClackPort` as a single\n * error line that names the environment alongside the failure mode. On\n * `launchFailed` the child never produced output of its own, so the parent\n * carries the diagnostic; on `nonZeroExit` the parent's line attributes the\n * exit code to a specific environment when several spawns are running.\n * @param input - Environment + spawn-override error to describe.\n * @param port - The output port the diagnostic is written to.\n */\nexport function renderOverrideError(input: OverrideErrorRender, port: ClackPort): void {\n\tport.logError(overrideErrorMessage(input));\n}\n\n/**\n * Render the failure surfaced when override discovery throws a non-absence\n * filesystem error (for example `EACCES` on a `.bedrock/<command>.ts` that\n * exists but cannot be read). Discovery refuses to fall through to the\n * built-in path in that case, so the CLI reports the cause and exits rather\n * than crashing on the unhandled throw.\n * @param error - The value thrown during override discovery.\n * @param port - The output port the diagnostic is written to.\n */\nexport function renderOverrideDiscoveryError(error: unknown, port: ClackPort): void {\n\tport.logError(`override discovery failed: ${safeStringify(error)}`);\n}\n\n/**\n * Render a `ParseMigrateError` to the supplied `ClackPort`. Reuses\n * `parseErrorMessage` for the three flag-shape variants and adds a\n * dedicated message for `unknownSource` listing the supported sources.\n * @param err - The parse error to describe.\n * @param port - The output port the diagnostic is written to.\n */\nexport function renderMigrateParseError(err: ParseMigrateError, port: ClackPort): void {\n\tport.logError(migrateParseErrorMessage(err));\n}\n\n/**\n * Render a `MigrateError` to the supplied `ClackPort` as a single error\n * line. Each variant points at the offending Mantle state file path,\n * primary-environment input, or wrapped `ConfigError` so the reader can\n * act without inspecting the raw error object.\n * @param err - The migrate error to describe.\n * @param port - The output port the diagnostic is written to.\n */\nexport function renderMigrateError(err: MigrateError, port: ClackPort): void {\n\tport.logError(migrateErrorMessage(err));\n}\n\n/**\n * Render a `MissingCredentialError` or `UnsupportedBackendError`\n * surfaced when the migrate command tried to default-construct the\n * configured `StatePort` and was missing its inputs.\n * @param err - The error returned by `buildStatePort`.\n * @param port - The output port the diagnostic is written to.\n */\nexport function renderBuildStatePortError(\n\terr: MissingCredentialError | UnsupportedBackendError,\n\tport: ClackPort,\n): void {\n\tport.logError(buildStatePortErrorMessage(err));\n}\n\n/**\n * Render the post-migrate review prompt to the supplied `ClackPort`.\n * Three outcomes:\n *\n * - Any `ambiguous` warnings exist: emit a single error line directing\n * the user to the report. The migration ran but there are decisions\n * the user still needs to make before deploy will be meaningful.\n * - No `ambiguous` warnings but non-zero `blocked` / `deferred` /\n * `interpretive`: emit a single success line pointing at the report\n * for auditing.\n * - All counts zero: silent. The closing `outro(\"migrate succeeded\")`\n * already speaks for the run.\n * @param input - Counts plus the path of the Markdown report.\n * @param port - The output port the line is written to.\n */\nexport function renderMigrationSummary(input: MigrationSummaryRender, port: ClackPort): void {\n\tconst { ambiguousCount, blockedCount, deferredCount, interpretiveCount } = input.summary;\n\tif (ambiguousCount > 0) {\n\t\tport.logError(\n\t\t\t`action required: ${String(ambiguousCount)} fields need your input. See ${input.reportPath}`,\n\t\t);\n\t\treturn;\n\t}\n\n\tconst reviewable = blockedCount + deferredCount + interpretiveCount;\n\tif (reviewable > 0) {\n\t\tport.logSuccess(\n\t\t\t`migration complete; see ${input.reportPath} for ${String(reviewable)} auto-mapped or skipped fields`,\n\t\t);\n\t}\n}\n\n/**\n * Render a `StateError` produced when the migrator wrote a per-environment\n * state through the `StatePort`. Names the environment alongside the\n * adapter's failure reason so the reader knows which write failed.\n * @param input - Environment + state-error to describe.\n * @param port - The output port the diagnostic is written to.\n */\nexport function renderStateWriteError(input: StateWriteErrorRender, port: ClackPort): void {\n\tport.logError(\n\t\t`state write failed for '${input.environment}' (${input.err.file}): ${input.err.reason}`,\n\t);\n}\n\nfunction permissionDetail(err: PermissionError): string {\n\tconst isPlural = err.requiredScopes.length > 1;\n\tconst label = isPlural ? \"scopes\" : \"scope\";\n\tconst pronoun = isPlural ? \"them\" : \"it\";\n\tconst scopeList = err.requiredScopes.map((scope) => `'${scope}'`).join(\", \");\n\treturn `${err.message} on ${err.operationKey}: missing required ${label} ${scopeList}. Grant ${pronoun} on the API key at https://create.roblox.com/credentials`;\n}\n\nfunction safeStringify(value: unknown): string {\n\tif (value instanceof Error) {\n\t\treturn value.message;\n\t}\n\n\t// `String(value)` can throw on null-prototype objects or values whose\n\t// `toString` / `Symbol.toPrimitive` rejects coercion; fall back so the\n\t// renderer never crashes mid-diagnostic.\n\ttry {\n\t\treturn String(value);\n\t} catch {\n\t\treturn \"<unprintable cause>\";\n\t}\n}\n\nfunction applyCauseDetail(cause: ApplyError): string {\n\tswitch (cause.kind) {\n\t\tcase \"driverFailure\": {\n\t\t\tif (cause.cause instanceof PermissionError) {\n\t\t\t\treturn permissionDetail(cause.cause);\n\t\t\t}\n\n\t\t\treturn cause.cause.message;\n\t\t}\n\t\tcase \"unexpectedThrow\": {\n\t\t\treturn `unexpected error: ${safeStringify(cause.cause)}`;\n\t\t}\n\t\tcase \"updateUnsupported\": {\n\t\t\treturn \"update not supported\";\n\t\t}\n\t}\n}\n\nfunction buildDesiredDetail(cause: BuildDesiredError): string {\n\tswitch (cause.kind) {\n\t\tcase \"fileReadFailed\": {\n\t\t\treturn `for '${cause.key}' (${cause.filePath}): ${cause.reason}`;\n\t\t}\n\t\tcase \"iconRemovalRejected\": {\n\t\t\treturn `for '${cause.key}': ${cause.message}`;\n\t\t}\n\t\tcase \"redactedNameCollision\": {\n\t\t\tconst [first, second] = cause.keys;\n\t\t\treturn `for '${first}' and '${second}': ${cause.message}`;\n\t\t}\n\t}\n}\n\nfunction configErrorDetail(err: ConfigError): string {\n\tswitch (err.kind) {\n\t\tcase \"configFunctionFailed\": {\n\t\t\treturn `${err.sourceFile}: config function threw: ${err.message}`;\n\t\t}\n\t\tcase \"fileNotFound\": {\n\t\t\treturn `no bedrock config under ${err.searchedFrom}`;\n\t\t}\n\t\tcase \"luauRuntimeMissing\": {\n\t\t\treturn `${err.sourceFile}: ${err.hint}`;\n\t\t}\n\t\tcase \"parseFailed\": {\n\t\t\treturn `${err.sourceFile}: ${err.message}`;\n\t\t}\n\t\tcase \"validationFailed\": {\n\t\t\tconst first = err.issues[0];\n\t\t\treturn first === undefined\n\t\t\t\t? `${err.sourceFile}: invalid`\n\t\t\t\t: `${err.sourceFile}: ${first.path.join(\".\")} ${first.message}`;\n\t\t}\n\t}\n}\n\nfunction stateErrorDetail(cause: StateError): string {\n\treturn `(${cause.file}): ${cause.reason}`;\n}\n\n/* eslint-disable-next-line max-lines-per-function -- single exhaustive switch over every DeployError variant is clearer than splitting into a wrapped-vs-unwrapped predicate plus a parallel prefix table. */\nfunction deployErrorMessage(err: Exclude<DeployError, { kind: \"applyFailed\" }>): string {\n\tswitch (err.kind) {\n\t\tcase \"buildDesiredFailed\": {\n\t\t\treturn `build desired state failed ${buildDesiredDetail(err.cause)}`;\n\t\t}\n\t\tcase \"configLoadFailed\": {\n\t\t\treturn `config load failed: ${configErrorDetail(err.cause)}`;\n\t\t}\n\t\tcase \"incompletePassEntry\": {\n\t\t\treturn `pass '${err.key}' is missing '${err.missingField}' under environment '${err.environment}'`;\n\t\t}\n\t\tcase \"incompletePlaceEntry\": {\n\t\t\treturn `place '${err.key}' is missing '${err.missingField}' under environment '${err.environment}'`;\n\t\t}\n\t\tcase \"incompleteUniverseEntry\": {\n\t\t\treturn `universe is missing '${err.missingField}' under environment '${err.environment}'`;\n\t\t}\n\t\tcase \"missingCredential\": {\n\t\t\treturn `missing credential: environment variable ${err.variable} is not set`;\n\t\t}\n\t\tcase \"registryConfigMissing\": {\n\t\t\treturn `registry config missing '${err.missing}' (${err.hint})`;\n\t\t}\n\t\tcase \"stateNotConfigured\": {\n\t\t\treturn `state not configured for environment '${err.environment}'`;\n\t\t}\n\t\tcase \"stateReadFailed\": {\n\t\t\treturn `state read failed ${stateErrorDetail(err.cause)}`;\n\t\t}\n\t\tcase \"stateWriteFailed\": {\n\t\t\treturn `state write failed ${stateErrorDetail(err.cause)}`;\n\t\t}\n\t\tcase \"unknownEnvironment\": {\n\t\t\treturn `unknown environment '${err.environment}' (declared: ${err.declared.join(\", \")})`;\n\t\t}\n\t\tcase \"unsupportedBackend\": {\n\t\t\treturn `unsupported state backend '${err.backend}' (${err.hint})`;\n\t\t}\n\t}\n}\n\nfunction parseErrorMessage(err: ParseOptionsError): string {\n\tswitch (err.kind) {\n\t\tcase \"invalidValue\": {\n\t\t\treturn `invalid value for flag '--${err.flag}' (expected a string)`;\n\t\t}\n\t\tcase \"missingRequired\": {\n\t\t\treturn `missing required flag --${err.flag}`;\n\t\t}\n\t\tcase \"unknownFlag\": {\n\t\t\treturn `unknown flag '--${err.flag}'`;\n\t\t}\n\t}\n}\n\nfunction overrideErrorMessage(input: OverrideErrorRender): string {\n\tconst { environment, err } = input;\n\tif (err.kind === \"launchFailed\") {\n\t\treturn `${environment}: failed to launch override - ${err.cause.message}`;\n\t}\n\n\treturn `${environment}: override exited with code ${String(err.exitCode)}`;\n}\n\nfunction migrateParseErrorMessage(err: ParseMigrateError): string {\n\tif (err.kind === \"unknownSource\") {\n\t\treturn `unknown migration source '${err.received}' (supported: ${err.supported.join(\", \")})`;\n\t}\n\n\treturn parseErrorMessage(err);\n}\n\nfunction migrateErrorMessage(err: MigrateError): string {\n\tswitch (err.kind) {\n\t\tcase \"internalError\": {\n\t\t\treturn `migrate internal error: ${err.reason} (${configErrorDetail(err.cause)})`;\n\t\t}\n\t\tcase \"primaryEnvironmentNotFound\": {\n\t\t\treturn `primary environment '${err.primary}' not found (available: ${err.available.join(\", \")})`;\n\t\t}\n\t\tcase \"primaryEnvironmentRequired\": {\n\t\t\treturn `primary environment required (available: ${err.available.join(\", \")})`;\n\t\t}\n\t\tcase \"stateFileNotFound\": {\n\t\t\treturn `Mantle state file not found at '${err.path}'`;\n\t\t}\n\t\tcase \"stateParseFailed\": {\n\t\t\treturn `Mantle state file at '${err.path}' could not be parsed: ${err.reason}`;\n\t\t}\n\t\tcase \"unsupportedMantleStateVersion\": {\n\t\t\treturn `unsupported Mantle state version '${err.found}' (supported: ${err.supported.join(\", \")})`;\n\t\t}\n\t}\n}\n\nfunction buildStatePortErrorMessage(err: MissingCredentialError | UnsupportedBackendError): string {\n\tswitch (err.kind) {\n\t\tcase \"missingCredential\": {\n\t\t\treturn `missing credential: environment variable ${err.variable} is not set`;\n\t\t}\n\t\tcase \"unsupportedBackend\": {\n\t\t\treturn `unsupported state backend '${err.backend}' (${err.hint})`;\n\t\t}\n\t}\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport type { StateConfig } from \"./schema.ts\";\n\n/**\n * Failure surfaced when no `StateConfig` is configured for the requested\n * environment. The shell layer wraps this in a `DeployError` when default\n * state-port construction is requested but the project has not declared\n * where state should live.\n */\nexport interface StateNotConfiguredError {\n\t/** Environment that the resolver was called against. */\n\treadonly environment: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"stateNotConfigured\";\n}\n\n/**\n * Minimal structural input the state resolver needs. Both `Config`\n * (pre-merge, discriminated XOR union) and `ResolvedConfig` (post-merge)\n * satisfy this shape, so callers can route either in without coupling\n * the resolver to the discriminated-union arms.\n */\ninterface StateResolutionInputs {\n\treadonly environments: Record<string, undefined | { readonly state?: StateConfig }>;\n\treadonly state?: StateConfig;\n}\n\n/**\n * Pick the `StateConfig` that applies to `environment`. Per-environment\n * overrides win over the root block; if neither is present, returns\n * `Err(stateNotConfigured)` so the deploy boundary can surface a typed\n * error instead of silently falling back.\n *\n * @param config - Validated project config.\n * @param environment - Target environment name.\n * @returns The resolved `StateConfig`, or `Err(stateNotConfigured)` when\n * neither the environment override nor the root block is set.\n * @example\n *\n * ```ts\n * import { resolveStateConfig } from \"@bedrock-rbx/core\";\n *\n * const result = resolveStateConfig(\n * {\n * state: { backend: \"gist\", gistId: \"root-gist\" },\n * environments: {\n * production: { state: { backend: \"gist\", gistId: \"prod-gist\" } },\n * },\n * },\n * \"production\",\n * );\n *\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data).toContainEntry([\"gistId\", \"prod-gist\"]);\n * }\n * ```\n */\nexport function resolveStateConfig(\n\tconfig: StateResolutionInputs,\n\tenvironment: string,\n): Result<StateConfig, StateNotConfiguredError> {\n\tconst override = config.environments[environment]?.state;\n\tif (override !== undefined) {\n\t\treturn { data: override, success: true };\n\t}\n\n\tif (config.state !== undefined) {\n\t\treturn { data: config.state, success: true };\n\t}\n\n\treturn { err: { environment, kind: \"stateNotConfigured\" }, success: false };\n}\n","import type { Tagged } from \"type-fest\";\n\n/**\n * Regex source shared by the `ResourceKey` brand validator and the runtime\n * config schema. Kept as a string (not a `RegExp`) so arktype can consume it\n * directly in keyed-map signatures without re-escaping.\n */\nexport const RESOURCE_KEY_PATTERN_SOURCE = \"^[A-Za-z0-9_-]+$\";\n\nconst RESOURCE_KEY_PATTERN = new RegExp(RESOURCE_KEY_PATTERN_SOURCE);\n\nconst ROBLOX_ASSET_ID_PATTERN = /^\\d+$/;\n\nconst SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;\n\n/**\n * User-supplied identifier for a resource within a config (e.g. `\"vip-pass\"`).\n * Stable across deploys; used to correlate desired ↔ current state.\n */\nexport type ResourceKey = Tagged<string, \"ResourceKey\">;\n\n/**\n * Roblox-assigned numeric asset ID, represented as a string to avoid int64\n * precision loss in JavaScript.\n */\nexport type RobloxAssetId = Tagged<string, \"RobloxAssetId\">;\n\n/**\n * Lowercase hex-encoded SHA-256 digest (exactly 64 characters drawn from\n * `0-9a-f`). Used to detect changes to file-backed resource inputs such as\n * game-pass icons without re-uploading the file.\n */\nexport type Sha256Hex = Tagged<string, \"Sha256Hex\">;\n\n/**\n * Type predicate: test whether a raw string is a valid {@link ResourceKey}.\n *\n * Prefer this when the caller owns error handling (for example, constructing a\n * `Result` error in a shell-layer parser). Use {@link asResourceKey} when an\n * exception is the right failure mode.\n *\n * @example\n *\n * ```ts\n * import { isResourceKey } from \"@bedrock-rbx/core\";\n *\n * const valid = isResourceKey(\"vip-pass\");\n * const invalid = isResourceKey(\"vip pass\");\n * expect(valid).toBe(true);\n * expect(invalid).toBe(false);\n * ```\n *\n * @param raw - String to test.\n * @returns `true` when `raw` matches the ResourceKey shape; narrows `raw`.\n */\nexport function isResourceKey(raw: string): raw is ResourceKey {\n\treturn RESOURCE_KEY_PATTERN.test(raw);\n}\n\n/**\n * Validate and brand a raw string as a {@link ResourceKey}.\n *\n * Accepts non-empty strings of alphanumeric characters, hyphens, and\n * underscores (matching `/^[A-Za-z0-9_-]+$/`).\n *\n * @example\n *\n * ```ts\n * import { asResourceKey } from \"@bedrock-rbx/core\";\n *\n * const key = asResourceKey(\"vip-pass\");\n *\n * let thrown: unknown;\n * try {\n * asResourceKey(\"vip pass\");\n * } catch (error) {\n * thrown = error;\n * }\n *\n * expect(key).toBe(\"vip-pass\");\n * expect(thrown).toBeInstanceOf(RangeError);\n * ```\n *\n * @param raw - Raw string to validate and brand.\n * @returns The input re-typed as a {@link ResourceKey}.\n * @throws RangeError when `raw` is empty or contains disallowed characters.\n */\nexport function asResourceKey(raw: string): ResourceKey {\n\tif (!isResourceKey(raw)) {\n\t\tthrow new RangeError(\n\t\t\t`ResourceKey must match ${String(RESOURCE_KEY_PATTERN)} (got ${JSON.stringify(raw)})`,\n\t\t);\n\t}\n\n\treturn raw;\n}\n\n/**\n * Type predicate: test whether a raw string is a valid {@link RobloxAssetId}.\n *\n * Prefer this when the caller owns error handling (for example, constructing a\n * `Result` error in a shell-layer parser). Use {@link asRobloxAssetId} when an\n * exception is the right failure mode.\n *\n * @example\n *\n * ```ts\n * import { isRobloxAssetId } from \"@bedrock-rbx/core\";\n *\n * const valid = isRobloxAssetId(\"12345\");\n * const invalid = isRobloxAssetId(\"12345abc\");\n * expect(valid).toBe(true);\n * expect(invalid).toBe(false);\n * ```\n *\n * @param raw - String to test.\n * @returns `true` when `raw` matches the RobloxAssetId shape; narrows `raw`.\n */\nexport function isRobloxAssetId(raw: string): raw is RobloxAssetId {\n\treturn ROBLOX_ASSET_ID_PATTERN.test(raw);\n}\n\n/**\n * Validate and brand a raw string as a {@link RobloxAssetId}.\n *\n * Accepts non-empty digit-only strings (matching `/^\\d+$/`). Roblox asset IDs\n * are int64 values carried as strings because JavaScript's `number` cannot\n * represent the full int64 range.\n *\n * @example\n *\n * ```ts\n * import { asRobloxAssetId } from \"@bedrock-rbx/core\";\n *\n * const id = asRobloxAssetId(\"12345\");\n *\n * let thrown: unknown;\n * try {\n * asRobloxAssetId(\"12345abc\");\n * } catch (error) {\n * thrown = error;\n * }\n *\n * expect(id).toBe(\"12345\");\n * expect(thrown).toBeInstanceOf(RangeError);\n * ```\n *\n * @param raw - Raw string to validate and brand.\n * @returns The input re-typed as a {@link RobloxAssetId}.\n * @throws RangeError when `raw` is empty or contains non-digit characters.\n */\nexport function asRobloxAssetId(raw: string): RobloxAssetId {\n\tif (!isRobloxAssetId(raw)) {\n\t\tthrow new RangeError(\n\t\t\t`RobloxAssetId must be a non-empty digit-only string matching ${String(\n\t\t\t\tROBLOX_ASSET_ID_PATTERN,\n\t\t\t)} (got ${JSON.stringify(raw)})`,\n\t\t);\n\t}\n\n\treturn raw;\n}\n\n/**\n * Type predicate: test whether a raw string is a valid {@link Sha256Hex}.\n *\n * Accepts exactly 64 lowercase hexadecimal characters (matching\n * `/^[0-9a-f]{64}$/`). Prefer this when the caller owns error handling;\n * use {@link asSha256Hex} when throwing is the right failure mode.\n *\n * @example\n *\n * ```ts\n * import { isSha256Hex } from \"@bedrock-rbx/core\";\n *\n * const digest = \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\";\n * const valid = isSha256Hex(digest);\n * const invalid = isSha256Hex(digest.toUpperCase());\n * expect(valid).toBe(true);\n * expect(invalid).toBe(false);\n * ```\n *\n * @param raw - String to test.\n * @returns `true` when `raw` matches the Sha256Hex shape; narrows `raw`.\n */\nexport function isSha256Hex(raw: string): raw is Sha256Hex {\n\treturn SHA256_HEX_PATTERN.test(raw);\n}\n\n/**\n * Validate and brand a raw string as a {@link Sha256Hex}.\n *\n * Accepts exactly 64 lowercase hexadecimal characters. Uppercase hex, lengths\n * other than 64, and any non-hex character are rejected so the brand is a\n * canonical representation.\n *\n * @example\n *\n * ```ts\n * import { asSha256Hex } from \"@bedrock-rbx/core\";\n *\n * const digest = asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * );\n *\n * let thrown: unknown;\n * try {\n * asSha256Hex(\"deadbeef\");\n * } catch (error) {\n * thrown = error;\n * }\n *\n * expect(digest).toHaveLength(64);\n * expect(thrown).toBeInstanceOf(RangeError);\n * ```\n *\n * @param raw - Raw string to validate and brand.\n * @returns The input re-typed as a {@link Sha256Hex}.\n * @throws RangeError when `raw` is not exactly 64 lowercase hex characters.\n */\nexport function asSha256Hex(raw: string): Sha256Hex {\n\tif (!isSha256Hex(raw)) {\n\t\tthrow new RangeError(\n\t\t\t`Sha256Hex must be 64 lowercase hex characters matching ${String(\n\t\t\t\tSHA256_HEX_PATTERN,\n\t\t\t)} (got ${raw.length} chars: ${JSON.stringify(raw)})`,\n\t\t);\n\t}\n\n\treturn raw;\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport type { StateError } from \"./state.ts\";\n\n// Character class mirrors `RESOURCE_KEY_PATTERN_SOURCE` in types/ids.ts\n// (both are \"safe identifier\" alphabets). They're kept in separate literals\n// because the contracts are distinct: ResourceKey is unbounded, this one\n// caps length at 64 so adapter-stored filenames can't exceed filesystem\n// limits. If one alphabet changes, update the other deliberately.\n/**\n * Source pattern for environment names, including `^` and `$` anchors.\n * Letters, digits, `-`, `_`, length 1-64.\n *\n * Exported so the config schema can validate `environments` keys against\n * the same alphabet and length cap that adapters enforce on storage\n * identifiers. Single source of truth: changing the alphabet here changes\n * both the runtime check and the schema-level key constraint.\n *\n * Anchors are embedded so callers do not have to re-add them, matching\n * the `RESOURCE_KEY_PATTERN_SOURCE` convention in `types/ids.ts`.\n */\nexport const ENV_NAME_PATTERN_SOURCE = \"^[A-Za-z0-9_-]{1,64}$\";\n\nconst ENVIRONMENT_NAME_PATTERN = new RegExp(ENV_NAME_PATTERN_SOURCE);\n\n/**\n * Validate an environment name at a state-adapter boundary.\n *\n * Adapters that map environment names onto filesystem-like identifiers\n * (gist filenames, S3 keys) must reject names that could collide or escape\n * their storage layout. This helper accepts letters, digits, `-`, and `_`\n * only, with length between 1 and 64, and returns a `StateError` for\n * anything outside that set so the adapter can fail loudly instead of\n * silently stripping characters.\n *\n * @example\n *\n * ```ts\n * import { validateEnvironmentName } from \"@bedrock-rbx/core\";\n *\n * const ok = validateEnvironmentName(\"production\");\n * expect(ok.success).toBeTrue();\n *\n * const bad = validateEnvironmentName(\"prod/staging\");\n * expect(bad.success).toBeFalse();\n * ```\n *\n * @param environment - Raw environment name supplied by a caller.\n * @returns `Ok(environment)` when the name is safe to use, or\n * `Err(StateError)` with a descriptive reason when it is not.\n */\nexport function validateEnvironmentName(environment: string): Result<string, StateError> {\n\tif (!ENVIRONMENT_NAME_PATTERN.test(environment)) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tfile: environment,\n\t\t\t\tkind: \"stateError\",\n\t\t\t\treason: `invalid environment name: must match ${String(ENVIRONMENT_NAME_PATTERN)}`,\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: environment, success: true };\n}\n","/**\n * Compute the SHA-256 hex digest of a byte sequence. Shared by kind modules\n * that hash file contents as part of pre-I/O normalization.\n *\n * @param bytes - Source bytes; typically the output of an injected `readFile`.\n * @returns Lowercase 64-character hex string.\n */\nexport async function sha256Hex(bytes: Uint8Array): Promise<string> {\n\t// `Uint8Array.from(bytes)` narrows `Uint8Array<ArrayBufferLike>` to\n\t// `Uint8Array<ArrayBuffer>` for `crypto.subtle.digest`, which rejects the\n\t// SharedArrayBuffer variant at the type level.\n\tconst buffer = await crypto.subtle.digest(\"SHA-256\", Uint8Array.from(bytes));\n\treturn Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, \"0\")).join(\n\t\t\"\",\n\t);\n}\n","// 64x64 grayscale PNG (color type 0, bit depth 8) rendering the word\n// \"REDACTED\" in a built-in 5x7 pixel font on a horizontally striped\n// field. Inlined as bytes so the placeholder ships inside the published\n// package and resolves without any filesystem access.\nconst REDACTED_ICON_BYTES = new Uint8Array([\n\t0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,\n\t0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0x00, 0x00, 0x8f, 0x02, 0x2e,\n\t0x02, 0x00, 0x00, 0x00, 0x89, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0xed, 0x56, 0xd1, 0x0e, 0x80,\n\t0x20, 0x08, 0xf4, 0x53, 0xfc, 0x94, 0xfe, 0xff, 0xa7, 0xae, 0x25, 0x62, 0x82, 0xbd, 0x14, 0x6e,\n\t0x39, 0x77, 0x6c, 0x1a, 0xc2, 0xbc, 0x40, 0x0f, 0x2a, 0xe5, 0xa0, 0xa4, 0x0d, 0x00, 0x8e, 0xa0,\n\t0x2c, 0x90, 0x02, 0x01, 0xc8, 0x03, 0xf2, 0x60, 0x52, 0x2d, 0xb0, 0x1f, 0xb0, 0xa1, 0xf4, 0x3c,\n\t0x00, 0x00, 0x99, 0x51, 0xf5, 0xeb, 0xa1, 0x8e, 0xdb, 0xde, 0xbc, 0xfe, 0xbb, 0x80, 0x32, 0xe0,\n\t0x74, 0xb5, 0xa8, 0xcd, 0x6a, 0x86, 0xca, 0x71, 0x00, 0x00, 0x6d, 0x96, 0xad, 0x65, 0x61, 0x00,\n\t0x3a, 0xef, 0x43, 0x04, 0xfe, 0x1d, 0xef, 0x53, 0xa8, 0xe8, 0xb1, 0x33, 0x90, 0xb0, 0xfb, 0x50,\n\t0x65, 0xd1, 0x52, 0x50, 0xef, 0x00, 0xf0, 0x55, 0xd8, 0x0f, 0xd8, 0x0f, 0xc8, 0x83, 0x59, 0xc5,\n\t0xb4, 0x01, 0x00, 0xff, 0x0f, 0x56, 0xb8, 0x05, 0x02, 0xe4, 0xff, 0xcf, 0xe0, 0x04, 0xe9, 0xf3,\n\t0xa6, 0xdb, 0xea, 0x95, 0x15, 0x74, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42,\n\t0x60, 0x82,\n]);\n\n/**\n * Sentinel path written into a resource's `icon[\"en-us\"]` field when\n * redaction substitutes the bedrock-supplied placeholder image. Callers\n * route this path through {@link withRedactedIcon} or through the\n * `readBytes` short-circuit; neither touches the filesystem.\n */\nexport const REDACTED_ICON_PATH = \"<bedrock:redacted-icon.png>\";\n\n/**\n * Embedded bytes of the bedrock-supplied placeholder icon. A 64x64\n * grayscale PNG that renders the word \"REDACTED\" centered on a striped\n * field. Used both for the icon-hash digest at normalize time and for\n * the multipart upload body the resource driver sends to Open Cloud.\n */\nexport { REDACTED_ICON_BYTES };\n\n/**\n * `true` when `path` is the redacted-icon sentinel. `readBytes` and\n * {@link withRedactedIcon} both use this predicate to decide whether to\n * bypass the injected file reader.\n *\n * @param path - Icon path supplied by a flattened or normalized resource entry.\n * @returns `true` for {@link REDACTED_ICON_PATH}; otherwise `false`.\n */\nexport function isRedactedIconPath(path: string): boolean {\n\treturn path === REDACTED_ICON_PATH;\n}\n\n/**\n * Wrap a `readFile` so the sentinel resolves to {@link REDACTED_ICON_BYTES}\n * without touching the inner reader. Applied once at the shell deploy /\n * preview boundary; the wrapped reader flows to every consumer (normalize,\n * registry drivers) unchanged.\n *\n * @param readFile - Inner reader that handles every non-sentinel path.\n * @returns Sentinel-aware reader with the same callable shape as `readFile`.\n */\nexport function withRedactedIcon(\n\treadFile: (path: string) => Promise<Uint8Array>,\n): (path: string) => Promise<Uint8Array> {\n\treturn async (path) => {\n\t\tif (isRedactedIconPath(path)) {\n\t\t\treturn new Uint8Array(REDACTED_ICON_BYTES);\n\t\t}\n\n\t\treturn readFile(path);\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport type { ResourceKey } from \"../../types/ids.ts\";\nimport { isRedactedIconPath, REDACTED_ICON_BYTES } from \"../redacted-icon.ts\";\nimport type { BuildDesiredError, KindIo } from \"./module.ts\";\n\n/**\n * Read file bytes via the injected reader, translating rejections into a\n * `fileReadFailed` `BuildDesiredError`. Shared by kind modules whose\n * pre-I/O normalization hashes a file the user declared by path. The\n * redacted-icon sentinel short-circuits to the embedded placeholder\n * bytes without invoking the injected reader, so a redaction-substituted\n * icon path produces a deterministic hash on every deploy.\n *\n * @param target - Path to read plus the resource key blamed on failure.\n * @param io - I/O surface carrying the injected `readFile` function.\n * @returns `Ok` with the bytes, or `Err` with a `fileReadFailed` error.\n */\nexport async function readBytes(\n\ttarget: { readonly filePath: string; readonly key: ResourceKey },\n\tio: KindIo,\n): Promise<Result<Uint8Array, BuildDesiredError>> {\n\tif (isRedactedIconPath(target.filePath)) {\n\t\treturn { data: new Uint8Array(REDACTED_ICON_BYTES), success: true };\n\t}\n\n\ttry {\n\t\treturn { data: await io.readFile(target.filePath), success: true };\n\t} catch (err) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tkey: target.key,\n\t\t\t\tfilePath: target.filePath,\n\t\t\t\tkind: \"fileReadFailed\",\n\t\t\t\treason: err instanceof Error ? err.message : String(err),\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { type, type Type } from \"arktype\";\n\nimport { asSha256Hex, type ResourceKey, type Sha256Hex } from \"../types/ids.ts\";\nimport { sha256Hex } from \"./kinds/hash.ts\";\nimport type { BuildDesiredError, KindIo } from \"./kinds/module.ts\";\nimport { readBytes } from \"./kinds/read-bytes.ts\";\n\n/**\n * ArkType schema for the locale-keyed icon path map. Today the only\n * accepted locale is `\"en-us\"`; declaring any other locale fails\n * validation. Shared by every icon-bearing entry schema (passes,\n * universe, and any future kind that adopts the same shape) so all\n * locale-rejection messages attribute the issue path identically.\n */\nexport const iconMap: Type<Record<\"en-us\", string>> = type({\n\t\"en-us\": \"string\",\n}).onUndeclaredKey(\"reject\");\n\n/**\n * Read one icon file via the injected I/O surface and return its branded\n * SHA-256 hex digest. Composes `readBytes` (which translates rejections\n * into `fileReadFailed` errors) with `sha256Hex` so kind modules and their\n * locale-iterating wrappers do not re-author the same orchestration.\n *\n * @param target - Path of the icon file plus the resource key blamed on\n * read failure; both are carried unchanged onto `BuildDesiredError`.\n * @param io - I/O surface carrying the injected `readFile`.\n * @returns `Ok` with the branded digest, or `Err` with a `fileReadFailed`\n * error carrying both `filePath` and `key` unchanged.\n */\nexport async function hashIconFile(\n\ttarget: { readonly filePath: string; readonly key: ResourceKey },\n\tio: KindIo,\n): Promise<Result<Sha256Hex, BuildDesiredError>> {\n\tconst read = await readBytes(target, io);\n\tif (!read.success) {\n\t\treturn read;\n\t}\n\n\treturn { data: asSha256Hex(await sha256Hex(read.data)), success: true };\n}\n\n/**\n * Hash every locale entry on a declared icon map, producing a hash map\n * keyed by the same locales. Iterates `hashIconFile` per locale so each\n * read failure surfaces the offending file's path verbatim. Stamped onto\n * desired-state entries by per-kind `normalize` implementations.\n *\n * @param input - Declared icon paths plus the resource key blamed on\n * read failure.\n * @param io - I/O surface carrying the injected `readFile`.\n * @returns `Ok` with hashes mirroring the locale shape of the input, or\n * `Err` from the first locale whose file could not be read.\n */\nexport async function hashIconLocales(\n\tinput: { readonly icon: Record<\"en-us\", string>; readonly key: ResourceKey },\n\tio: KindIo,\n): Promise<Result<Record<\"en-us\", Sha256Hex>, BuildDesiredError>> {\n\tconst enUs = await hashIconFile({ key: input.key, filePath: input.icon[\"en-us\"] }, io);\n\tif (!enUs.success) {\n\t\treturn enUs;\n\t}\n\n\treturn { data: { \"en-us\": enUs.data }, success: true };\n}\n\n/**\n * Locale-aware tri-state equality on icon-file hash maps. Used by per-kind\n * `fieldsEqual` implementations to detect drift on resources that carry an\n * optional locale-keyed icon (universe, game-pass, and any future kind that\n * adopts the same shape).\n *\n * The signature widens from kind-typed inputs to hash maps so the helper\n * does not couple to any specific resource-kind shape; callers project the\n * `iconFileHashes` field off their own current/desired values. Parameter\n * order mirrors `shouldReuploadIcon` so the two helpers can be read side\n * by side without re-orienting at each call.\n *\n * Tri-state semantics:\n *\n * - both `undefined` (icon absent on both sides): `true`.\n * - one `undefined`, the other present: `false`.\n * - both present: per-locale hash equality on the `\"en-us\"` key.\n *\n * @param current - Hashes recorded on the prior current-state entry.\n * @param desired - Hashes layered onto the desired-state entry by `normalize`.\n * @returns `true` when no re-upload is implied by the hash comparison.\n */\nexport function iconHashesEqual(\n\tcurrent: Record<\"en-us\", Sha256Hex> | undefined,\n\tdesired: Record<\"en-us\", Sha256Hex> | undefined,\n): boolean {\n\tif (current === undefined) {\n\t\treturn desired === undefined;\n\t}\n\n\tif (desired === undefined) {\n\t\treturn false;\n\t}\n\n\treturn current[\"en-us\"] === desired[\"en-us\"];\n}\n\n/**\n * Cost-gate for icon re-uploads. Returns `true` when the locally-hashed\n * desired icon differs from the hash recorded on the prior current-state\n * entry, signalling that the driver must re-upload before reconciling.\n * Returns `false` when the hashes match (no re-upload needed) and when\n * both sides report no icon.\n *\n * The signature takes hash maps directly (not whole-state) so the helper\n * is independent of any specific resource-kind shape; every icon-bearing\n * driver projects its own `iconFileHashes` and `outputs.iconFileHashes`\n * fields before calling.\n *\n * @param currentHashes - Hashes recorded on the prior current-state entry.\n * @param desiredHashes - Hashes layered onto the desired-state entry by\n * `normalize` from the local icon file's bytes.\n * @returns `true` when the driver should re-upload the icon.\n *\n * @example\n *\n * ```ts\n * import { asSha256Hex, shouldReuploadIcon } from \"@bedrock-rbx/core\";\n *\n * const previous = asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * );\n * const fresh = asSha256Hex(\n * \"2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881\",\n * );\n *\n * expect(shouldReuploadIcon({ \"en-us\": previous }, { \"en-us\": previous })).toBe(false);\n * expect(shouldReuploadIcon({ \"en-us\": previous }, { \"en-us\": fresh })).toBe(true);\n * ```\n */\nexport function shouldReuploadIcon(\n\tcurrentHashes: Record<\"en-us\", Sha256Hex> | undefined,\n\tdesiredHashes: Record<\"en-us\", Sha256Hex> | undefined,\n): boolean {\n\treturn !iconHashesEqual(currentHashes, desiredHashes);\n}\n","import type { EnvironmentEntry, UniverseEntry } from \"./schema.ts\";\n\n/**\n * Loose authored-shape that the runtime narrow operates on before the\n * config gets cast to the discriminated `Config` union. Mirrors the\n * arktype schema's inferred shape: every field is structurally a\n * supertype of every `Config` variant arm, so the narrow can run before\n * the XOR rule has discriminated the value into one arm.\n */\ninterface LooseConfigForValidation {\n\treadonly environments: Record<string, EnvironmentEntry>;\n\treadonly universe?: UniverseEntry;\n}\n\ninterface UniverseIdIssue {\n\treadonly message: string;\n\treadonly path: ReadonlyArray<string>;\n}\n\n/**\n * Walk the loose authored-shape and surface every place the\n * universeId-XOR-between-root-and-env rule is violated. Pure: returns\n * the issue list; the caller hands it to arktype's `ctx.reject` so each\n * one lands at the offending config path. The schema's runtime narrow\n * uses this to enforce the rule at validation time before the validated\n * value is cast to the strict `Config` discriminated union.\n *\n * @param value - Parsed config the schema is validating.\n * @returns Zero or more issues. Empty when the config satisfies the rule.\n */\nexport function collectUniverseIdIssues(\n\tvalue: LooseConfigForValidation,\n): ReadonlyArray<UniverseIdIssue> {\n\tconst rootUniverseId = value.universe?.universeId;\n\tconst hasRootUniverseBlock = value.universe !== undefined;\n\tconst environmentEntries = Object.entries(value.environments);\n\tconst hasEnvironmentUniverseId = environmentEntries.some(\n\t\t([, environment]) => environment.universe?.universeId !== undefined,\n\t);\n\n\tconst environmentIssues = collectEnvironmentIssues(rootUniverseId, environmentEntries);\n\tconst rootIssues =\n\t\thasRootUniverseBlock && rootUniverseId === undefined && !hasEnvironmentUniverseId\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\"universeId must be declared on the root universe block, or on every environment that declares its own universe overlay.\",\n\t\t\t\t\t\tpath: [\"universe\", \"universeId\"],\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: [];\n\n\treturn [...environmentIssues, ...rootIssues];\n}\n\nfunction collectEnvironmentIssues(\n\trootUniverseId: string | undefined,\n\tenvironmentEntries: ReadonlyArray<readonly [string, EnvironmentEntry]>,\n): ReadonlyArray<UniverseIdIssue> {\n\treturn environmentEntries.flatMap(([environmentName, environment]) => {\n\t\tif (environment.universe === undefined) {\n\t\t\treturn [];\n\t\t}\n\n\t\tif (rootUniverseId !== undefined && environment.universe.universeId !== undefined) {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"universeId is declared at the root universe block; remove it from this environment overlay (root is authoritative) or remove it from the root and declare it on every environment.\",\n\t\t\t\t\tpath: [\"environments\", environmentName, \"universe\", \"universeId\"],\n\t\t\t\t},\n\t\t\t];\n\t\t}\n\n\t\tif (rootUniverseId === undefined && environment.universe.universeId === undefined) {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"universeId must be declared on this environment overlay because the root universe block does not provide one.\",\n\t\t\t\t\tpath: [\"environments\", environmentName, \"universe\", \"universeId\"],\n\t\t\t\t},\n\t\t\t];\n\t\t}\n\n\t\treturn [];\n\t});\n}\n","/* eslint-disable max-lines -- centralized public-API schema; growing the surface here is expected. */\nimport type { Result } from \"@bedrock-rbx/ocale\";\nimport type { SocialLink } from \"@bedrock-rbx/ocale/universes\";\n\nimport { ArkErrors, type, type Type } from \"arktype\";\nimport type { SetRequired } from \"type-fest\";\n\nimport { RESOURCE_KEY_PATTERN_SOURCE } from \"../types/ids.ts\";\nimport type { ConfigError } from \"./config-error.ts\";\nimport { ENV_NAME_PATTERN_SOURCE } from \"./environment.ts\";\nimport { iconMap } from \"./icons.ts\";\nimport { collectUniverseIdIssues } from \"./validate-universe-xor.ts\";\n\n/**\n * Per-field redaction override for a game-pass entry. Each supplied field\n * replaces the matching bedrock-supplied placeholder; omitted fields fall\n * through to the placeholder defaults. The object form implies redaction\n * is enabled, so authors who want only defaults should write\n * `redacted: true` instead of an empty object.\n *\n * @example\n *\n * ```ts\n * import type { GamePassEntry, RedactedGamePassOverride } from \"@bedrock-rbx/core/config\";\n *\n * const override: RedactedGamePassOverride = { name: \"Closed Beta\", price: 500 };\n *\n * const entry: GamePassEntry = {\n * name: \"VIP Pass\",\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip.png\" },\n * price: 1500,\n * redacted: override,\n * };\n *\n * expect(entry.redacted).toStrictEqual({ name: \"Closed Beta\", price: 500 });\n * ```\n */\nexport interface RedactedGamePassOverride {\n\t/** Override name; falls through to the bedrock default when omitted. */\n\tname?: string | undefined;\n\t/** Override description; falls through to the bedrock default when omitted. */\n\tdescription?: string | undefined;\n\t/** Override icon path; falls through to the embedded placeholder when omitted. */\n\ticon?: Record<\"en-us\", string> | undefined;\n\t/**\n\t * Override Robux price; falls through to the bedrock default (`99999`) when\n\t * omitted. Ignored when the entry's `price` is `undefined` so an off-sale\n\t * pass stays off-sale through redaction.\n\t */\n\tprice?: number | undefined;\n}\n\n/**\n * Per-field redaction override for a place entry. Each supplied field\n * replaces the matching bedrock-supplied placeholder; omitted `description`\n * falls through to the placeholder default. `displayName` has no\n * placeholder default; an omitted `displayName` preserves the real value\n * declared on the entry. The object form implies redaction is enabled, so\n * authors who want only the description default should write\n * `redacted: true` instead of an empty object.\n *\n * @example\n *\n * ```ts\n * import type { PlaceEntry, RedactedPlaceOverride } from \"@bedrock-rbx/core/config\";\n *\n * const override: RedactedPlaceOverride = { displayName: \"Hidden Project\" };\n *\n * const entry: PlaceEntry = {\n * description: \"The lobby place.\",\n * displayName: \"Start Place\",\n * filePath: \"places/start.rbxl\",\n * redacted: override,\n * };\n *\n * expect(entry.redacted).toStrictEqual({ displayName: \"Hidden Project\" });\n * ```\n */\nexport interface RedactedPlaceOverride {\n\t/** Override description; falls through to the bedrock default when omitted. */\n\tdescription?: string | undefined;\n\t/** Override display name; preserves the real entry value when omitted (no default). */\n\tdisplayName?: string | undefined;\n}\n\n/**\n * Env-scoped redaction override that applies across every redactable kind in\n * a single environment. Each field is projected onto the kinds whose own\n * override type names it: `price`, `name`, and `icon` reach passes and\n * products; `description` reaches passes, products, and places; `displayName`\n * reaches places. Fields a kind does not recognize are silently ignored for\n * that kind.\n *\n * Composes field-by-field with per-resource overrides at the root and inside\n * an env overlay; the most-specific layer wins per field. Boolean `true`\n * contributes no fields; `false` carves the resource out at its layer.\n *\n * @example\n *\n * ```ts\n * import type { Config, RedactedEnvironmentOverride } from \"@bedrock-rbx/core/config\";\n *\n * const devRedaction: RedactedEnvironmentOverride = { price: 1 };\n *\n * const config: Config = {\n * environments: { dev: { redacted: devRedaction } },\n * passes: {\n * \"vip-pass\": {\n * name: \"VIP Pass\",\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip.png\" },\n * price: 500,\n * },\n * },\n * state: { backend: \"gist\", gistId: \"abc123\" },\n * };\n *\n * expect(config.environments[\"dev\"]?.redacted).toStrictEqual({ price: 1 });\n * ```\n */\nexport interface RedactedEnvironmentOverride {\n\t/** Override name applied to every passes and products entry the env redacts. */\n\tname?: string | undefined;\n\t/** Override description applied to every passes, products, and places entry the env redacts. */\n\tdescription?: string | undefined;\n\t/** Override display name applied only to places (and universes, when their redaction lands). */\n\tdisplayName?: string | undefined;\n\t/** Override icon path applied to every passes and products entry the env redacts. */\n\ticon?: Record<\"en-us\", string> | undefined;\n\t/** Override Robux price applied to every on-sale passes and products entry the env redacts. */\n\tprice?: number | undefined;\n}\n\n/**\n * Body of a single entry in the `passes` collection. Keys in the parent\n * record are `ResourceKey`-shaped strings enforced at schema validation.\n */\nexport interface GamePassEntry {\n\t/** Name shown on the Roblox storefront. */\n\tname: string;\n\t/** Description shown on the game-pass detail page. */\n\tdescription: string;\n\t/**\n\t * Locale-keyed icon path. The Roblox game-pass API is monolingual, so\n\t * only the `\"en-us\"` key is accepted; the map shape leaves room for\n\t * translated icons should the API ever expose them.\n\t */\n\ticon: Record<\"en-us\", string>;\n\t/** Robux price, or omitted / `undefined` for off-sale. */\n\tprice?: number | undefined;\n\t/**\n\t * Set to `true` to deploy this pass with bedrock-supplied placeholder\n\t * content (default name, empty description, embedded placeholder icon,\n\t * price `99999` Robux when the entry is on-sale) in place of the real\n\t * values declared above. Off-sale passes (`price` omitted) stay off-sale.\n\t * Set to a {@link RedactedGamePassOverride} to substitute selected\n\t * placeholders with custom values while leaving the rest at bedrock\n\t * defaults; the object form implies redaction is enabled. Omit or set\n\t * `false` to push the real values unchanged. Environment overlays accept\n\t * the same shape and compose field-by-field with this layer.\n\t */\n\tredacted?: boolean | RedactedGamePassOverride | undefined;\n}\n\n/**\n * Per-field redaction override for a developer-product entry. Each supplied\n * field replaces the matching bedrock-supplied placeholder; omitted fields\n * fall through to the placeholder defaults. The object form implies\n * redaction is enabled, so authors who want only defaults should write\n * `redacted: true` instead of an empty object.\n *\n * @example\n *\n * ```ts\n * import type {\n * DeveloperProductEntry,\n * RedactedDeveloperProductOverride,\n * } from \"@bedrock-rbx/core/config\";\n *\n * const override: RedactedDeveloperProductOverride = {\n * name: \"Closed Beta Pack\",\n * price: 500,\n * };\n *\n * const entry: DeveloperProductEntry = {\n * name: \"Gem Pack\",\n * description: \"Stocks the player up with 1,000 premium gems.\",\n * price: 1500,\n * redacted: override,\n * };\n *\n * expect(entry.redacted).toStrictEqual({ name: \"Closed Beta Pack\", price: 500 });\n * ```\n */\nexport interface RedactedDeveloperProductOverride {\n\t/** Override name; falls through to the bedrock default when omitted. */\n\tname?: string | undefined;\n\t/** Override description; falls through to the bedrock default when omitted. */\n\tdescription?: string | undefined;\n\t/** Override icon path; falls through to the embedded placeholder when omitted. */\n\ticon?: Record<\"en-us\", string> | undefined;\n\t/**\n\t * Override Robux price; falls through to the bedrock default (`99999`) when\n\t * omitted. Ignored when the entry's `price` is `undefined` so an off-sale\n\t * product stays off-sale through redaction.\n\t */\n\tprice?: number | undefined;\n}\n\n/**\n * Body of a single entry in the `products` collection. Keys in the parent\n * record are `ResourceKey`-shaped strings enforced at schema validation.\n */\nexport interface DeveloperProductEntry {\n\t/** Name shown on the Roblox storefront. */\n\tname: string;\n\t/** Description shown on the developer-product detail page. */\n\tdescription: string;\n\t/**\n\t * Locale-keyed icon path. Mirrors `GamePassEntry.icon`; the Roblox\n\t * developer-product API is monolingual, so only the `\"en-us\"` key is\n\t * accepted.\n\t */\n\ticon?: Record<\"en-us\", string>;\n\t/**\n\t * Whether Roblox-managed regional pricing applies to the product.\n\t * Tri-state: omit (or set `undefined`) to leave the flag unmanaged;\n\t * setting `true` or `false` is propagated to Roblox on every deploy.\n\t */\n\tisRegionalPricingEnabled?: boolean | undefined;\n\t/**\n\t * Robux price. Omit (or set `undefined`) for an off-sale product;\n\t * re-adding the field puts the product back on sale on the next deploy.\n\t */\n\tprice?: number | undefined;\n\t/**\n\t * Set to `true` to deploy this product with bedrock-supplied placeholder\n\t * content (default name, empty description, embedded placeholder icon,\n\t * price `99999` Robux when the entry is on-sale) in place of the real\n\t * values declared above. Off-sale products (`price` omitted) stay\n\t * off-sale. Set to a {@link RedactedDeveloperProductOverride} to\n\t * substitute selected placeholders with custom values while leaving the\n\t * rest at bedrock defaults; the object form implies redaction is enabled.\n\t * Omit or set `false` to push the real values unchanged. Environment\n\t * overlays accept the same shape and compose field-by-field with this\n\t * layer.\n\t */\n\tredacted?: boolean | RedactedDeveloperProductOverride | undefined;\n\t/**\n\t * Whether the product appears on the universe's external store page.\n\t * Tri-state: omit (or set `undefined`) to leave the flag unmanaged.\n\t * The Roblox v2 create endpoint does not accept this field, so the\n\t * driver applies it via a follow-up PATCH after the create POST.\n\t */\n\tstorePageEnabled?: boolean | undefined;\n}\n\n/**\n * Body of a single entry under the root `places` collection. Carries the\n * file-path environments share plus the optional Open-Cloud-supported\n * metadata fields. The Roblox `placeId` is environment-specific and lives\n * on each per-environment overlay so the same `.rbxl` file can publish to\n * different places across staging, production, and so on.\n */\nexport interface PlaceEntry {\n\t/** User-facing description shown on the place's detail page. */\n\tdescription?: string | undefined;\n\t/** User-facing place name shown on the Roblox storefront. */\n\tdisplayName?: string | undefined;\n\t/** Path to the `.rbxl` or `.rbxlx` file; handed to `readFile` verbatim by `buildDesired`. */\n\tfilePath: string;\n\t/**\n\t * Set to `true` to deploy this place with bedrock-supplied placeholder\n\t * content (empty description) in place of the real values declared\n\t * above. `displayName` is preserved by default because it surfaces in\n\t * Roblox Studio's place picker and the Creator Hub experience list;\n\t * authors who want full opacity write the object form\n\t * {@link RedactedPlaceOverride} to substitute selected placeholders\n\t * with custom values, including `displayName`. The object form\n\t * implies redaction is enabled. Omit or set `false` to push the real\n\t * values unchanged. Environment overlays accept the same shape and\n\t * compose field-by-field with this layer.\n\t */\n\tredacted?: boolean | RedactedPlaceOverride | undefined;\n\t/** Maximum players per server; positive integer. */\n\tserverSize?: number | undefined;\n}\n\n/**\n * Body of a places entry after `selectEnvironment` has merged the\n * matching per-environment overlay onto the root entry. `filePath` flows\n * from the root (or an overlay override), `placeId` is supplied by the\n * per-environment overlay, and the optional metadata fields fall through\n * from the root unless overridden per-environment.\n *\n * `placeId` is user-supplied because Open Cloud cannot mint places; the\n * place must already exist in Roblox before Bedrock can publish versions\n * to it.\n */\nexport interface ResolvedPlaceEntry {\n\t/** User-facing description shown on the place's detail page. */\n\tdescription?: string | undefined;\n\t/** User-facing place name shown on the Roblox storefront. */\n\tdisplayName?: string | undefined;\n\t/** Path to the `.rbxl` or `.rbxlx` file; handed to `readFile` verbatim by `buildDesired`. */\n\tfilePath: string;\n\t/** Existing Roblox place ID. */\n\tplaceId: string;\n\t/**\n\t * Resolved redaction setting after merging the per-environment overlay\n\t * onto the root entry. See {@link PlaceEntry.redacted} for the\n\t * authored shape.\n\t */\n\tredacted?: boolean | RedactedPlaceOverride | undefined;\n\t/** Maximum players per server; positive integer. */\n\tserverSize?: number | undefined;\n}\n\n/**\n * Body of the singleton `universe` block. Bedrock synthesizes the\n * `ResourceKey` (`\"main\"`) in `flattenConfig`, so user config supplies\n * only the existing `universeId` plus any managed fields they want\n * bedrock to own. Fields omitted here remain unmanaged (the diff treats\n * them as non-drift and the driver omits them from the `updateMask`).\n *\n * `universeId` is user-supplied because Open Cloud cannot mint universes;\n * the universe must already exist in Roblox before bedrock can reconcile\n * its configuration. Declare `universeId` either here at the root (which\n * applies to every environment) or under each `environments[name].universe`\n * overlay, but never both: the schema rejects a config that sets it in\n * both places, and rejects a `universe` block without a resolvable\n * `universeId`.\n */\nexport interface UniverseEntry {\n\t/** Whether console players can join; omit or set `undefined` to leave unmanaged. */\n\tconsoleEnabled?: boolean | undefined;\n\t/** Whether desktop players can join; omit or set `undefined` to leave unmanaged. */\n\tdesktopEnabled?: boolean | undefined;\n\t/**\n\t * Discord social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\tdiscordSocialLink?: SocialLink | undefined;\n\t/**\n\t * Display name for the universe. Because Roblox derives this from\n\t * the root place's name, the driver routes the update through\n\t * `PlacesClient.update`; omit or set `undefined` to leave unmanaged.\n\t */\n\tdisplayName?: string | undefined;\n\t/**\n\t * Facebook social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\tfacebookSocialLink?: SocialLink | undefined;\n\t/**\n\t * Guilded social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\tguildedSocialLink?: SocialLink | undefined;\n\t/** Whether mobile players can join; omit or set `undefined` to leave unmanaged. */\n\tmobileEnabled?: boolean | undefined;\n\t/**\n\t * Private-server price in Robux. Declare as `undefined` to disable\n\t * private servers (cancels active subscriptions); omit to leave the\n\t * server value untouched.\n\t */\n\tprivateServerPriceRobux?: number | undefined;\n\t/**\n\t * Roblox Group social link; omit to leave the server value untouched, set\n\t * to `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\trobloxGroupSocialLink?: SocialLink | undefined;\n\t/** Whether tablet players can join; omit or set `undefined` to leave unmanaged. */\n\ttabletEnabled?: boolean | undefined;\n\t/**\n\t * Twitch social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\ttwitchSocialLink?: SocialLink | undefined;\n\t/**\n\t * Twitter social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\ttwitterSocialLink?: SocialLink | undefined;\n\t/**\n\t * Existing Roblox universe ID. Optional in this entry shape because\n\t * authors may declare it here (root-authoritative, single universe) or\n\t * on each `environments[name].universe` overlay (per-environment\n\t * universes), but never both.\n\t */\n\tuniverseId?: string | undefined;\n\t/** Whether voice chat is enabled; omit or set `undefined` to leave unmanaged. */\n\tvoiceChatEnabled?: boolean | undefined;\n\t/** Whether VR players can join; omit or set `undefined` to leave unmanaged. */\n\tvrEnabled?: boolean | undefined;\n\t/**\n\t * YouTube social link; omit to leave the server value untouched, set to\n\t * `undefined` to clear it, or set to a `SocialLink` to update it.\n\t */\n\tyoutubeSocialLink?: SocialLink | undefined;\n}\n\n/**\n * State configuration for the GitHub Gist backend. Holds the public gist\n * ID; the library reads the GitHub token from `BEDROCK_GITHUB_TOKEN` when\n * it default-constructs the adapter.\n */\nexport interface GistStateConfig {\n\t/** Discriminator selecting the gist adapter. */\n\treadonly backend: \"gist\";\n\t/** ID of an existing GitHub Gist that holds this project's state files. */\n\treadonly gistId: string;\n}\n\n/**\n * Tagged union describing where Bedrock persists its state. The `backend`\n * tag is `\"gist\" | (string & {})` so unknown names autocomplete the\n * builtins while permitting custom values for plugin scenarios. The\n * dispatch path inside `deploy()` rejects unknown names with a typed\n * `unsupportedBackend` error.\n */\nexport type StateConfig = GistStateConfig | { readonly backend: string & {} };\n\n/**\n * Body of a single entry under `environments`. Per-environment overrides\n * narrow root-level settings for that environment without redefining\n * unrelated fields. Resource overlays (`passes`, `places`, `universe`)\n * derive their field shapes from the matching root entry types so adding\n * a field to a base entry surfaces on the overlay automatically.\n *\n * `placeId` stays required when the matching `places` overlay is present\n * because each environment targets its own Roblox place. `universeId` is\n * optional on the `universe` overlay because authors may declare it\n * either at the root (root-authoritative) or per environment, but never\n * both: the schema enforces this XOR at validation time, attributing the\n * failure to the offending field's path.\n */\nexport interface EnvironmentEntry {\n\t/**\n\t * Human-readable label fed to the project-level\n\t * {@link DisplayNamePrefixConfig.format | displayNamePrefix.format}\n\t * template. An environment without a label (or with an empty string)\n\t * is implicitly excluded from prefixing even when the project enables\n\t * it.\n\t */\n\tlabel?: string | undefined;\n\t/**\n\t * Per-environment game-pass overlay. Every field is optional; missing\n\t * fields fall through to the matching root `passes` entry at merge time.\n\t *\n\t * Uses a partial `GamePassEntry` directly rather than `Overlay<T, K>`\n\t * because game passes have no user-supplied identity key (Open Cloud\n\t * mints the asset ID). The `redacted` field accepts the same shape it\n\t * does at the root entry: a boolean toggle or a {@link RedactedGamePassOverride}\n\t * carrying per-field overrides for this resource in this environment.\n\t */\n\tpasses?: Record<string, Partial<GamePassEntry>>;\n\t/**\n\t * Per-environment places overlay. `placeId` is required on every\n\t * declared entry; `filePath` is optional and falls through to the\n\t * matching root `places` entry when omitted.\n\t */\n\tplaces?: Record<string, Overlay<ResolvedPlaceEntry, \"placeId\">>;\n\t/**\n\t * Per-environment developer-product overlay. Every field is optional;\n\t * missing fields fall through to the matching root `products` entry at\n\t * merge time. Mirrors the `passes` shape because developer products\n\t * also have no user-supplied identity key (Open Cloud mints the\n\t * `productId`). The `redacted` field accepts the same shape it does\n\t * at the root entry: a boolean toggle or a\n\t * {@link RedactedDeveloperProductOverride} carrying per-field\n\t * overrides for this resource in this environment.\n\t */\n\tproducts?: Record<string, Partial<DeveloperProductEntry>>;\n\t/**\n\t * Per-environment redaction layer. Accepts a boolean toggle or a\n\t * {@link RedactedEnvironmentOverride} carrying cross-kind override\n\t * fields. Per-resource `redacted` flags on the merged config take\n\t * precedence per field; `false` at any layer carves out at that\n\t * layer.\n\t */\n\tredacted?: boolean | RedactedEnvironmentOverride | undefined;\n\t/** Per-environment state override; takes precedence over root `state`. */\n\tstate?: StateConfig;\n\t/**\n\t * Per-environment universe overlay. Every field is optional, including\n\t * `universeId`: the schema-level XOR rule requires `universeId` here if\n\t * and only if the root `universe` block does not declare one. Other\n\t * fields fall through to the root `universe` block when omitted.\n\t */\n\tuniverse?: Partial<UniverseEntry>;\n}\n\n/**\n * Per-kind entry registry. Each `ResourceKind` must have a matching entry\n * type or `ResourceEntryByKind[K]` is a compile error. Modelled as an\n * interface (not a type alias) so downstream resource kinds can declare\n * their entry type alongside the kind's other domain types without\n * touching this module.\n *\n * @example\n *\n * ```ts\n * import type { ResourceEntryByKind } from \"@bedrock-rbx/core/config\";\n *\n * const entry: ResourceEntryByKind[\"gamePass\"] = {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * name: \"VIP Pass\",\n * price: 500,\n * };\n *\n * expect(entry.name).toBe(\"VIP Pass\");\n * ```\n */\nexport interface ResourceEntryByKind {\n\t/** Authored entry body for a developer-product resource. */\n\tdeveloperProduct: DeveloperProductEntry;\n\t/** Authored entry body for a game-pass resource. */\n\tgamePass: GamePassEntry;\n\t/** Post-merge entry body for a place resource (root + env overlay). */\n\tplace: ResolvedPlaceEntry;\n\t/** Authored entry body for a universe resource. */\n\tuniverse: UniverseEntry;\n}\n\n/**\n * Project-level prefixing policy for universe and place display names.\n * Each environment's `label` flows through `format` to render a prefix\n * that `selectEnvironment` prepends to every declared display name.\n *\n * Defaults: `enabled` is `true`; `format` is `\"[{LABEL}] \"`.\n */\nexport interface DisplayNamePrefixConfig {\n\t/**\n\t * Whether the project applies environment-label prefixing. Treat\n\t * `undefined` as enabled; set `false` to opt out across the project.\n\t */\n\tenabled?: boolean | undefined;\n\t/**\n\t * Template string applied to each environment's `label`. Placeholders:\n\t *\n\t * - `{label}`: label as written.\n\t * - `{LABEL}`: upper-cased label.\n\t * - `{Label}`: capitalized label (first character upper, rest as\n\t * written).\n\t *\n\t * Any other characters in the template flow through verbatim. The\n\t * rendered string is prepended to each declared `displayName`.\n\t */\n\tformat?: string | undefined;\n}\n\n/**\n * Helper that produces a shallow `Omit<T, K>` without using TypeScript's\n * built-in `Omit` (deprecated under the project's lint rules because of\n * its lossy interaction with mapped types).\n *\n * @template T - Source type to project keys away from.\n * @template Key - Key (or union of keys) on `T` to remove.\n */\nexport type WithoutKey<T, Key extends keyof T> = Pick<T, Exclude<keyof T, Key>>;\n\n/**\n * Per-environment universe overlay shape that prevents `universeId` from\n * being redeclared alongside a root-authoritative `universeId`.\n * Used by {@link ConfigRootUniverseId}: when the root universe block\n * declares `universeId`, no per-env overlay may redeclare it. Setting\n * `universeId` here produces a descriptive type error pointing at this\n * field rather than the opaque `never` message.\n */\nexport type UniverseOverlayWithoutId = Partial<WithoutKey<UniverseEntry, \"universeId\">> & {\n\tuniverseId?: \"universeId is already declared on the root universe block; remove it from this environment overlay, or remove it from root and declare it on every environment overlay instead\" & {\n\t\treadonly errorBrand: never;\n\t};\n};\n\n/**\n * Per-environment universe overlay shape that requires `universeId`.\n * Used by {@link ConfigEnvironmentUniverseId}: when the root universe\n * block does not declare `universeId`, every env that declares a\n * `universe` overlay must supply one of its own.\n */\nexport type UniverseOverlayWithId = Partial<WithoutKey<UniverseEntry, \"universeId\">> & {\n\tuniverseId: string;\n};\n\n/**\n * Variant of `Config` where the root `universe` block declares\n * `universeId`. Per-environment universe overlays may carry shared\n * fields (device flags, social links, display name) but cannot\n * redeclare `universeId`; the schema rejects any env overlay that\n * does. The runtime `selectEnvironment` merges shared-field overlays\n * onto the root and inherits `universeId` from the root unchanged.\n */\nexport type ConfigRootUniverseId = ConfigBase & {\n\t/**\n\t * Per-environment overrides keyed by environment name. Required and\n\t * non-empty; environment names match `[A-Za-z0-9_-]{1,64}`. Each env\n\t * entry's `universe` overlay forbids `universeId` because the root\n\t * declares it.\n\t */\n\tenvironments: Record<\n\t\tstring,\n\t\tWithoutKey<EnvironmentEntry, \"universe\"> & { universe?: UniverseOverlayWithoutId }\n\t>;\n\t/**\n\t * Singleton universe block declaring the Roblox universe bedrock\n\t * manages. `universeId` is required in this variant because no\n\t * per-environment overlay may supply one.\n\t */\n\tuniverse?: UniverseEntry & { universeId: string };\n};\n\n/**\n * Variant of `Config` where the root `universe` block omits\n * `universeId`. Every env that declares a `universe` overlay must\n * supply its own `universeId`; envs that omit the overlay deploy no\n * universe at all. The root may still carry shared fields (device\n * flags, social links, display name, icon) which `selectEnvironment`\n * merges onto each env's overlay at resolution time.\n */\nexport type ConfigEnvironmentUniverseId = ConfigBase & {\n\t/**\n\t * Per-environment overrides keyed by environment name. Required and\n\t * non-empty; environment names match `[A-Za-z0-9_-]{1,64}`. Every\n\t * env that declares a `universe` overlay must include `universeId`\n\t * because the root universe block does not provide one.\n\t */\n\tenvironments: Record<\n\t\tstring,\n\t\tWithoutKey<EnvironmentEntry, \"universe\"> & { universe?: UniverseOverlayWithId }\n\t>;\n\t/**\n\t * Singleton universe block declaring the Roblox universe bedrock\n\t * manages. `universeId` is not permitted here in this variant because\n\t * every environment supplies its own; setting it produces a descriptive\n\t * type error rather than the opaque `never` message.\n\t */\n\tuniverse?: WithoutKey<UniverseEntry, \"universeId\"> & {\n\t\tuniverseId?: \"universeId is already declared per environment; remove it from the root universe block, or remove it from every environment overlay and declare it here instead\" & {\n\t\t\treadonly errorBrand: never;\n\t\t};\n\t};\n};\n\n/**\n * Validated project config as accepted by `loadConfig`. Plain mutable so\n * users can adjust fields in a long-running script before deploying.\n *\n * Discriminated union over the location of `universeId`: it lives at the\n * root universe block ({@link ConfigRootUniverseId}) or on every\n * environment universe overlay ({@link ConfigEnvironmentUniverseId}), but never\n * both. The TypeScript types reject the both-set case at compile time,\n * and the arktype runtime narrow rejects every offending field path at\n * `validateConfig` time. State must be configured at the root or under\n * every entry of `environments`; `resolveStateConfig` surfaces the\n * missing case at the deploy boundary as `stateNotConfigured`.\n *\n * @example\n *\n * ```ts\n * import type { Config } from \"@bedrock-rbx/core/config\";\n *\n * const config: Config = {\n * environments: { production: {} },\n * state: { backend: \"gist\", gistId: \"abc123def456\" },\n * passes: {\n * \"vip-pass\": {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * name: \"VIP Pass\",\n * price: 500,\n * },\n * },\n * };\n *\n * expect(config.passes![\"vip-pass\"]!.name).toBe(\"VIP Pass\");\n * ```\n */\nexport type Config = ConfigEnvironmentUniverseId | ConfigRootUniverseId;\n\n/**\n * Body of the singleton `universe` block after `selectEnvironment` has\n * merged a per-environment overlay onto the root. Identical to\n * {@link UniverseEntry} except `universeId` is required: the schema-level\n * XOR rule ensures every projected universe carries a resolved\n * `universeId`. Resource drivers consume this shape rather than\n * `UniverseEntry` so the post-merge invariant is visible in the type\n * system.\n */\nexport interface ResolvedUniverseEntry extends Pick<\n\tUniverseEntry,\n\tExclude<keyof UniverseEntry, \"universeId\">\n> {\n\t/** Existing Roblox universe ID, resolved from the root or per-environment overlay. */\n\tuniverseId: string;\n}\n\n/**\n * Project config after `selectEnvironment` has merged a single\n * environment's overlays onto the root. The shape mirrors `Config`\n * except `places` carries `ResolvedPlaceEntry` (both `filePath` and\n * `placeId`), since the resolver fails before this point if an entry is\n * missing its environment-supplied `placeId`. Downstream consumers\n * (`flattenConfig`, `buildDefaultRegistry`, the deploy pipeline) accept\n * this shape rather than `Config` so the post-merge invariant is visible\n * in the type system.\n *\n * @example\n *\n * ```ts\n * import { selectEnvironment, type ResolvedConfig } from \"@bedrock-rbx/core\";\n * import type { Config } from \"@bedrock-rbx/core/config\";\n *\n * const config: Config = {\n * environments: {\n * production: { places: { \"start-place\": { placeId: \"4711\" } } },\n * },\n * places: { \"start-place\": { filePath: \"places/start.rbxl\" } },\n * state: { backend: \"gist\", gistId: \"abc\" },\n * };\n *\n * const result = selectEnvironment(config, \"production\");\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * const resolved: ResolvedConfig = result.data;\n * expect(resolved.places?.[\"start-place\"]?.placeId).toBe(\"4711\");\n * }\n * ```\n */\nexport interface ResolvedConfig extends Pick<ConfigBase, Exclude<keyof ConfigBase, \"places\">> {\n\t/**\n\t * Per-environment overrides preserved from the source `Config`.\n\t * Carried for downstream context; `selectEnvironment` does not read\n\t * other environments after resolving the requested one.\n\t */\n\tenvironments: Record<string, EnvironmentEntry>;\n\t/** Keyed-map collection of resolved place entries; both `filePath` and `placeId` are present. */\n\tplaces?: Record<string, ResolvedPlaceEntry>;\n\t/**\n\t * Singleton universe block after `selectEnvironment` has resolved the\n\t * XOR between root and per-environment `universeId`. The schema narrow\n\t * rejects any config that would leave `universeId` unresolved, so the\n\t * post-merge invariant promotes `universeId` from optional to required.\n\t */\n\tuniverse?: ResolvedUniverseEntry;\n}\n\n/**\n * Overlay shape used by per-environment entries: every field of `T`\n * becomes optional, except `RequiredKey`, which stays required so the\n * overlay still re-asserts the identity-bearing field of its target\n * resource.\n *\n * @template T - Base entry type whose field shapes the overlay derives from.\n * @template RequiredKey - Identity-bearing key on `T` that the overlay must\n * still declare (for example `\"placeId\"` or `\"universeId\"`).\n */\ntype Overlay<T, RequiredKey extends keyof T> = SetRequired<Partial<T>, RequiredKey>;\n\n/**\n * Fields shared by every {@link Config} variant. The discriminated\n * `Config` union narrows `universe` and `environments` to enforce the\n * `universeId` XOR rule between the root and per-environment overlays;\n * everything else lives here.\n */\ninterface ConfigBase {\n\t/**\n\t * Project-level prefixing of universe and place display names with the\n\t * environment label. Default behaviour (when omitted) is enabled with a\n\t * `\"[{LABEL}] \"` template; set `enabled: false` to opt out, or set\n\t * `format` to a custom template.\n\t */\n\tdisplayNamePrefix?: DisplayNamePrefixConfig;\n\t/** Reserved at the root for c12's config layering / overlay work. */\n\textends?: unknown;\n\t/** Keyed-map collection of game-pass entries by user-supplied ResourceKey. */\n\tpasses?: Record<string, GamePassEntry>;\n\t/** Keyed-map collection of place entries by user-supplied ResourceKey. */\n\tplaces?: Record<string, PlaceEntry>;\n\t/** Keyed-map collection of developer-product entries by user-supplied ResourceKey. */\n\tproducts?: Record<string, DeveloperProductEntry>;\n\t/** Where Bedrock persists state for this project; required at deploy time. */\n\tstate?: StateConfig;\n}\n\n/**\n * Narrow a `StateConfig` to the `GistStateConfig` arm. The `(string & {})`\n * autocomplete idiom prevents TypeScript from narrowing on\n * `backend === \"gist\"` alone, so dispatch sites use this guard to\n * preserve the `gistId` field shape.\n *\n * @example\n *\n * ```ts\n * import { isGistStateConfig } from \"@bedrock-rbx/core\";\n * import type { StateConfig } from \"@bedrock-rbx/core/config\";\n *\n * const config: StateConfig = { backend: \"gist\", gistId: \"abc\" };\n *\n * expect(isGistStateConfig(config)).toBeTrue();\n * if (isGistStateConfig(config)) {\n * expect(config.gistId).toBe(\"abc\");\n * }\n * ```\n *\n * @param config - Resolved state config to inspect.\n * @returns `true` when `config.backend === \"gist\"`; otherwise `false`.\n */\nexport function isGistStateConfig(config: StateConfig): config is GistStateConfig {\n\treturn config.backend === \"gist\";\n}\n\nconst OPTIONAL_BOOLEAN = \"boolean | undefined\";\n\nconst OPTIONAL_STRING = \"string | undefined\";\n\nconst REDACTED_KEY = \"redacted?\";\n\nconst NON_EMPTY_OVERRIDE_MESSAGE =\n\t\"a non-empty override object; use `redacted: true` for default placeholders\";\n\n/**\n * Shared arktype constraint for any optional positive-integer field.\n * Reused by per-kind entry schemas so positive-integer fields validate\n * identically.\n */\nexport const OPTIONAL_POSITIVE_INTEGER = \"(number.integer >= 1) | undefined\";\n\n/**\n * Shared arktype constraint for any optional Robux-price field. The schema\n * rejects negatives, fractional values, `NaN`, and `Infinity` at config\n * validation time so a malformed price surfaces with a path attributing the\n * failure to the offending field, rather than slipping through to the\n * Roblox API and surfacing as an opaque error at apply time. Per-kind entry\n * schemas reuse this constant so all Robux-price fields validate\n * identically.\n */\nexport const OPTIONAL_ROBUX_PRICE = \"number.integer >= 0 | undefined\";\n\nconst gamePassRedactedOverride = type({\n\t\"description?\": \"string\",\n\t\"icon?\": iconMap,\n\t\"name?\": \"string\",\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n})\n\t.onUndeclaredKey(\"reject\")\n\t.narrow((value, ctx) => {\n\t\tif (Object.keys(value).length === 0) {\n\t\t\treturn ctx.mustBe(NON_EMPTY_OVERRIDE_MESSAGE);\n\t\t}\n\n\t\treturn true;\n\t});\n\nconst gamePassRedacted = gamePassRedactedOverride.or(OPTIONAL_BOOLEAN);\n\nconst placeRedactedOverride = type({\n\t\"description?\": \"string\",\n\t\"displayName?\": \"string\",\n})\n\t.onUndeclaredKey(\"reject\")\n\t.narrow((value, ctx) => {\n\t\tif (Object.keys(value).length === 0) {\n\t\t\treturn ctx.mustBe(NON_EMPTY_OVERRIDE_MESSAGE);\n\t\t}\n\n\t\treturn true;\n\t});\n\nconst placeRedacted = placeRedactedOverride.or(OPTIONAL_BOOLEAN);\n\nconst productRedactedOverride = type({\n\t\"description?\": \"string\",\n\t\"icon?\": iconMap,\n\t\"name?\": \"string\",\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n})\n\t.onUndeclaredKey(\"reject\")\n\t.narrow((value, ctx) => {\n\t\tif (Object.keys(value).length === 0) {\n\t\t\treturn ctx.mustBe(NON_EMPTY_OVERRIDE_MESSAGE);\n\t\t}\n\n\t\treturn true;\n\t});\n\nconst productRedacted = productRedactedOverride.or(OPTIONAL_BOOLEAN);\n\nconst environmentRedactedOverride = type({\n\t\"description?\": \"string\",\n\t\"displayName?\": \"string\",\n\t\"icon?\": iconMap,\n\t\"name?\": \"string\",\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n})\n\t.onUndeclaredKey(\"reject\")\n\t.narrow((value, ctx) => {\n\t\tif (Object.keys(value).length === 0) {\n\t\t\treturn ctx.mustBe(NON_EMPTY_OVERRIDE_MESSAGE);\n\t\t}\n\n\t\treturn true;\n\t});\n\nconst environmentRedacted = environmentRedactedOverride.or(OPTIONAL_BOOLEAN);\n\n// Resource-kind entry schemas. Adding a new kind is two additions:\n// 1. Declare its entry schema and keyed-map collection below.\n// 2. Reference that collection as an optional property on `rootSchema`.\n// No existing entries change. The ResourceKey regex lives on the map key\n// signature so invalid identifiers surface as schema failures pointing at\n// the offending key, not as deferred errors downstream.\nconst gamePassEntry = type({\n\t\"name\": \"string\",\n\t\"description\": \"string\",\n\t\"icon\": iconMap,\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n\t[REDACTED_KEY]: gamePassRedacted,\n});\n\nconst passesCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: gamePassEntry,\n}).onUndeclaredKey(\"reject\");\n\nconst developerProductEntry = type({\n\t\"name\": \"string\",\n\t\"description\": \"string\",\n\t\"icon?\": iconMap,\n\t\"isRegionalPricingEnabled?\": OPTIONAL_BOOLEAN,\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n\t[REDACTED_KEY]: productRedacted,\n\t\"storePageEnabled?\": OPTIONAL_BOOLEAN,\n}).onUndeclaredKey(\"reject\");\n\nconst productsCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: developerProductEntry,\n}).onUndeclaredKey(\"reject\");\n\nconst ROBLOX_ID_DIGITS = \"string.digits\";\n\nconst placeEntry = type({\n\t\"description?\": OPTIONAL_STRING,\n\t\"displayName?\": OPTIONAL_STRING,\n\t\"filePath\": \"string\",\n\t[REDACTED_KEY]: placeRedacted,\n\t\"serverSize?\": OPTIONAL_POSITIVE_INTEGER,\n}).onUndeclaredKey(\"reject\");\n\nconst placesCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: placeEntry,\n}).onUndeclaredKey(\"reject\");\n\nconst socialLink = type({\n\ttitle: \"string\",\n\turi: \"string\",\n}).onUndeclaredKey(\"reject\");\n\nconst socialLinkOrUndefined = socialLink.or(\"undefined\");\n\nconst universeEntry = type({\n\t\"consoleEnabled?\": OPTIONAL_BOOLEAN,\n\t\"desktopEnabled?\": OPTIONAL_BOOLEAN,\n\t\"discordSocialLink?\": socialLinkOrUndefined,\n\t\"displayName?\": OPTIONAL_STRING,\n\t\"facebookSocialLink?\": socialLinkOrUndefined,\n\t\"guildedSocialLink?\": socialLinkOrUndefined,\n\t\"mobileEnabled?\": OPTIONAL_BOOLEAN,\n\t\"privateServerPriceRobux?\": OPTIONAL_ROBUX_PRICE,\n\t\"robloxGroupSocialLink?\": socialLinkOrUndefined,\n\t\"tabletEnabled?\": OPTIONAL_BOOLEAN,\n\t\"twitchSocialLink?\": socialLinkOrUndefined,\n\t\"twitterSocialLink?\": socialLinkOrUndefined,\n\t\"universeId?\": ROBLOX_ID_DIGITS,\n\t\"voiceChatEnabled?\": OPTIONAL_BOOLEAN,\n\t\"vrEnabled?\": OPTIONAL_BOOLEAN,\n\t\"youtubeSocialLink?\": socialLinkOrUndefined,\n}).onUndeclaredKey(\"reject\");\n\nconst stateConfig = type({\n\t\"backend\": \"string\",\n\t\"gistId?\": \"string > 0\",\n}).onUndeclaredKey(\"reject\");\n\n// Overlay schemas mirror the base entry schemas but with every field\n// optional, except the identity-bearing key (`placeId`, `universeId`)\n// which stays required. Game passes have no user-supplied identity, so\n// the overlay is fully partial.\nconst gamePassOverlay = type({\n\t\"description?\": \"string\",\n\t\"icon?\": iconMap,\n\t\"name?\": \"string\",\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n\t[REDACTED_KEY]: gamePassRedacted,\n}).onUndeclaredKey(\"reject\");\n\nconst passesOverlayCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: gamePassOverlay,\n}).onUndeclaredKey(\"reject\");\n\nconst developerProductOverlay = type({\n\t\"description?\": \"string\",\n\t\"icon?\": iconMap,\n\t\"isRegionalPricingEnabled?\": OPTIONAL_BOOLEAN,\n\t\"name?\": \"string\",\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n\t[REDACTED_KEY]: productRedacted,\n\t\"storePageEnabled?\": OPTIONAL_BOOLEAN,\n}).onUndeclaredKey(\"reject\");\n\nconst productsOverlayCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: developerProductOverlay,\n}).onUndeclaredKey(\"reject\");\n\nconst placeOverlay = type({\n\t\"description?\": OPTIONAL_STRING,\n\t\"displayName?\": OPTIONAL_STRING,\n\t\"filePath?\": \"string\",\n\t\"placeId\": ROBLOX_ID_DIGITS,\n\t[REDACTED_KEY]: placeRedacted,\n\t\"serverSize?\": OPTIONAL_POSITIVE_INTEGER,\n}).onUndeclaredKey(\"reject\");\n\nconst placesOverlayCollection = type({\n\t[`[/${RESOURCE_KEY_PATTERN_SOURCE}/]`]: placeOverlay,\n}).onUndeclaredKey(\"reject\");\n\n// `Partial<UniverseEntry>` is structurally equal to `UniverseEntry`\n// itself because every field on `UniverseEntry` is optional. Reusing\n// `universeEntry` here keeps the field set in lockstep and avoids a\n// parallel declaration to drift. The XOR rule that ties root and\n// per-environment `universeId` together lives on `rootSchema` below,\n// where both sides of the relationship are in scope.\nconst universeOverlay = universeEntry;\n\nconst environmentEntry: Type<EnvironmentEntry> = type({\n\t\"label?\": OPTIONAL_STRING,\n\t\"passes?\": passesOverlayCollection,\n\t\"places?\": placesOverlayCollection,\n\t\"products?\": productsOverlayCollection,\n\t[REDACTED_KEY]: environmentRedacted,\n\t\"state?\": stateConfig,\n\t\"universe?\": universeOverlay,\n}).onUndeclaredKey(\"reject\");\n\nconst displayNamePrefix: Type<DisplayNamePrefixConfig> = type({\n\t\"enabled?\": OPTIONAL_BOOLEAN,\n\t\"format?\": OPTIONAL_STRING,\n}).onUndeclaredKey(\"reject\");\n\nconst environmentsCollection = type({\n\t[`[/${ENV_NAME_PATTERN_SOURCE}/]`]: environmentEntry,\n})\n\t.onUndeclaredKey(\"reject\")\n\t.narrow((value, ctx) => {\n\t\tif (Object.keys(value).length === 0) {\n\t\t\treturn ctx.mustBe(\"an environments record with at least one declared environment\");\n\t\t}\n\n\t\treturn true;\n\t});\n\n// `rootSchema` is intentionally not annotated `Type<Config>` because\n// `Config` is a discriminated union enforcing the universeId XOR rule\n// at the type level. The arktype schema describes the loose\n// authored-shape that's structurally a supertype of every union arm;\n// the runtime narrow rejects any value that doesn't satisfy one arm so\n// `validateConfig` can cast the result to `Config` safely. Splitting\n// the schema into two `.or()` variants would mirror the type union but\n// duplicate every field declaration without buying additional runtime\n// coverage on top of the narrow.\nconst rootSchema = type({\n\t\"displayNamePrefix?\": displayNamePrefix,\n\t\"environments\": environmentsCollection,\n\t\"extends?\": \"unknown\",\n\t\"passes?\": passesCollection,\n\t\"places?\": placesCollection,\n\t\"products?\": productsCollection,\n\t\"state?\": stateConfig,\n\t\"universe?\": universeEntry,\n})\n\t.onUndeclaredKey(\"reject\")\n\t.narrow((value, ctx) => {\n\t\t// `ctx.reject` returns `false` for every issue and `reduce` walks the\n\t\t// whole list so every offending field gets attributed; the seeded\n\t\t// `true` flips to `false` on the first issue.\n\t\treturn collectUniverseIdIssues(value).reduce<boolean>((_accumulator, issue) => {\n\t\t\treturn ctx.reject({ message: issue.message, path: [...issue.path] });\n\t\t}, true);\n\t});\n\n/**\n * Validate a parsed config value against the runtime schema. Returns the\n * validated `Config` on success or a `validationFailed` `ConfigError` with\n * one issue per problem, each attributed to a field path. `sourceFile`\n * appears in the error so callers can point a human at the offending file.\n *\n * @param input - Parsed value from a config source (object tree from a\n * config loader, or a hand-built literal). Shape is checked, not assumed.\n * @param sourceFile - Path or identifier of the source file, used in the\n * `validationFailed` error.\n * @returns `Ok` with the validated `Config`, or `Err` with a\n * `validationFailed` error carrying each issue's field path.\n * @example\n *\n * ```ts\n * import { validateConfig } from \"@bedrock-rbx/core\";\n *\n * const ok = validateConfig(\n * {\n * environments: { production: {} },\n * passes: {\n * \"vip-pass\": {\n * description: \"VIP perks.\",\n * icon: { \"en-us\": \"assets/vip.png\" },\n * name: \"VIP Pass\",\n * price: 500,\n * },\n * },\n * },\n * \"bedrock.config.ts\",\n * );\n * expect(ok.success).toBeTrue();\n *\n * const err = validateConfig(\n * { environments: { production: {} }, passes: { \"vip-pass\": { name: \"VIP\" } } },\n * \"bedrock.config.ts\",\n * );\n * expect(err.success).toBeFalse();\n * if (!err.success) {\n * expect(err.err.kind).toBe(\"validationFailed\");\n * }\n * ```\n */\nexport function validateConfig(input: unknown, sourceFile: string): Result<Config, ConfigError> {\n\tconst validated = rootSchema(input);\n\tif (validated instanceof ArkErrors) {\n\t\tconst issues = Array.from(validated, (issue) => {\n\t\t\treturn {\n\t\t\t\tmessage: issue.message,\n\t\t\t\tpath: [...issue.path].map((segment) => String(segment)),\n\t\t\t};\n\t\t});\n\n\t\treturn {\n\t\t\terr: { issues, kind: \"validationFailed\", sourceFile },\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\t// Precondition for the cast: the runtime narrow rejects every value\n\t// that violates the universeId XOR rule, so a successful validation\n\t// always lands in one arm of the discriminated `Config` union. The\n\t// schema's inferred type is the structurally loose authored-shape\n\t// (universeId optional in both root and per-env overlays); the cast\n\t// collapses it to the strict union without loss of safety.\n\treturn { data: validated as unknown as Config, success: true };\n}\n","import { createClackPort } from \"../cli/clack-port.ts\";\nimport { type ClackPort, renderDeployError } from \"../cli/render.ts\";\nimport { resolveStateConfig } from \"../core/resolve-state-config.ts\";\nimport {\n\ttype Config,\n\tisGistStateConfig,\n\ttype ResolvedConfig,\n\ttype StateConfig,\n} from \"../core/schema.ts\";\nimport type {\n\tProgressEvent,\n\tProgressPort,\n\tResourceOpSucceededCreateEvent,\n} from \"../ports/progress-port.ts\";\nimport type { ApplyError } from \"../shell/apply-ops.ts\";\n\n/**\n * Configuration for {@link createClackProgressAdapter}.\n */\nexport interface ClackProgressAdapterDeps {\n\t/** Output port the events are rendered through. */\n\treadonly clack: ClackPort;\n\t/**\n\t * Loaded project config (raw `Config` or env-resolved `ResolvedConfig`);\n\t * the `stateWritten` case resolves the per-environment `StateConfig`\n\t * against this to format the backend label. When omitted, `stateWritten`\n\t * renders the generic `\"state\"` placeholder.\n\t */\n\treadonly config?: Config | ResolvedConfig;\n}\n\n/**\n * Build a {@link ProgressPort} that renders events through a `ClackPort`.\n * Pattern-matches on the event `kind`: per-resource events render one line each,\n * the aggregate `applySummary` becomes the deploy footer, and `stateWritten`\n * names the persistence backend resolved from the loaded `Config`.\n *\n * @example\n *\n * ```ts\n * import { createClackProgressAdapter, type ClackPort } from \"@bedrock-rbx/core\";\n *\n * const lines: Array<string> = [];\n * const clack: ClackPort = {\n * cancel: (message) => lines.push(`cancel: ${message}`),\n * intro: (message) => lines.push(`intro: ${message}`),\n * logError: (message) => lines.push(`error: ${message}`),\n * logMessage: (message) => lines.push(`log: ${message}`),\n * logSuccess: (message) => lines.push(`ok: ${message}`),\n * outro: (message) => lines.push(`outro: ${message}`),\n * };\n *\n * const port = createClackProgressAdapter({ clack });\n *\n * port.emit({ environment: \"production\", kind: \"stateWritten\" });\n *\n * expect(lines).toEqual([\"log: State written to state\"]);\n * ```\n *\n * @param deps - The clack port and optional config the adapter renders through.\n * @returns A `ProgressPort` that renders via clack.\n */\nexport function createClackProgressAdapter(deps: ClackProgressAdapterDeps): ProgressPort {\n\treturn {\n\t\temit(event: ProgressEvent): void {\n\t\t\trenderEvent(event, deps);\n\t\t},\n\t};\n}\n\n/**\n * Build a {@link ProgressPort} for the default CLI rendering path: wires a\n * fresh {@link createClackPort} into {@link createClackProgressAdapter}. The\n * `config` argument (raw `Config` or env-resolved `ResolvedConfig`) is\n * forwarded so `stateWritten` events can name the persistence backend; pass\n * `undefined` when the config has not yet loaded.\n *\n * Internal: used by `deploy()`'s default-port resolver when callers omit\n * `progress` and `BEDROCK_CLI` is set.\n *\n * @param config - Pre-loaded or env-resolved config used to format the\n * state-backend label, or `undefined` to render the generic placeholder.\n * @returns A clack-backed `ProgressPort` that writes to `process.stdout`.\n */\nexport function createDefaultProgressAdapter(\n\tconfig: Config | ResolvedConfig | undefined,\n): ProgressPort {\n\tconst clack = createClackPort();\n\treturn config === undefined\n\t\t? createClackProgressAdapter({ clack })\n\t\t: createClackProgressAdapter({ clack, config });\n}\n\nfunction applySummaryLine(event: Extract<ProgressEvent, { kind: \"applySummary\" }>): string {\n\tconst seconds = (event.durationMs / 1000).toFixed(1);\n\tconst parts = [\n\t\t`${event.created} create`,\n\t\t`${event.updated} update`,\n\t\t`${event.noop} noop`,\n\t\t`${event.failed} failed`,\n\t];\n\treturn `Succeeded in ${seconds}s: ${parts.join(\", \")}`;\n}\n\nfunction stateConfigLabel(state: StateConfig): string {\n\tif (isGistStateConfig(state)) {\n\t\treturn `gist:${state.gistId}`;\n\t}\n\n\treturn state.backend;\n}\n\nfunction formatStateLabel(\n\tconfig: Config | ResolvedConfig | undefined,\n\tenvironment: string,\n): string {\n\tif (config === undefined) {\n\t\treturn \"state\";\n\t}\n\n\tconst resolved = resolveStateConfig(config, environment);\n\tif (!resolved.success) {\n\t\treturn \"state\";\n\t}\n\n\treturn stateConfigLabel(resolved.data);\n}\n\nfunction extractResourceId(event: ResourceOpSucceededCreateEvent): string | undefined {\n\tswitch (event.resourceKind) {\n\t\tcase \"developerProduct\": {\n\t\t\treturn event.outputs.productId;\n\t\t}\n\t\tcase \"gamePass\": {\n\t\t\treturn event.outputs.assetId;\n\t\t}\n\t\tcase \"place\": {\n\t\t\treturn undefined;\n\t\t}\n\t\tcase \"universe\": {\n\t\t\treturn event.outputs.rootPlaceId;\n\t\t}\n\t}\n}\n\nfunction renderResourceOpSucceeded(\n\tevent: Extract<ProgressEvent, { kind: \"resourceOpSucceeded\" }>,\n\tclack: ClackPort,\n): void {\n\tif (event.opType === \"create\") {\n\t\tconst id = extractResourceId(event);\n\t\tconst suffix = id === undefined ? \"\" : ` (id ${id})`;\n\t\tclack.logSuccess(`${event.resourceKind}.${event.key} created${suffix}`);\n\t\treturn;\n\t}\n\n\tclack.logSuccess(\n\t\t`${event.resourceKind}.${event.key} ${event.changedFields.join(\", \")} updated`,\n\t);\n}\n\nfunction describeApplyError(error: ApplyError): string {\n\tswitch (error.kind) {\n\t\tcase \"driverFailure\": {\n\t\t\treturn `failed: ${error.cause.message}`;\n\t\t}\n\t\tcase \"unexpectedThrow\": {\n\t\t\treturn \"unexpected error\";\n\t\t}\n\t\tcase \"updateUnsupported\": {\n\t\t\treturn \"update not supported\";\n\t\t}\n\t}\n}\n\n/* eslint-disable-next-line max-lines-per-function -- single exhaustive switch over every ProgressEvent variant is clearer than splitting into deploy-level vs per-resource halves, which would leave both halves non-exhaustive and required a boolean handoff that hides the dispatch surface. */\nfunction renderEvent(event: ProgressEvent, deps: ClackProgressAdapterDeps): void {\n\tconst { clack, config } = deps;\n\tswitch (event.kind) {\n\t\tcase \"applySummary\": {\n\t\t\tclack.logMessage(applySummaryLine(event));\n\t\t\treturn;\n\t\t}\n\t\tcase \"deployFailure\": {\n\t\t\trenderDeployError(event.error, clack);\n\t\t\treturn;\n\t\t}\n\t\tcase \"deploySuccess\": {\n\t\t\tclack.logSuccess(`${event.environment}: ${event.resourceCount} resources reconciled`);\n\t\t\treturn;\n\t\t}\n\t\tcase \"resourceOpFailed\": {\n\t\t\tclack.logError(`${event.resourceKind}.${event.key} ${describeApplyError(event.error)}`);\n\t\t\treturn;\n\t\t}\n\t\tcase \"resourceOpNoop\": {\n\t\t\tclack.logMessage(`${event.resourceKind}.${event.key} unchanged`);\n\t\t\treturn;\n\t\t}\n\t\tcase \"resourceOpStarted\": {\n\t\t\treturn;\n\t\t}\n\t\tcase \"resourceOpSucceeded\": {\n\t\t\trenderResourceOpSucceeded(event, clack);\n\t\t\treturn;\n\t\t}\n\t\tcase \"stateWritten\": {\n\t\t\tclack.logMessage(`State written to ${formatStateLabel(config, event.environment)}`);\n\t\t}\n\t}\n}\n","/**\n * Wire-shape pricing fragment produced by {@link derivePriceFields}: the\n * `isForSale` flag and an optional numeric `price`. Mirrors the multipart\n * fields the Open Cloud `developer-products` create and update endpoints\n * accept for setting Robux pricing.\n */\nexport interface PriceFields {\n\t/** Whether the developer product should be purchasable. */\n\treadonly isForSale: boolean;\n\t/** Default price in Robux; absent when the product is off-sale. */\n\treadonly price?: number;\n}\n\n/**\n * Translate a Mantle-style optional price into the Open Cloud wire shape.\n *\n * `desired.price === undefined` (no price declared) becomes\n * `{ isForSale: false }` and the `price` key is omitted entirely. A defined\n * price (including `0`) becomes `{ isForSale: true, price }`. Both\n * `developerProduct` create and update paths share this helper so the\n * \"absent ⇒ off-sale\" semantics live in exactly one place.\n *\n * @param desired - Object carrying the user-declared `price`.\n * @returns The wire-shape `{ isForSale, price? }` fragment.\n *\n * @example\n *\n * ```ts\n * import { derivePriceFields } from \"@bedrock-rbx/core\";\n *\n * expect(derivePriceFields({ price: undefined })).toStrictEqual({ isForSale: false });\n * expect(derivePriceFields({ price: 250 })).toStrictEqual({ isForSale: true, price: 250 });\n * ```\n */\nexport function derivePriceFields(desired: { readonly price: number | undefined }): PriceFields {\n\tif (desired.price === undefined) {\n\t\treturn { isForSale: false };\n\t}\n\n\treturn { isForSale: true, price: desired.price };\n}\n","import type { DeveloperProduct } from \"@bedrock-rbx/ocale/developer-products\";\n\nimport type { DeveloperProductDesiredState } from \"./resources.ts\";\n\n/**\n * Optional follow-up PATCH body the developer-product driver issues after a\n * successful create POST when `storePageEnabled` was declared on desired\n * state. The Roblox v2 create endpoint does not accept `storePageEnabled`,\n * so the driver applies the flag in a PATCH after the POST returns.\n */\ninterface FollowUpPatchBody {\n\t/** The `storePageEnabled` value the driver should apply via PATCH. */\n\treadonly storePageEnabled: boolean;\n}\n\n/**\n * Plan the optional follow-up PATCH body needed after a developer-product\n * create POST. Returns `undefined` when no PATCH is required: either the\n * user did not declare `storePageEnabled`, or the create response already\n * matches the desired value.\n *\n * @param desired - Desired state for the developer product being created.\n * @param createResponse - The `storePageEnabled` value reported by the create POST response.\n * @returns The PATCH body to issue, or `undefined` when no follow-up is needed.\n */\nexport function planFollowUpPatch(\n\tdesired: DeveloperProductDesiredState,\n\tcreateResponse: Pick<DeveloperProduct, \"storePageEnabled\">,\n): FollowUpPatchBody | undefined {\n\tif (desired.storePageEnabled === undefined) {\n\t\treturn undefined;\n\t}\n\n\tif (desired.storePageEnabled === createResponse.storePageEnabled) {\n\t\treturn undefined;\n\t}\n\n\treturn { storePageEnabled: desired.storePageEnabled };\n}\n","import type { OpenCloudError, Result } from \"@bedrock-rbx/ocale\";\nimport type {\n\tDeveloperProduct,\n\tDeveloperProductsClient,\n} from \"@bedrock-rbx/ocale/developer-products\";\n\nimport { derivePriceFields } from \"../core/derive-price-fields.ts\";\nimport { shouldReuploadIcon } from \"../core/icons.ts\";\nimport { planFollowUpPatch } from \"../core/plan-follow-up-patch.ts\";\nimport { withRedactedIcon } from \"../core/redacted-icon.ts\";\nimport type { DeveloperProductDesiredState, ResourceCurrentState } from \"../core/resources.ts\";\nimport type { ResourceDriver } from \"../ports/resource-driver.ts\";\nimport { asRobloxAssetId, type RobloxAssetId } from \"../types/ids.ts\";\n\n/**\n * Dependencies of `createDeveloperProductDriver`. `universeId` is captured\n * at construction time (matching `GamePassDriverDeps`) so each driver\n * instance is bound to a single universe; multi-universe deploys construct\n * one driver per universe. `readFile` exists on the driver (not upstream\n * in shell) because icon hashes flow through `diff` but bytes do not.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { DeveloperProductsClient } from \"@bedrock-rbx/ocale/developer-products\";\n * import { asRobloxAssetId, type DeveloperProductDriverDeps } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return { data: { body: {}, headers: {}, status: 200 }, success: true };\n * },\n * };\n *\n * const deps: DeveloperProductDriverDeps = {\n * client: new DeveloperProductsClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () => new Uint8Array([0x89, 0x50, 0x4e, 0x47]),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * };\n *\n * expect(deps.universeId).toBe(\"1234567890\");\n * ```\n */\nexport interface DeveloperProductDriverDeps {\n\t/** Configured developer-products client from `@bedrock-rbx/ocale/developer-products`. */\n\treadonly client: DeveloperProductsClient;\n\t/** Reads icon bytes for upload; rejections propagate out of `create` and `update`. */\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\t/** Universe that owns every developer product this driver creates. */\n\treadonly universeId: RobloxAssetId;\n}\n\ninterface UpdateInputs {\n\treadonly current: ResourceCurrentState<\"developerProduct\">;\n\treadonly desired: DeveloperProductDesiredState;\n}\n\ninterface FollowUpPatchInputs {\n\treadonly created: DeveloperProduct;\n\treadonly desired: DeveloperProductDesiredState;\n}\n\n/**\n * Wraps {@link DeveloperProductsClient} as a `ResourceDriver<\"developerProduct\">`\n * that maps a desired-state entry to an ocale create or update call and the\n * response back to a `ResourceCurrentState<\"developerProduct\">`. The\n * `update` path consumes the upstream `204 No Content` response and\n * synthesizes the post-update `ResourceCurrentState` from `desired` plus\n * the existing `current.outputs`, carrying `iconImageAssetId` forward when\n * present.\n *\n * Upstream `OpenCloudError` results pass through as `Result` failures.\n *\n * @param deps - Injected ocale client and owning universe.\n * @returns A driver indexable by `\"developerProduct\"` in a `DriverRegistry`.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { DeveloperProductsClient } from \"@bedrock-rbx/ocale/developer-products\";\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * createDeveloperProductDriver,\n * } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return {\n * data: {\n * body: {\n * createdTimestamp: \"2024-01-15T10:30:00.000Z\",\n * description: \"Stocks the player up with 1,000 premium gems.\",\n * iconImageAssetId: null,\n * isForSale: false,\n * isImmutable: false,\n * name: \"Gem Pack\",\n * priceInformation: null,\n * productId: 9_876_543_210,\n * storePageEnabled: false,\n * universeId: 1_234_567_890,\n * updatedTimestamp: \"2024-01-15T10:30:00.000Z\",\n * },\n * headers: {},\n * status: 200,\n * },\n * success: true,\n * };\n * },\n * };\n *\n * const driver = createDeveloperProductDriver({\n * client: new DeveloperProductsClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () => new Uint8Array([0x89, 0x50, 0x4e, 0x47]),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * });\n *\n * return driver\n * .create({\n * description: \"Stocks the player up with 1,000 premium gems.\",\n * isRegionalPricingEnabled: undefined,\n * key: asResourceKey(\"gem-pack\"),\n * kind: \"developerProduct\",\n * name: \"Gem Pack\",\n * price: undefined,\n * storePageEnabled: undefined,\n * })\n * .then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.outputs.productId).toBe(\"9876543210\");\n * }\n * });\n * ```\n */\nexport function createDeveloperProductDriver(\n\tdeps: DeveloperProductDriverDeps,\n): ResourceDriver<\"developerProduct\"> {\n\tconst effective: DeveloperProductDriverDeps = {\n\t\t...deps,\n\t\treadFile: withRedactedIcon(deps.readFile),\n\t};\n\treturn {\n\t\tasync create(desired) {\n\t\t\treturn createOne(effective, desired);\n\t\t},\n\t\tasync update(current, desired) {\n\t\t\treturn updateOne(effective, { current, desired });\n\t\t},\n\t};\n}\n\nfunction toCurrentState(\n\tdesired: DeveloperProductDesiredState,\n\tdata: DeveloperProduct,\n): Result<ResourceCurrentState<\"developerProduct\">, OpenCloudError> {\n\tconst iconImageAssetId =\n\t\tdata.iconImageAssetId === undefined ? undefined : asRobloxAssetId(data.iconImageAssetId);\n\n\treturn {\n\t\tdata: {\n\t\t\t...desired,\n\t\t\toutputs: {\n\t\t\t\tproductId: asRobloxAssetId(data.id),\n\t\t\t\t...(iconImageAssetId === undefined ? {} : { iconImageAssetId }),\n\t\t\t},\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nasync function applyFollowUpPatch(\n\tdeps: DeveloperProductDriverDeps,\n\t{ created, desired }: FollowUpPatchInputs,\n): Promise<Result<ResourceCurrentState<\"developerProduct\">, OpenCloudError>> {\n\tconst followUp = planFollowUpPatch(desired, created);\n\tif (followUp === undefined) {\n\t\treturn toCurrentState(desired, created);\n\t}\n\n\tconst patched = await deps.client.update({\n\t\tproductId: asRobloxAssetId(created.id),\n\t\tuniverseId: deps.universeId,\n\t\t...followUp,\n\t});\n\tif (patched.success) {\n\t\treturn toCurrentState(desired, created);\n\t}\n\n\t// PATCH failed but the POST persisted the resource. Surface success\n\t// carrying the wire-reported storePageEnabled so state captures what\n\t// actually exists; the next deploy's diff sees the desired/current\n\t// mismatch and retries the PATCH (self-heal).\n\treturn toCurrentState({ ...desired, storePageEnabled: created.storePageEnabled }, created);\n}\n\nasync function createOne(\n\tdeps: DeveloperProductDriverDeps,\n\tdesired: DeveloperProductDesiredState,\n): Promise<Result<ResourceCurrentState<\"developerProduct\">, OpenCloudError>> {\n\tconst imageFile =\n\t\tdesired.icon === undefined ? undefined : await deps.readFile(desired.icon[\"en-us\"]);\n\tconst created = await deps.client.create({\n\t\tname: desired.name,\n\t\tdescription: desired.description,\n\t\tuniverseId: deps.universeId,\n\t\t...(imageFile === undefined ? {} : { imageFile }),\n\t\t...derivePriceFields(desired),\n\t\t...(desired.isRegionalPricingEnabled === undefined\n\t\t\t? {}\n\t\t\t: { isRegionalPricingEnabled: desired.isRegionalPricingEnabled }),\n\t});\n\tif (!created.success) {\n\t\treturn created;\n\t}\n\n\treturn applyFollowUpPatch(deps, { created: created.data, desired });\n}\n\nasync function updateOne(\n\tdeps: DeveloperProductDriverDeps,\n\t{ current, desired }: UpdateInputs,\n): Promise<Result<ResourceCurrentState<\"developerProduct\">, OpenCloudError>> {\n\tconst imageFile =\n\t\tdesired.icon !== undefined &&\n\t\tshouldReuploadIcon(current.iconFileHashes, desired.iconFileHashes)\n\t\t\t? await deps.readFile(desired.icon[\"en-us\"])\n\t\t\t: undefined;\n\tconst result = await deps.client.update({\n\t\tname: desired.name,\n\t\tdescription: desired.description,\n\t\tproductId: current.outputs.productId,\n\t\tuniverseId: deps.universeId,\n\t\t...(imageFile === undefined ? {} : { imageFile }),\n\t\t...derivePriceFields(desired),\n\t\t...(desired.isRegionalPricingEnabled === undefined\n\t\t\t? {}\n\t\t\t: { isRegionalPricingEnabled: desired.isRegionalPricingEnabled }),\n\t\t...(desired.storePageEnabled === undefined\n\t\t\t? {}\n\t\t\t: { storePageEnabled: desired.storePageEnabled }),\n\t});\n\tif (!result.success) {\n\t\treturn result;\n\t}\n\n\t// The PATCH endpoint returns 204; the post-update state is the desired\n\t// entry composed with the existing Roblox-assigned outputs.\n\treturn { data: { ...desired, outputs: current.outputs }, success: true };\n}\n","import { ApiError, type OpenCloudError, type Result } from \"@bedrock-rbx/ocale\";\nimport type { GamePass, GamePassesClient } from \"@bedrock-rbx/ocale/game-passes\";\n\nimport { derivePriceFields } from \"../core/derive-price-fields.ts\";\nimport { shouldReuploadIcon } from \"../core/icons.ts\";\nimport { withRedactedIcon } from \"../core/redacted-icon.ts\";\nimport type { GamePassDesiredState, ResourceCurrentState } from \"../core/resources.ts\";\nimport type { ResourceDriver } from \"../ports/resource-driver.ts\";\nimport { asRobloxAssetId, type RobloxAssetId } from \"../types/ids.ts\";\n\n/**\n * `universeId` is captured at construction time rather than on\n * `GamePassDesiredState` so state files round-trip with Mantle's `PassInputs`\n * shape. `readFile` exists on the driver (not upstream in shell) because icon\n * hashes flow through `diff` but bytes do not.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { GamePassesClient } from \"@bedrock-rbx/ocale/game-passes\";\n * import { asRobloxAssetId, type GamePassDriverDeps } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return { data: { body: {}, headers: {}, status: 200 }, success: true };\n * },\n * };\n *\n * const deps: GamePassDriverDeps = {\n * client: new GamePassesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () => new Uint8Array([0x89, 0x50, 0x4e, 0x47]),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * };\n *\n * expect(deps.universeId).toBe(\"1234567890\");\n * ```\n */\nexport interface GamePassDriverDeps {\n\t/** Configured game-passes client from `@bedrock-rbx/ocale/game-passes`. */\n\treadonly client: GamePassesClient;\n\t/** Reads icon bytes for upload; rejections propagate out of `create`. */\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\t/** Universe that owns every game pass this driver creates. */\n\treadonly universeId: RobloxAssetId;\n}\n\n/**\n * Wraps {@link GamePassesClient} as a `ResourceDriver<\"gamePass\">` that maps\n * a desired-state entry to an ocale create call and the response back to a\n * `ResourceCurrentState<\"gamePass\">`.\n *\n * Upstream `OpenCloudError` results pass through as `Result` failures.\n * Filesystem errors from `deps.readFile` do not fit the `OpenCloudError`\n * shape and propagate as promise rejections; shell callers are expected to\n * translate them if a unified error surface is required.\n *\n * @param deps - Injected ocale client, file reader, and owning universe.\n * @returns A driver indexable by `\"gamePass\"` in a `DriverRegistry`.\n * @throws Whatever `deps.readFile` rejects with.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { GamePassesClient } from \"@bedrock-rbx/ocale/game-passes\";\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * asSha256Hex,\n * createGamePassDriver,\n * } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return {\n * data: {\n * body: {\n * createdTimestamp: \"2024-01-15T10:30:00.000Z\",\n * description: \"Grants VIP perks.\",\n * gamePassId: 9_876_543_210,\n * iconAssetId: 1_122_334_455,\n * isForSale: true,\n * name: \"VIP Pass\",\n * updatedTimestamp: \"2024-01-15T10:30:00.000Z\",\n * },\n * headers: {},\n * status: 200,\n * },\n * success: true,\n * };\n * },\n * };\n *\n * const driver = createGamePassDriver({\n * client: new GamePassesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () => new Uint8Array([0x89, 0x50, 0x4e, 0x47]),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * });\n *\n * return driver\n * .create({\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * iconFileHashes: {\n * \"en-us\": asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * ),\n * },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * price: 500,\n * })\n * .then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.outputs.assetId).toBe(\"9876543210\");\n * }\n * });\n * ```\n */\nexport function createGamePassDriver(deps: GamePassDriverDeps): ResourceDriver<\"gamePass\"> {\n\tconst effective: GamePassDriverDeps = {\n\t\t...deps,\n\t\treadFile: withRedactedIcon(deps.readFile),\n\t};\n\treturn {\n\t\tasync create(desired) {\n\t\t\treturn createGamePass(effective, desired);\n\t\t},\n\t\tasync update(current, desired) {\n\t\t\treturn updateGamePass(effective, { current, desired });\n\t\t},\n\t};\n}\n\nfunction toCurrentState(\n\tdesired: GamePassDesiredState,\n\tdata: GamePass,\n): Result<ResourceCurrentState<\"gamePass\">, OpenCloudError> {\n\tconst { id, iconAssetId } = data;\n\tif (iconAssetId === undefined) {\n\t\treturn {\n\t\t\terr: new ApiError(\n\t\t\t\t\"Malformed game pass response: iconAssetId missing after icon upload\",\n\t\t\t\t{ statusCode: 200 },\n\t\t\t),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\t...desired,\n\t\t\toutputs: {\n\t\t\t\tassetId: asRobloxAssetId(id),\n\t\t\t\ticonAssetIds: { \"en-us\": asRobloxAssetId(iconAssetId) },\n\t\t\t},\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nasync function createGamePass(\n\tdeps: GamePassDriverDeps,\n\tdesired: GamePassDesiredState,\n): Promise<Result<ResourceCurrentState<\"gamePass\">, OpenCloudError>> {\n\tconst imageFile = await deps.readFile(desired.icon[\"en-us\"]);\n\tconst result = await deps.client.create({\n\t\tname: desired.name,\n\t\tdescription: desired.description,\n\t\timageFile,\n\t\tuniverseId: deps.universeId,\n\t\t...(desired.price !== undefined ? { price: desired.price } : {}),\n\t});\n\tif (!result.success) {\n\t\treturn result;\n\t}\n\n\treturn toCurrentState(desired, result.data);\n}\n\nasync function resolveUpdatedState(\n\tdeps: GamePassDriverDeps,\n\tcontext: {\n\t\treadonly current: ResourceCurrentState<\"gamePass\">;\n\t\treadonly desired: GamePassDesiredState;\n\t\treadonly hasIconChanged: boolean;\n\t},\n): Promise<Result<ResourceCurrentState<\"gamePass\">, OpenCloudError>> {\n\tconst { current, desired, hasIconChanged } = context;\n\tif (!hasIconChanged) {\n\t\treturn { data: { ...desired, outputs: current.outputs }, success: true };\n\t}\n\n\tconst fetched = await deps.client.get({\n\t\tgamePassId: current.outputs.assetId,\n\t\tuniverseId: deps.universeId,\n\t});\n\tif (!fetched.success) {\n\t\treturn fetched;\n\t}\n\n\treturn toCurrentState(desired, fetched.data);\n}\n\nasync function updateGamePass(\n\tdeps: GamePassDriverDeps,\n\tstates: {\n\t\treadonly current: ResourceCurrentState<\"gamePass\">;\n\t\treadonly desired: GamePassDesiredState;\n\t},\n): Promise<Result<ResourceCurrentState<\"gamePass\">, OpenCloudError>> {\n\tconst { current, desired } = states;\n\tconst hasIconChanged = shouldReuploadIcon(current.iconFileHashes, desired.iconFileHashes);\n\tconst imageFile = hasIconChanged ? await deps.readFile(desired.icon[\"en-us\"]) : undefined;\n\n\tconst result = await deps.client.update({\n\t\tname: desired.name,\n\t\tdescription: desired.description,\n\t\tgamePassId: current.outputs.assetId,\n\t\tuniverseId: deps.universeId,\n\t\t...derivePriceFields(desired),\n\t\t...(imageFile !== undefined ? { imageFile } : {}),\n\t});\n\tif (!result.success) {\n\t\treturn result;\n\t}\n\n\treturn resolveUpdatedState(deps, { current, desired, hasIconChanged });\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { ArkErrors, type } from \"arktype\";\n\nimport type { ResourceCurrentState } from \"./resources.ts\";\nimport type { BedrockState, StateError } from \"./state.ts\";\n\n// Resource-level validation is intentionally shallow: every resource must be\n// an object with a known `kind` discriminator, but per-kind field validation\n// (game-pass vs place vs universe) is deferred. Bedrock is both the writer\n// and the reader of state files, so tampering is out of scope for v0.1;\n// a follow-up issue widens this to full per-kind schema + brand narrowing.\nconst resourceShape = type({\n\t\"key\": \"string\",\n\t\"[string]\": \"unknown\",\n\t\"kind\": \"'developerProduct' | 'gamePass' | 'place' | 'universe'\",\n\t\"outputs\": \"object\",\n});\n\nconst envelopeSchema = type({\n\t$bedrock: { version: \"1\" },\n\tenvironment: \"string\",\n\tresources: resourceShape.array(),\n});\n\n/**\n * Serialize a {@link BedrockState} to the on-disk JSON representation used by\n * state-port adapters.\n *\n * The on-disk shape wraps the in-memory state with a\n * `$bedrock: { version: N }` envelope so that a future breaking change to the\n * schema can be detected and rejected at parse time rather than silently\n * accepted. The top-level `version` field is not duplicated on disk.\n *\n * @example\n *\n * ```ts\n * import { serializeStateFile, type BedrockState } from \"@bedrock-rbx/core\";\n *\n * const state: BedrockState = {\n * environment: \"production\",\n * resources: [],\n * version: 1,\n * };\n *\n * const wire = serializeStateFile(state);\n * expect(JSON.parse(wire)).toStrictEqual({\n * $bedrock: { version: 1 },\n * environment: \"production\",\n * resources: [],\n * });\n * ```\n *\n * @param state - The in-memory state snapshot to serialize.\n * @returns A pretty-printed JSON string ready to hand to a state adapter's write method.\n */\nexport function serializeStateFile(state: BedrockState): string {\n\tconst envelope = {\n\t\t$bedrock: { version: state.version },\n\t\tenvironment: state.environment,\n\t\tresources: state.resources,\n\t};\n\treturn JSON.stringify(envelope, undefined, 2);\n}\n\n/**\n * Parse a raw on-disk state file into a {@link BedrockState}.\n *\n * A backend that reports \"no state file for this environment yet\" must pass\n * `undefined`: that distinguishes a legitimate first deploy from a file that\n * exists but cannot be trusted.\n *\n * @example\n *\n * ```ts\n * import { parseStateFile } from \"@bedrock-rbx/core\";\n *\n * const freshStart = parseStateFile(undefined, \"gist:abc123/state.production.json\");\n * expect(freshStart.success).toBeTrue();\n * if (freshStart.success) {\n * expect(freshStart.data).toBeUndefined();\n * }\n * ```\n *\n * @param raw - Raw file contents as a string, or `undefined` when the\n * backend reports no file exists yet.\n * @param file - Adapter-specific identifier included in any `StateError`\n * surfaced during parsing.\n * @returns `Ok(undefined)` for a missing file, `Ok(state)` for a parseable\n * file, or `Err(StateError)` for anything that cannot be trusted.\n */\nexport function parseStateFile(\n\traw: string | undefined,\n\tfile: string,\n): Result<BedrockState | undefined, StateError> {\n\tif (raw === undefined) {\n\t\treturn { data: undefined, success: true };\n\t}\n\n\tconst parsed = parseJson(raw, file);\n\tif (!parsed.success) {\n\t\treturn parsed;\n\t}\n\n\tconst validated = envelopeSchema(parsed.data);\n\tif (validated instanceof ArkErrors) {\n\t\treturn errState(file, `invalid state file: ${validated.summary}`);\n\t}\n\n\tconst resources = validated.resources as unknown as ReadonlyArray<ResourceCurrentState>;\n\treturn {\n\t\tdata: { environment: validated.environment, resources, version: 1 },\n\t\tsuccess: true,\n\t};\n}\n\nfunction parseJson(raw: string, file: string): Result<JSONValue, StateError> {\n\ttry {\n\t\treturn { data: JSON.parse(raw), success: true };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn {\n\t\t\terr: { file, kind: \"stateError\", reason: `malformed JSON: ${message}` },\n\t\t\tsuccess: false,\n\t\t};\n\t}\n}\n\nfunction errState(file: string, reason: string): Result<BedrockState | undefined, StateError> {\n\treturn {\n\t\terr: { file, kind: \"stateError\", reason },\n\t\tsuccess: false,\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { validateEnvironmentName } from \"../core/environment.ts\";\nimport { parseStateFile, serializeStateFile } from \"../core/state-file.ts\";\nimport type { BedrockState, StateError } from \"../core/state.ts\";\nimport type { StatePort } from \"../ports/state-port.ts\";\n\nconst GITHUB_API_BASE = \"https://api.github.com\";\nconst GITHUB_API_VERSION = \"2026-03-10\";\nconst USER_AGENT = \"bedrock\";\nconst MAX_INLINE_BYTES = 10_000_000;\nconst MAX_RETRIES = 6;\nconst BASE_BACKOFF_MS = 500;\nconst MAX_BACKOFF_MS = 16_000;\nconst RETRYABLE_STATUSES: ReadonlySet<number> = new Set([409, 502, 503, 504]);\nconst MAX_VISIBILITY_ATTEMPTS = 5;\nconst VISIBILITY_BASE_DELAY_MS = 250;\n\n/**\n * Minimal `fetch`-compatible signature the adapter needs, narrower than\n * `typeof globalThis.fetch` so test fakes do not have to stub runtime\n * extensions such as `fetch.preconnect`.\n */\nexport type GistFetch = (\n\tinput: globalThis.Request | string | URL,\n\tinit?: RequestInit,\n) => Promise<Response>;\n\n/**\n * Configuration for {@link createGistStateAdapter}.\n */\nexport interface GistStateAdapterDeps {\n\t/** Injection seam for tests; defaults to `globalThis.fetch`. */\n\treadonly fetch?: GistFetch | undefined;\n\t/** ID of an existing GitHub Gist that holds this project's state files. */\n\treadonly gistId: string;\n\t/**\n\t * Injection seam for retry jitter; defaults to `Math.random`. Tests pass a\n\t * deterministic source so jittered sleep durations stay stable across runs.\n\t * Jitter prevents concurrent callers (parallel CI jobs writing to the same\n\t * gist) from retrying in lockstep and re-colliding on each backoff.\n\t */\n\treadonly random?: (() => number) | undefined;\n\t/**\n\t * Injection seam for retry backoff timing; defaults to a `setTimeout`-based\n\t * promise. Tests pass a fake to keep retry assertions deterministic.\n\t */\n\treadonly sleep?: ((ms: number) => Promise<void>) | undefined;\n\t/** GitHub token (fine-grained PAT or classic PAT) with gist read/write scope. */\n\treadonly token: string;\n}\n\ninterface AdapterContext {\n\treadonly fetchFn: GistFetch;\n\treadonly gistId: string;\n\treadonly random: () => number;\n\treadonly sleep: (ms: number) => Promise<void>;\n\treadonly token: string;\n}\n\ninterface GistFile {\n\treadonly content: string | undefined;\n\treadonly isTruncated: boolean;\n\treadonly rawUrl: string | undefined;\n\treadonly size: number;\n}\n\ninterface HttpFailure {\n\treadonly file: string;\n\treadonly gistId: string;\n\treadonly response: Response;\n}\n\ninterface RetryDeps {\n\treadonly random: () => number;\n\treadonly sleep: (ms: number) => Promise<void>;\n}\n\ninterface ReadContentParameters {\n\treadonly entry: GistFile;\n\treadonly fetchFn: GistFetch;\n\treadonly file: string;\n\treadonly retry: RetryDeps;\n}\n\n/**\n * Build a `StatePort` that persists Bedrock state in a GitHub Gist.\n *\n * One gist holds one file per environment, named `state.<env>.json`. The\n * adapter authenticates with a user-supplied token and speaks the GitHub\n * REST API directly; no SDK dependency.\n *\n * @example\n *\n * ```ts\n * import { createGistStateAdapter } from \"@bedrock-rbx/core\";\n *\n * const port = createGistStateAdapter({\n * fetch: async () =>\n * new Response(JSON.stringify({ files: {} }), { status: 200 }),\n * gistId: \"abc123def456\",\n * token: \"ghp_example\",\n * });\n *\n * return port.read(\"production\").then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data).toBeUndefined();\n * }\n * });\n * ```\n *\n * @param deps - Gist ID, GitHub token, and optional fetch override.\n * @returns A `StatePort` ready to be passed to `deploy()`.\n */\nexport function createGistStateAdapter(deps: GistStateAdapterDeps): StatePort {\n\tconst ctx: AdapterContext = {\n\t\tfetchFn: deps.fetch ?? globalThis.fetch.bind(globalThis),\n\t\tgistId: deps.gistId,\n\t\trandom: deps.random ?? Math.random,\n\t\tsleep: deps.sleep ?? defaultSleep,\n\t\ttoken: deps.token,\n\t};\n\n\treturn {\n\t\tasync read(environment) {\n\t\t\tconst safe = validateEnvironmentName(environment);\n\t\t\tif (!safe.success) {\n\t\t\t\treturn safe;\n\t\t\t}\n\n\t\t\treturn readPath(ctx, safe.data);\n\t\t},\n\t\tasync write(state) {\n\t\t\tconst safe = validateEnvironmentName(state.environment);\n\t\t\tif (!safe.success) {\n\t\t\t\treturn safe;\n\t\t\t}\n\n\t\t\treturn writePath(ctx, state);\n\t\t},\n\t};\n}\n\nasync function defaultSleep(ms: number): Promise<void> {\n\tawait new Promise<void>((resolve) => {\n\t\tsetTimeout(resolve, ms);\n\t});\n}\n\nfunction fileLabel(gistId: string, environment: string): string {\n\treturn `gist:${gistId}/state.${environment}.json`;\n}\n\nfunction fileName(environment: string): string {\n\treturn `state.${environment}.json`;\n}\n\nfunction toGistFile(entry: unknown): GistFile | undefined {\n\tif (typeof entry !== \"object\" || entry === null) {\n\t\treturn undefined;\n\t}\n\n\tconst record = entry as Record<string, unknown>;\n\tconst content = typeof record[\"content\"] === \"string\" ? record[\"content\"] : undefined;\n\tconst rawUrl = typeof record[\"raw_url\"] === \"string\" ? record[\"raw_url\"] : undefined;\n\tconst size = typeof record[\"size\"] === \"number\" ? record[\"size\"] : 0;\n\tconst isTruncated = record[\"truncated\"] === true;\n\treturn { content, isTruncated, rawUrl, size };\n}\n\nfunction isRateLimited(headers: Headers): boolean {\n\treturn headers.get(\"retry-after\") !== null || headers.get(\"x-ratelimit-remaining\") === \"0\";\n}\n\nfunction rateLimitReason(status: number, headers: Headers): string {\n\tconst retryAfter = headers.get(\"retry-after\");\n\tif (retryAfter !== null) {\n\t\treturn `rate limited (${status}): retry after ${retryAfter}s`;\n\t}\n\n\treturn `rate limited (${status})`;\n}\n\nfunction mapHttpError({ file, gistId, response }: HttpFailure): StateError {\n\tconst { headers, status } = response;\n\tif (status === 404) {\n\t\treturn { file, kind: \"stateError\", reason: `gist ${gistId} not found: check gistId` };\n\t}\n\n\tif (status === 403 && isRateLimited(headers)) {\n\t\treturn { file, kind: \"stateError\", reason: rateLimitReason(status, headers) };\n\t}\n\n\tif (status === 401 || status === 403) {\n\t\treturn { file, kind: \"stateError\", reason: `auth failed (${status}): check token scopes` };\n\t}\n\n\treturn { file, kind: \"stateError\", reason: `github returned ${status}` };\n}\n\nfunction networkError(error: unknown, file: string): StateError {\n\tconst message = error instanceof Error ? error.message : String(error);\n\treturn { file, kind: \"stateError\", reason: `network error: ${message}` };\n}\n\nfunction buildHeaders(token: string): Headers {\n\tconst headers = new Headers();\n\theaders.set(\"Accept\", \"application/vnd.github+json\");\n\theaders.set(\"Authorization\", `Bearer ${token}`);\n\theaders.set(\"User-Agent\", USER_AGENT);\n\theaders.set(\"X-GitHub-Api-Version\", GITHUB_API_VERSION);\n\treturn headers;\n}\n\nasync function sendGet(ctx: AdapterContext): Promise<Response> {\n\treturn ctx.fetchFn(`${GITHUB_API_BASE}/gists/${ctx.gistId}`, {\n\t\theaders: buildHeaders(ctx.token),\n\t\tmethod: \"GET\",\n\t});\n}\n\nfunction isRetryableStatus(status: number): boolean {\n\treturn RETRYABLE_STATUSES.has(status);\n}\n\nfunction backoffMs(attempt: number, random: () => number): number {\n\tconst cap = Math.min(MAX_BACKOFF_MS, BASE_BACKOFF_MS * 2 ** attempt);\n\tconst half = cap / 2;\n\treturn half + random() * half;\n}\n\nasync function withRetry(retry: RetryDeps, operation: () => Promise<Response>): Promise<Response> {\n\tlet response = await operation();\n\tfor (let attempt = 0; attempt < MAX_RETRIES; attempt += 1) {\n\t\tif (response.ok || !isRetryableStatus(response.status)) {\n\t\t\treturn response;\n\t\t}\n\n\t\tawait retry.sleep(backoffMs(attempt, retry.random));\n\t\tresponse = await operation();\n\t}\n\n\treturn response;\n}\n\nasync function fetchGistBody(\n\tctx: AdapterContext,\n\tfile: string,\n): Promise<Result<Record<string, unknown>, StateError>> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await withRetry(ctx, async () => sendGet(ctx));\n\t} catch (err) {\n\t\treturn { err: networkError(err, file), success: false };\n\t}\n\n\tif (!response.ok) {\n\t\treturn {\n\t\t\terr: mapHttpError({ file, gistId: ctx.gistId, response }),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\tconst body = (await response.json()) as Record<string, unknown>;\n\treturn { data: body, success: true };\n}\n\nfunction stateErr<T>(file: string, reason: string): Result<T, StateError> {\n\treturn { err: { file, kind: \"stateError\", reason }, success: false };\n}\n\nasync function readGistContent({\n\tentry,\n\tfetchFn,\n\tfile,\n\tretry,\n}: ReadContentParameters): Promise<Result<BedrockState | undefined, StateError>> {\n\tif (entry.size > MAX_INLINE_BYTES) {\n\t\treturn stateErr(file, `state file too large: ${entry.size} bytes`);\n\t}\n\n\tif (entry.isTruncated) {\n\t\tif (entry.rawUrl === undefined) {\n\t\t\treturn stateErr(file, \"truncated gist file missing raw_url\");\n\t\t}\n\n\t\tconst { rawUrl } = entry;\n\t\tlet rawResponse: Response;\n\t\ttry {\n\t\t\trawResponse = await withRetry(retry, async () => fetchFn(rawUrl));\n\t\t} catch (err) {\n\t\t\treturn { err: networkError(err, file), success: false };\n\t\t}\n\n\t\tif (!rawResponse.ok) {\n\t\t\treturn stateErr(file, `raw_url fetch returned ${rawResponse.status}`);\n\t\t}\n\n\t\tconst raw = await rawResponse.text();\n\t\treturn parseStateFile(raw, file);\n\t}\n\n\treturn parseStateFile(entry.content, file);\n}\n\nasync function readPath(\n\tctx: AdapterContext,\n\tenvironment: string,\n): Promise<Result<BedrockState | undefined, StateError>> {\n\tconst file = fileLabel(ctx.gistId, environment);\n\tconst gist = await fetchGistBody(ctx, file);\n\tif (!gist.success) {\n\t\treturn gist;\n\t}\n\n\tconst files = gist.data[\"files\"] as Record<string, unknown> | undefined;\n\tconst entry = toGistFile(files?.[fileName(environment)]);\n\tif (entry === undefined) {\n\t\treturn { data: undefined, success: true };\n\t}\n\n\treturn readGistContent({ entry, fetchFn: ctx.fetchFn, file, retry: ctx });\n}\n\nasync function sendPatch(ctx: AdapterContext, body: string): Promise<Response> {\n\tconst headers = buildHeaders(ctx.token);\n\theaders.set(\"Content-Type\", \"application/json\");\n\treturn ctx.fetchFn(`${GITHUB_API_BASE}/gists/${ctx.gistId}`, {\n\t\tbody,\n\t\theaders,\n\t\tmethod: \"PATCH\",\n\t});\n}\n\nasync function isFileVisible(ctx: AdapterContext, target: string): Promise<boolean> {\n\ttry {\n\t\tconst response = await sendGet(ctx);\n\t\tconst body = JSON.parse(await response.text());\n\t\tconst files = Reflect.get(body, \"files\");\n\t\treturn typeof files === \"object\" && files !== null && target in files;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Polls the gist until the just-written environment file is visible on a\n * GET, with bounded retries. GitHub's gist API does not guarantee\n * read-your-write across replicas: a GET issued immediately after a\n * successful PATCH can return a body that omits the new file. The poll\n * pre-warms the cache the consumer's next read will hit, so a successful\n * write honours read-after-write at the port boundary.\n *\n * Best-effort: resolves after exhausting the visibility budget regardless\n * of whether the file became visible. The PATCH already committed; the\n * poll only narrows the window in which subsequent reads can lag.\n *\n * @param ctx - Adapter context carrying the injected fetch and sleep seams.\n * @param environment - Environment name whose file is being verified.\n */\nasync function waitForFileVisibility(ctx: AdapterContext, environment: string): Promise<void> {\n\tconst target = fileName(environment);\n\n\tfor (let attempt = 0; attempt < MAX_VISIBILITY_ATTEMPTS; attempt += 1) {\n\t\tif (await isFileVisible(ctx, target)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (attempt < MAX_VISIBILITY_ATTEMPTS - 1) {\n\t\t\tawait ctx.sleep(VISIBILITY_BASE_DELAY_MS * 2 ** attempt);\n\t\t}\n\t}\n}\n\nasync function writePath(\n\tctx: AdapterContext,\n\tstate: BedrockState,\n): Promise<Result<void, StateError>> {\n\tconst file = fileLabel(ctx.gistId, state.environment);\n\tconst body = JSON.stringify({\n\t\tfiles: { [fileName(state.environment)]: { content: serializeStateFile(state) } },\n\t});\n\n\tlet response: Response;\n\ttry {\n\t\tresponse = await withRetry(ctx, async () => sendPatch(ctx, body));\n\t} catch (err) {\n\t\treturn { err: networkError(err, file), success: false };\n\t}\n\n\tif (response.ok) {\n\t\ttry {\n\t\t\tawait waitForFileVisibility(ctx, state.environment);\n\t\t} catch {\n\t\t\t/* visibility poll errors are non-fatal; the PATCH already committed. */\n\t\t}\n\n\t\treturn { data: undefined, success: true };\n\t}\n\n\tif (response.status === 422) {\n\t\treturn stateErr(file, \"invalid PATCH body sent to github\");\n\t}\n\n\treturn {\n\t\terr: mapHttpError({ file, gistId: ctx.gistId, response }),\n\t\tsuccess: false,\n\t};\n}\n","import type { ProgressPort } from \"../ports/progress-port.ts\";\n\n/**\n * Build a {@link ProgressPort} that silently drops every event. Useful for\n * tests and programmatic callers who want to invoke deploy logic without\n * any rendering.\n *\n * @example\n *\n * ```ts\n * import { createNoOpProgressAdapter } from \"@bedrock-rbx/core\";\n *\n * const port = createNoOpProgressAdapter();\n *\n * expect(() =>\n * port.emit({ environment: \"production\", kind: \"deploySuccess\", resourceCount: 3 }),\n * ).not.toThrow();\n * ```\n *\n * @returns A `ProgressPort` whose `emit` method is a no-op.\n */\nexport function createNoOpProgressAdapter(): ProgressPort {\n\treturn {\n\t\temit() {\n\t\t\t/* no-op */\n\t\t},\n\t};\n}\n","import type { SocialLink } from \"@bedrock-rbx/ocale/universes\";\n\nimport {\n\tasResourceKey,\n\ttype ResourceKey,\n\ttype RobloxAssetId,\n\ttype Sha256Hex,\n} from \"../types/ids.ts\";\n\n/**\n * Desired state for a game pass, as declared in user config.\n *\n * Each field is `readonly` because desired state is treated as an immutable\n * snapshot once normalized by `buildDesired`; downstream consumers (`diff`,\n * drivers) must not mutate it.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, asSha256Hex, type GamePassDesiredState } from \"@bedrock-rbx/core\";\n *\n * const pass: GamePassDesiredState = {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * iconFileHashes: {\n * \"en-us\": asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * ),\n * },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * price: undefined,\n * };\n *\n * expect(pass.kind).toBe(\"gamePass\");\n * expect(pass.price).toBeUndefined();\n * ```\n */\nexport interface GamePassDesiredState {\n\t/** User-supplied key; stable across deploys; used to correlate desired with current. */\n\treadonly key: ResourceKey;\n\t/** User-facing game-pass name as shown on the Roblox storefront. */\n\treadonly name: string;\n\t/** User-facing description shown on the game-pass detail page. */\n\treadonly description: string;\n\t/**\n\t * Locale-keyed icon paths declared on the authored config. The Roblox\n\t * game-pass API is monolingual, so only the `\"en-us\"` icon is ever\n\t * uploaded; the map shape mirrors `UniverseDesiredState.icon` for\n\t * cross-kind parity.\n\t */\n\treadonly icon: Record<\"en-us\", string>;\n\t/**\n\t * SHA-256 digests of the local icon files keyed by the same locales as\n\t * the icon map. The diff compares this map against the prior current\n\t * state so the driver re-uploads only when a file's bytes change.\n\t */\n\treadonly iconFileHashes: Record<\"en-us\", Sha256Hex>;\n\t/** Discriminator tag for the `ResourceDesiredState` union. */\n\treadonly kind: \"gamePass\";\n\t/**\n\t * Robux price. `undefined` means off-sale (mirrors Mantle's `Option<u32>`;\n\t * the state parser normalizes JSON `null` to `undefined` at the wire\n\t * boundary per the project type convention).\n\t */\n\treadonly price: number | undefined;\n}\n\n/**\n * Desired state for a place, the `.rbxl` or `.rbxlx` file a universe serves\n * as one of its levels.\n *\n * `placeId` sits on desired state (rather than on outputs like\n * {@link GamePassOutputs.assetId}) because Roblox Open Cloud cannot mint\n * places; the user supplies the existing place ID per entry. `filePath` and\n * `fileHash` describe the local file the driver publishes; `buildDesired`\n * computes `fileHash` from the file bytes so `diff` can detect drift without\n * re-uploading unchanged content. `displayName`, `description`, and\n * `serverSize` are optional metadata fields routed through\n * `PlacesClient.update`; `undefined` leaves the server value untouched.\n *\n * @example\n *\n * ```ts\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * asSha256Hex,\n * type PlaceDesiredState,\n * } from \"@bedrock-rbx/core\";\n *\n * const place: PlaceDesiredState = {\n * description: undefined,\n * displayName: \"Start Place\",\n * fileHash: asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * ),\n * filePath: \"places/start.rbxl\",\n * key: asResourceKey(\"start-place\"),\n * kind: \"place\",\n * placeId: asRobloxAssetId(\"4711\"),\n * serverSize: 50,\n * };\n *\n * expect(place.kind).toBe(\"place\");\n * expect(place.displayName).toBe(\"Start Place\");\n * expect(place.description).toBeUndefined();\n * expect(place.serverSize).toBe(50);\n * ```\n */\nexport interface PlaceDesiredState {\n\t/** User-supplied key; stable across deploys; used to correlate desired with current. */\n\treadonly key: ResourceKey;\n\t/** User-facing place description; `undefined` leaves the server value untouched. */\n\treadonly description: string | undefined;\n\t/** User-facing place name; `undefined` leaves the server value untouched. */\n\treadonly displayName: string | undefined;\n\t/** SHA-256 hex digest of the place file, computed by `buildDesired` in shell. */\n\treadonly fileHash: Sha256Hex;\n\t/** Path to the `.rbxl` or `.rbxlx` file on disk, relative to the config file. */\n\treadonly filePath: string;\n\t/** Discriminator tag for the `ResourceDesiredState` union. */\n\treadonly kind: \"place\";\n\t/** Existing Roblox place ID; Open Cloud cannot create places, so this is an input, not an output. */\n\treadonly placeId: RobloxAssetId;\n\t/** Maximum players per server; positive integer. `undefined` leaves the server value untouched. */\n\treadonly serverSize: number | undefined;\n}\n\n/**\n * Ordered list of optional metadata fields the driver routes through\n * `PlacesClient.update`. Iterated by `placeKind.fieldsEqual` and the place\n * driver's parameter translator so drift detection and the constructed\n * `updateMask` cannot drift apart.\n */\nexport const PLACE_MANAGED_METADATA_FIELDS = [\n\t\"displayName\",\n\t\"description\",\n\t\"serverSize\",\n] as const satisfies ReadonlyArray<keyof PlaceDesiredState>;\n\n/**\n * Roblox-returned value produced by publishing a place version. The publish\n * endpoint does not return an asset ID (the `placeId` is supplied by the\n * caller); `versionNumber` is the only Roblox-assigned field the response\n * carries.\n */\nexport interface PlaceOutputs {\n\t/** Auto-incrementing version number assigned by Roblox on every publish. */\n\treadonly versionNumber: number;\n}\n\n/**\n * Desired state for the singleton universe a config manages.\n *\n * The universe is adopted rather than provisioned: the user supplies an\n * existing `universeId` (Open Cloud cannot mint universes) and bedrock\n * reconciles the declared managed fields against it.\n *\n * Most managed fields use `T | undefined` to mean \"unmanaged\": the diff\n * treats `undefined` as absent and the driver omits the field from the\n * `updateMask`. The clearable fields (`privateServerPriceRobux` and each\n * social link) are additionally key-presence aware: a present key with\n * `undefined` tells the driver to clear the server value (ocale emits\n * JSON `null`), while an absent key leaves the server value untouched.\n *\n * @example\n *\n * ```ts\n * import {\n * asRobloxAssetId,\n * UNIVERSE_SINGLETON_KEY,\n * type UniverseDesiredState,\n * } from \"@bedrock-rbx/core\";\n *\n * const universe: UniverseDesiredState = {\n * consoleEnabled: undefined,\n * desktopEnabled: true,\n * discordSocialLink: { title: \"Join our Discord\", uri: \"https://discord.gg/example\" },\n * displayName: \"Fun Universe\",\n * key: UNIVERSE_SINGLETON_KEY,\n * kind: \"universe\",\n * mobileEnabled: false,\n * privateServerPriceRobux: undefined,\n * tabletEnabled: undefined,\n * twitterSocialLink: undefined,\n * universeId: asRobloxAssetId(\"1234567890\"),\n * voiceChatEnabled: true,\n * vrEnabled: undefined,\n * };\n *\n * expect(universe.kind).toBe(\"universe\");\n * expect(\"privateServerPriceRobux\" in universe).toBeTrue();\n * expect(universe.discordSocialLink?.title).toBe(\"Join our Discord\");\n * expect(universe.twitterSocialLink).toBeUndefined();\n * ```\n */\nexport interface UniverseDesiredState {\n\t/** Fixed singleton key (`\"main\"`); bedrock synthesizes it in `flattenConfig`. */\n\treadonly key: ResourceKey;\n\t/** Whether console players can join; `undefined` leaves the server value untouched. */\n\treadonly consoleEnabled: boolean | undefined;\n\t/** Whether desktop players can join; `undefined` leaves the server value untouched. */\n\treadonly desktopEnabled: boolean | undefined;\n\t/** Discord social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly discordSocialLink?: SocialLink | undefined;\n\t/**\n\t * Display name for the universe. `undefined` leaves the server\n\t * value untouched. The driver routes declared updates through\n\t * `PlacesClient.update` because the universe PATCH endpoint treats\n\t * `displayName` as read-only.\n\t */\n\treadonly displayName: string | undefined;\n\t/** Facebook social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly facebookSocialLink?: SocialLink | undefined;\n\t/** Guilded social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly guildedSocialLink?: SocialLink | undefined;\n\t/** Discriminator tag for the `ResourceDesiredState` union. */\n\treadonly kind: \"universe\";\n\t/** Whether mobile players can join; `undefined` leaves the server value untouched. */\n\treadonly mobileEnabled: boolean | undefined;\n\t/**\n\t * Private-server price in Robux. A present key with `undefined`\n\t * clears the server value on apply; an absent key leaves the server\n\t * value untouched.\n\t */\n\treadonly privateServerPriceRobux?: number | undefined;\n\t/** Roblox Group social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly robloxGroupSocialLink?: SocialLink | undefined;\n\t/** Whether tablet players can join; `undefined` leaves the server value untouched. */\n\treadonly tabletEnabled: boolean | undefined;\n\t/** Twitch social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly twitchSocialLink?: SocialLink | undefined;\n\t/** Twitter social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly twitterSocialLink?: SocialLink | undefined;\n\t/** User-supplied Roblox universe ID; the universe must already exist. */\n\treadonly universeId: RobloxAssetId;\n\t/** Whether voice chat is enabled; `undefined` leaves the server value untouched. */\n\treadonly voiceChatEnabled: boolean | undefined;\n\t/** Whether VR players can join; `undefined` leaves the server value untouched. */\n\treadonly vrEnabled: boolean | undefined;\n\t/** YouTube social link; tri-state (absent/undefined/set) — see interface JSDoc. */\n\treadonly youtubeSocialLink?: SocialLink | undefined;\n}\n\n/**\n * Ordered list of optional boolean managed fields on {@link UniverseDesiredState}.\n *\n * The driver translator and the diff's per-field equality guard both iterate\n * this list so they cannot drift apart. Order drives `updateMask` sequence in\n * generated requests.\n */\nexport const UNIVERSE_MANAGED_FLAGS = [\n\t\"desktopEnabled\",\n\t\"mobileEnabled\",\n\t\"tabletEnabled\",\n\t\"consoleEnabled\",\n\t\"vrEnabled\",\n\t\"voiceChatEnabled\",\n] as const satisfies ReadonlyArray<keyof UniverseDesiredState>;\n\n/** Key of an optional boolean managed field on {@link UniverseDesiredState}. */\nexport type UniverseManagedFlag = (typeof UNIVERSE_MANAGED_FLAGS)[number];\n\n/**\n * Tuple of every social link field name on {@link UniverseDesiredState}.\n * Iterated by flatten, driver, and diff to handle the tri-state clearable\n * semantics uniformly across all seven fields.\n */\nexport const SOCIAL_LINK_FIELDS = [\n\t\"discordSocialLink\",\n\t\"facebookSocialLink\",\n\t\"guildedSocialLink\",\n\t\"robloxGroupSocialLink\",\n\t\"twitchSocialLink\",\n\t\"twitterSocialLink\",\n\t\"youtubeSocialLink\",\n] as const satisfies ReadonlyArray<keyof UniverseDesiredState>;\n\n/** Union of the seven social link field names on {@link UniverseDesiredState}. */\nexport type SocialLinkField = (typeof SOCIAL_LINK_FIELDS)[number];\n\n/**\n * Desired state for a developer product, the consumable a player can buy via\n * `MarketplaceService:PromptProductPurchase`.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, type DeveloperProductDesiredState } from \"@bedrock-rbx/core\";\n *\n * const product: DeveloperProductDesiredState = {\n * description: \"Stocks the player up with 1,000 premium gems.\",\n * isRegionalPricingEnabled: true,\n * key: asResourceKey(\"gem-pack\"),\n * kind: \"developerProduct\",\n * name: \"Gem Pack\",\n * price: 100,\n * storePageEnabled: true,\n * };\n *\n * expect(product.kind).toBe(\"developerProduct\");\n * expect(product.price).toBe(100);\n * ```\n */\nexport interface DeveloperProductDesiredState {\n\t/** User-supplied key; stable across deploys; used to correlate desired with current. */\n\treadonly key: ResourceKey;\n\t/** User-facing developer product name as shown on the storefront. */\n\treadonly name: string;\n\t/** User-facing description shown on the developer product detail page. */\n\treadonly description: string;\n\t/**\n\t * Locale-keyed icon paths declared on the authored config. Absent when\n\t * the user did not declare an icon block. The Roblox developer-product\n\t * API is monolingual, so only the `\"en-us\"` icon is ever uploaded; the\n\t * map shape mirrors `GamePassDesiredState.icon` for cross-kind parity.\n\t */\n\treadonly icon?: Record<\"en-us\", string>;\n\t/**\n\t * SHA-256 digests of the local icon files keyed by the same locales as\n\t * the icon map. The diff compares this map against the prior current\n\t * state so the driver re-uploads only when a file's bytes change. Absent\n\t * when `icon` is absent.\n\t */\n\treadonly iconFileHashes?: Record<\"en-us\", Sha256Hex>;\n\t/**\n\t * Whether Roblox-managed regional pricing applies to the product.\n\t * Tri-state: `undefined` means the flag is unmanaged (the diff ignores\n\t * it); a defined value is propagated to Roblox on every deploy.\n\t */\n\treadonly isRegionalPricingEnabled: boolean | undefined;\n\t/** Discriminator tag for the `ResourceDesiredState` union. */\n\treadonly kind: \"developerProduct\";\n\t/**\n\t * Robux price. `undefined` means off-sale; removing the field from config\n\t * takes the product off-sale on the next deploy, re-adding puts it back\n\t * on sale.\n\t */\n\treadonly price: number | undefined;\n\t/**\n\t * Whether the product appears on the universe's external store page.\n\t * Tri-state: `undefined` means the flag is unmanaged. A defined value is\n\t * applied via a follow-up PATCH after the create POST because the v2\n\t * create endpoint does not accept this field.\n\t */\n\treadonly storePageEnabled: boolean | undefined;\n}\n\n/**\n * Discriminated union of every desired-state shape Bedrock manages.\n *\n * Extend by adding new members to this union; the mapped\n * `ResourceOutputsByKind` interface then forces a matching outputs entry for\n * the new kind at compile time.\n */\nexport type ResourceDesiredState =\n\t| DeveloperProductDesiredState\n\t| GamePassDesiredState\n\t| PlaceDesiredState\n\t| UniverseDesiredState;\n\n/**\n * Roblox-returned identifiers produced by creating or updating a game pass.\n *\n * Distinct from `GamePassDesiredState.key`: the desired-state key is a\n * user-supplied handle, while these IDs are assigned by Roblox and\n * discovered only after the first successful API call.\n */\nexport interface GamePassOutputs {\n\t/** Primary Roblox asset ID for the game pass itself. */\n\treadonly assetId: RobloxAssetId;\n\t/**\n\t * Locale-keyed Roblox-assigned image IDs for the game-pass icons. The\n\t * Roblox game-pass API is monolingual, so the `\"en-us\"` entry is the\n\t * only one ever populated; the map shape leaves room for translated\n\t * icons should the API ever expose them.\n\t */\n\treadonly iconAssetIds: Record<\"en-us\", RobloxAssetId>;\n}\n\n/**\n * Roblox-returned identifiers produced by creating or updating a developer\n * product. `productId` is what `MarketplaceService:PromptProductPurchase`\n * accepts and is stable across re-deploys. `iconImageAssetId` is optional\n * because the wire returns it as nullable when no icon is uploaded; slice 1\n * never populates it because icon support lands in a later slice.\n */\nexport interface DeveloperProductOutputs {\n\t/** Roblox asset ID of the uploaded icon image; `undefined` when no icon is uploaded. */\n\treadonly iconImageAssetId?: RobloxAssetId | undefined;\n\t/** Roblox-assigned developer product ID; stable across re-deploys. */\n\treadonly productId: RobloxAssetId;\n}\n\n/**\n * Roblox-returned value produced by reconciling a universe. The root place\n * ID is server-authoritative: bedrock cannot set it directly, but records it\n * so a future places slice can cross-validate the declared start place.\n */\nexport interface UniverseOutputs {\n\t/** Server-assigned root place ID for the universe. */\n\treadonly rootPlaceId: RobloxAssetId;\n}\n\n/**\n * String union of every discriminator tag in `ResourceDesiredState`.\n *\n * Derived from the union rather than hand-maintained so adding a new\n * `ResourceDesiredState` member automatically widens this type.\n */\nexport type ResourceKind = ResourceDesiredState[\"kind\"];\n\n/**\n * Per-kind outputs registry. Each `ResourceKind` must have a matching entry\n * or `ResourceOutputs<K>` is a compile error. Modelled as an interface (not a\n * type alias) so downstream packages can use declaration merging to register\n * outputs for new kinds without touching this module.\n *\n * @example\n *\n * ```ts\n * import { asRobloxAssetId, type ResourceOutputsByKind } from \"@bedrock-rbx/core\";\n *\n * const outputs: ResourceOutputsByKind[\"gamePass\"] = {\n * assetId: asRobloxAssetId(\"9876543210\"),\n * iconAssetIds: { \"en-us\": asRobloxAssetId(\"1122334455\") },\n * };\n *\n * expect(outputs.assetId).toBe(\"9876543210\");\n * ```\n */\nexport interface ResourceOutputsByKind {\n\t/** Outputs returned by the Roblox API for a developer-product resource. */\n\tdeveloperProduct: DeveloperProductOutputs;\n\t/** Outputs returned by the Roblox API for a game-pass resource. */\n\tgamePass: GamePassOutputs;\n\t/** Outputs returned by the Roblox API for a place publish. */\n\tplace: PlaceOutputs;\n\t/** Outputs returned by the Roblox API for a universe reconcile. */\n\tuniverse: UniverseOutputs;\n}\n\n/**\n * Resolved outputs for a specific resource kind.\n *\n * @template K - The resource kind discriminator.\n */\nexport type ResourceOutputs<K extends ResourceKind> = ResourceOutputsByKind[K];\n\n/**\n * Current (live) state for a resource kind.\n *\n * Composed from the matching desired-state shape plus a nested `outputs`\n * object carrying Roblox-assigned identifiers. The outer `K extends\n * ResourceKind` conditional distributes `K` across the union so the default\n * `ResourceCurrentState` resolves to a clean per-kind union rather than a\n * cross-product intersection of every kind's fields.\n *\n * The `outputs` sub-object stays nested (rather than flattening into the\n * top level) to mirror Mantle's `{ inputs, outputs }` state layout,\n * keeping migration copy clean.\n *\n * @template K - The resource kind discriminator. Defaults to the full\n * `ResourceKind` union for the broad form used in `ReadonlyArray`\n * collections.\n *\n * @example\n *\n * ```ts\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * asSha256Hex,\n * type ResourceCurrentState,\n * } from \"@bedrock-rbx/core\";\n *\n * const current: ResourceCurrentState<\"gamePass\"> = {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * iconFileHashes: {\n * \"en-us\": asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * ),\n * },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * outputs: {\n * assetId: asRobloxAssetId(\"9876543210\"),\n * iconAssetIds: { \"en-us\": asRobloxAssetId(\"1122334455\") },\n * },\n * price: 500,\n * };\n *\n * expect(current.outputs.assetId).toBe(\"9876543210\");\n * expect(current.kind).toBe(\"gamePass\");\n * ```\n */\nexport type ResourceCurrentState<K extends ResourceKind = ResourceKind> = K extends ResourceKind\n\t? Prettify<\n\t\t\tExtract<ResourceDesiredState, { kind: K }> & { readonly outputs: ResourceOutputs<K> }\n\t\t>\n\t: never;\n\ntype Prettify<T> = { readonly [K in keyof T]: T[K] };\n\ntype WithOptionalSocialLinks = Readonly<Partial<Record<SocialLinkField, SocialLink | undefined>>>;\n\n/**\n * Copy every social link field that is present as a key on `source`,\n * preserving the tri-state distinction between \"key absent\" (unmanaged,\n * omitted from result) and \"key present with `undefined`\" (cleared,\n * forwarded as-is). Shared by flatten, build-desired, and the universe\n * driver so all three layers propagate the same tri-state semantics.\n *\n * @param source - Object whose declared social link keys should be copied.\n * @returns Partial record containing only the social link keys present on\n * `source`; absent keys stay absent.\n */\nexport function copyDeclaredSocialLinks(\n\tsource: WithOptionalSocialLinks,\n): Partial<Record<SocialLinkField, SocialLink | undefined>> {\n\tconst copied: Partial<Record<SocialLinkField, SocialLink | undefined>> = {};\n\tfor (const field of SOCIAL_LINK_FIELDS) {\n\t\tif (field in source) {\n\t\t\tcopied[field] = source[field];\n\t\t}\n\t}\n\n\treturn copied;\n}\n\n/**\n * Fixed stable key for the singleton universe resource. `flattenConfig`\n * stamps this onto the sole `UniverseDesiredInput` it emits; fixtures and\n * state adapters share the constant so the invariant is encoded once.\n *\n * @example\n *\n * ```ts\n * import { UNIVERSE_SINGLETON_KEY } from \"@bedrock-rbx/core\";\n *\n * expect(UNIVERSE_SINGLETON_KEY).toBe(\"main\");\n * ```\n */\nexport const UNIVERSE_SINGLETON_KEY: ResourceKey = asResourceKey(\"main\");\n","import { ApiError, type OpenCloudError, type Result } from \"@bedrock-rbx/ocale\";\nimport type { PlacesClient, UpdatePlaceParameters } from \"@bedrock-rbx/ocale/places\";\n\nimport type { PlaceDesiredState, PlaceOutputs, ResourceCurrentState } from \"../core/resources.ts\";\nimport { PLACE_MANAGED_METADATA_FIELDS } from \"../core/resources.ts\";\nimport type { ResourceDriver } from \"../ports/resource-driver.ts\";\nimport type { RobloxAssetId } from \"../types/ids.ts\";\n\n/**\n * Dependencies of `createPlaceDriver`. `universeId` is captured at\n * construction time (matching `GamePassDriverDeps`) so each driver instance\n * is bound to a single universe; multi-universe deploys construct one driver\n * per universe. `readFile` is injected because `diff` operates on file hashes\n * while the driver is the only place that needs the raw bytes.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { PlacesClient } from \"@bedrock-rbx/ocale/places\";\n * import { asRobloxAssetId, type PlaceDriverDeps } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return { data: { body: {}, headers: {}, status: 200 }, success: true };\n * },\n * };\n *\n * const deps: PlaceDriverDeps = {\n * client: new PlacesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () => new Uint8Array(),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * };\n *\n * expect(deps.universeId).toBe(\"1234567890\");\n * ```\n */\nexport interface PlaceDriverDeps {\n\t/** Configured places client from `@bedrock-rbx/ocale/places`. */\n\treadonly client: PlacesClient;\n\t/** Reads place-file bytes for upload; rejections propagate out of the driver. */\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\t/** Universe that owns every place this driver publishes. */\n\treadonly universeId: RobloxAssetId;\n}\n\n/**\n * Wraps {@link PlacesClient} as a `ResourceDriver<\"place\">`. `create` and\n * `update` are both thin wrappers over a shared publish helper because the\n * upstream Open Cloud call is identical either way: there is no \"create\n * place\" endpoint (the place is user-supplied input), only \"publish version\".\n *\n * Format is detected from the file extension (`.rbxl` → binary,\n * `.rbxlx` → XML); any other extension returns an `ApiError`-backed failure\n * without hitting the network.\n *\n * @param deps - Injected ocale client, file reader, and owning universe.\n * @returns A driver indexable by `\"place\"` in a `DriverRegistry`.\n * @throws Whatever `deps.readFile` rejects with.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { PlacesClient } from \"@bedrock-rbx/ocale/places\";\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * asSha256Hex,\n * createPlaceDriver,\n * } from \"@bedrock-rbx/core\";\n *\n * const httpClient: HttpClient = {\n * async request() {\n * return {\n * data: { body: { versionNumber: 1 }, headers: {}, status: 200 },\n * success: true,\n * };\n * },\n * };\n *\n * const driver = createPlaceDriver({\n * client: new PlacesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient,\n * sleep: async () => {},\n * }),\n * readFile: async () =>\n * new Uint8Array([\n * 0x3c, 0x72, 0x6f, 0x62, 0x6c, 0x6f, 0x78, 0x21, 0x89, 0xff, 0x0d, 0x0a, 0x1a,\n * 0x0a,\n * ]),\n * universeId: asRobloxAssetId(\"1234567890\"),\n * });\n *\n * return driver\n * .create({\n * description: undefined,\n * displayName: undefined,\n * fileHash: asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * ),\n * filePath: \"places/start.rbxl\",\n * key: asResourceKey(\"start-place\"),\n * kind: \"place\",\n * placeId: asRobloxAssetId(\"4711\"),\n * serverSize: undefined,\n * })\n * .then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.outputs.versionNumber).toBe(1);\n * }\n * });\n * ```\n */\nexport function createPlaceDriver(deps: PlaceDriverDeps): ResourceDriver<\"place\"> {\n\treturn {\n\t\tasync create(desired) {\n\t\t\treturn publishPlace(deps, desired);\n\t\t},\n\t\tasync update(_current, desired) {\n\t\t\treturn publishPlace(deps, desired);\n\t\t},\n\t};\n}\n\nfunction buildMetadataParameters(\n\tuniverseId: RobloxAssetId,\n\tdesired: PlaceDesiredState,\n): undefined | UpdatePlaceParameters {\n\tconst metadata = PLACE_MANAGED_METADATA_FIELDS.reduce<Partial<UpdatePlaceParameters>>(\n\t\t(accumulator, field) => {\n\t\t\tconst value = desired[field];\n\t\t\treturn value === undefined ? accumulator : { ...accumulator, [field]: value };\n\t\t},\n\t\t{},\n\t);\n\n\tif (Object.keys(metadata).length === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn { ...metadata, placeId: desired.placeId, universeId };\n}\n\nfunction detectFormat(filePath: string): \"rbxl\" | \"rbxlx\" | undefined {\n\tif (filePath.endsWith(\".rbxlx\")) {\n\t\treturn \"rbxlx\";\n\t}\n\n\tif (filePath.endsWith(\".rbxl\")) {\n\t\treturn \"rbxl\";\n\t}\n\n\treturn undefined;\n}\n\nasync function publishVersion(\n\tdeps: PlaceDriverDeps,\n\tdesired: PlaceDesiredState,\n): Promise<Result<PlaceOutputs, OpenCloudError>> {\n\tconst format = detectFormat(desired.filePath);\n\tif (format === undefined) {\n\t\treturn {\n\t\t\terr: new ApiError(\n\t\t\t\t`Unsupported place file extension for ${desired.filePath}; expected .rbxl or .rbxlx`,\n\t\t\t\t{ statusCode: 0 },\n\t\t\t),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\tconst body = await deps.readFile(desired.filePath);\n\treturn deps.client.publish({\n\t\t// Narrows `Uint8Array<ArrayBufferLike>` to `Uint8Array<ArrayBuffer>`\n\t\t// so the ocale wire type rejects SharedArrayBuffer at the call site.\n\t\tbody: Uint8Array.from(body),\n\t\tformat,\n\t\tplaceId: desired.placeId,\n\t\tuniverseId: deps.universeId,\n\t});\n}\n\nasync function publishPlace(\n\tdeps: PlaceDriverDeps,\n\tdesired: PlaceDesiredState,\n): Promise<Result<ResourceCurrentState<\"place\">, OpenCloudError>> {\n\tconst publishResult = await publishVersion(deps, desired);\n\tif (!publishResult.success) {\n\t\treturn publishResult;\n\t}\n\n\tconst metadataParameters = buildMetadataParameters(deps.universeId, desired);\n\tif (metadataParameters !== undefined) {\n\t\tconst metadataResult = await deps.client.update(metadataParameters);\n\t\tif (!metadataResult.success) {\n\t\t\treturn metadataResult;\n\t\t}\n\t}\n\n\treturn { data: { ...desired, outputs: publishResult.data }, success: true };\n}\n","import { ApiError, type OpenCloudError, type Result } from \"@bedrock-rbx/ocale\";\nimport type { PlacesClient } from \"@bedrock-rbx/ocale/places\";\nimport type { UniversesClient, UpdateUniverseParameters } from \"@bedrock-rbx/ocale/universes\";\n\nimport {\n\tcopyDeclaredSocialLinks,\n\ttype ResourceCurrentState,\n\tSOCIAL_LINK_FIELDS,\n\tUNIVERSE_MANAGED_FLAGS,\n\ttype UniverseDesiredState,\n} from \"../core/resources.ts\";\nimport type { ResourceDriver } from \"../ports/resource-driver.ts\";\nimport { asRobloxAssetId } from \"../types/ids.ts\";\n\n/**\n * Dependencies of `createUniverseDriver`. The driver reconciles the\n * universe singleton against both the universes endpoint and the root\n * place (for fields Roblox marks read-only on the universe, like\n * `displayName`). There is no `universeId` at construction time because\n * the universe *is* the resource the driver reconciles, so the ID rides\n * along on each `UniverseDesiredState`.\n */\nexport interface UniverseDriverDeps {\n\t/** Configured places client from `@bedrock-rbx/ocale/places`. */\n\treadonly places: PlacesClient;\n\t/** Configured universes client from `@bedrock-rbx/ocale/universes`. */\n\treadonly universes: UniversesClient;\n}\n\ninterface ResolvedUniverse {\n\treadonly rootPlaceId: string;\n}\n\ninterface ReconcileInputs {\n\treadonly deps: UniverseDriverDeps;\n\treadonly desired: UniverseDesiredState;\n}\n\n/**\n * Wraps {@link UniversesClient} as a `ResourceDriver<\"universe\">`. `create`\n * and `update` both delegate to a shared reconcile helper because Open\n * Cloud cannot mint universes; the user supplies an existing `universeId`\n * and bedrock adopts the universe on first apply.\n *\n * A `NotFound` error (HTTP 404) from `UniversesClient.update` is repackaged\n * as an adoption-error `ApiError` whose message names the config key and\n * the `universeId`, so operators can tell adoption failure apart from\n * transient upstream errors. A successful response whose `rootPlaceId` is\n * absent surfaces as an `ApiError` with status 200, mirroring the\n * malformed-response guard in `GamePassDriver`.\n *\n * When `displayName` is declared, the driver routes that field through\n * `PlacesClient.update` on the root place after the universe PATCH\n * succeeds. A subsequent places failure surfaces to the caller as the\n * driver's error result without rolling back the prior universe patch,\n * so callers observing a partial failure should reconcile by\n * reapplying rather than assuming the universe-level fields are\n * unchanged.\n *\n * @param deps - Injected ocale clients (universes plus places for the\n * read-only universe fields Roblox derives from the root place).\n * @returns A driver indexable by `\"universe\"` in a `DriverRegistry`.\n *\n * @example\n *\n * ```ts\n * import type { HttpClient } from \"@bedrock-rbx/ocale\";\n * import { PlacesClient } from \"@bedrock-rbx/ocale/places\";\n * import { UniversesClient } from \"@bedrock-rbx/ocale/universes\";\n * import { validUniverseBody } from \"@bedrock-rbx/ocale/testing\";\n * import {\n * asRobloxAssetId,\n * createUniverseDriver,\n * UNIVERSE_SINGLETON_KEY,\n * } from \"@bedrock-rbx/core\";\n *\n * const universeBodyHttpClient: HttpClient = {\n * async request() {\n * return {\n * data: {\n * body: validUniverseBody({\n * path: \"universes/1234567890\",\n * rootPlace: \"universes/1234567890/places/4711\",\n * }),\n * headers: {},\n * status: 200,\n * },\n * success: true,\n * };\n * },\n * };\n *\n * const driver = createUniverseDriver({\n * places: new PlacesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient: universeBodyHttpClient,\n * sleep: async () => {},\n * }),\n * universes: new UniversesClient({\n * apiKey: \"rbx-your-key\",\n * httpClient: universeBodyHttpClient,\n * sleep: async () => {},\n * }),\n * });\n *\n * return driver\n * .create({\n * consoleEnabled: undefined,\n * desktopEnabled: true,\n * displayName: undefined,\n * key: UNIVERSE_SINGLETON_KEY,\n * kind: \"universe\",\n * mobileEnabled: undefined,\n * privateServerPriceRobux: undefined,\n * tabletEnabled: undefined,\n * universeId: asRobloxAssetId(\"1234567890\"),\n * voiceChatEnabled: true,\n * vrEnabled: undefined,\n * })\n * .then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.outputs.rootPlaceId).toBe(\"4711\");\n * }\n * });\n * ```\n */\nexport function createUniverseDriver(deps: UniverseDriverDeps): ResourceDriver<\"universe\"> {\n\treturn {\n\t\tasync create(desired) {\n\t\t\treturn reconcileUniverse({ deps, desired });\n\t\t},\n\t\tasync update(_current, desired) {\n\t\t\treturn reconcileUniverse({ deps, desired });\n\t\t},\n\t};\n}\n\nfunction toCurrentState(\n\tdesired: UniverseDesiredState,\n\trootPlaceId: string,\n): ResourceCurrentState<\"universe\"> {\n\treturn {\n\t\t...desired,\n\t\toutputs: { rootPlaceId: asRobloxAssetId(rootPlaceId) },\n\t};\n}\n\nfunction buildParameters(desired: UniverseDesiredState): UpdateUniverseParameters {\n\tconst base = UNIVERSE_MANAGED_FLAGS.reduce<UpdateUniverseParameters>(\n\t\t(accumulator, flag) => {\n\t\t\tconst isEnabled = desired[flag];\n\t\t\treturn isEnabled === undefined ? accumulator : { ...accumulator, [flag]: isEnabled };\n\t\t},\n\t\t{ universeId: desired.universeId },\n\t);\n\n\tconst withPrice =\n\t\t\"privateServerPriceRobux\" in desired\n\t\t\t? { ...base, privateServerPriceRobux: desired.privateServerPriceRobux }\n\t\t\t: base;\n\n\treturn { ...withPrice, ...copyDeclaredSocialLinks(desired) };\n}\n\nfunction wrapUpdateError(err: OpenCloudError, desired: UniverseDesiredState): OpenCloudError {\n\tif (err instanceof ApiError && err.statusCode === 404) {\n\t\treturn new ApiError(\n\t\t\t`Universe ${desired.universeId} (key '${desired.key}') was not found; adoption failed`,\n\t\t\t{ statusCode: 404 },\n\t\t);\n\t}\n\n\treturn err;\n}\n\nfunction hasUniverseLevelUpdate(desired: UniverseDesiredState): boolean {\n\tif (UNIVERSE_MANAGED_FLAGS.some((flag) => desired[flag] !== undefined)) {\n\t\treturn true;\n\t}\n\n\tif (\"privateServerPriceRobux\" in desired) {\n\t\treturn true;\n\t}\n\n\treturn SOCIAL_LINK_FIELDS.some((field) => field in desired);\n}\n\nasync function resolveUniverse(\n\tdeps: UniverseDriverDeps,\n\tdesired: UniverseDesiredState,\n): Promise<Result<ResolvedUniverse, OpenCloudError>> {\n\tconst result = hasUniverseLevelUpdate(desired)\n\t\t? await deps.universes.update(buildParameters(desired))\n\t\t: await deps.universes.get({ universeId: desired.universeId });\n\n\tif (!result.success) {\n\t\treturn { err: wrapUpdateError(result.err, desired), success: false };\n\t}\n\n\tconst { rootPlaceId } = result.data;\n\tif (rootPlaceId === undefined) {\n\t\treturn {\n\t\t\terr: new ApiError(\n\t\t\t\t`Malformed universe response for ${desired.universeId}: rootPlaceId missing`,\n\t\t\t\t{ statusCode: 200 },\n\t\t\t),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: { rootPlaceId }, success: true };\n}\n\nasync function reconcileUniverse(\n\tinputs: ReconcileInputs,\n): Promise<Result<ResourceCurrentState<\"universe\">, OpenCloudError>> {\n\tconst { deps, desired } = inputs;\n\tconst universeResult = await resolveUniverse(deps, desired);\n\tif (!universeResult.success) {\n\t\treturn universeResult;\n\t}\n\n\tconst { rootPlaceId } = universeResult.data;\n\tif (desired.displayName !== undefined) {\n\t\tconst placesResult = await deps.places.update({\n\t\t\tdisplayName: desired.displayName,\n\t\t\tplaceId: rootPlaceId,\n\t\t\tuniverseId: desired.universeId,\n\t\t});\n\t\tif (!placesResult.success) {\n\t\t\treturn { err: placesResult.err, success: false };\n\t\t}\n\t}\n\n\treturn { data: toCurrentState(desired, rootPlaceId), success: true };\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { spawn } from \"node:child_process\";\nimport process from \"node:process\";\n\nimport type { Spawner, SpawnInvocation, SpawnLaunchError } from \"./spawner.ts\";\n\n/**\n * Translate a `child.on(\"close\", code, signal)` payload into the\n * {@link Spawner.spawn} return shape. Extracted from the adapter so the\n * signal-terminated branch can be exercised without launching a real\n * process. The caller normalizes node's `null` to `undefined` at the\n * boundary so this helper never sees `null`.\n * @param code - Exit code reported by the child, or `undefined` if the\n * child was terminated by a signal before exiting.\n * @param signal - Signal name reported by the child, or `undefined` when\n * no signal terminated it.\n * @returns `Ok(code)` for a clean exit (including `0`); otherwise\n * `Err(launchFailed)` carrying a synthetic Error whose message names\n * the signal.\n */\nexport function classifySpawnClose(\n\tcode: number | undefined,\n\tsignal: NodeJS.Signals | undefined,\n): Result<number, SpawnLaunchError> {\n\tif (code !== undefined) {\n\t\treturn { data: code, success: true };\n\t}\n\n\tconst cause: NodeJS.ErrnoException = new Error(\n\t\t`spawned process terminated by signal ${signal ?? \"unknown\"}`,\n\t);\n\treturn { err: { cause, kind: \"launchFailed\" }, success: false };\n}\n\n/**\n * Construct a {@link Spawner} backed by `node:child_process.spawn` with\n * `stdio` inherited from the parent process. The child's environment is\n * `process.env` overlaid with {@link SpawnInvocation.envOverrides} (overrides\n * win on key collision).\n *\n * - Exit codes resolve as `Ok(exitCode)` (including `0`).\n * - `ENOENT` and other launch-time errors resolve as `Err(launchFailed)`\n * with the original error in `cause` (its `code` field carries the\n * errno where present).\n * - Children terminated by signal before producing an exit code collapse\n * into `launchFailed` with a synthetic `Error` whose message names the\n * signal; a distinct variant lands the day a caller needs to act on the\n * difference.\n *\n * @returns A `Spawner` whose `spawn` settles once the child closes.\n * @example\n *\n * ```ts\n * import { createDefaultSpawner } from \"@bedrock-rbx/core\";\n * import process from \"node:process\";\n *\n * const spawner = createDefaultSpawner();\n *\n * return spawner\n * .spawn({\n * args: [\"-e\", \"process.exit(0)\"],\n * command: process.execPath,\n * envOverrides: {},\n * })\n * .then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data).toBe(0);\n * }\n * });\n * ```\n */\nexport function createDefaultSpawner(): Spawner {\n\treturn { spawn: spawnViaChildProcess };\n}\n\nasync function spawnViaChildProcess(invocation: SpawnInvocation): ReturnType<Spawner[\"spawn\"]> {\n\treturn new Promise((resolve) => {\n\t\tconst child = spawn(invocation.command, [...invocation.args], {\n\t\t\tenv: { ...process.env, ...invocation.envOverrides },\n\t\t\tstdio: \"inherit\",\n\t\t});\n\n\t\tchild.once(\"error\", (error: NodeJS.ErrnoException) => {\n\t\t\tresolve({ err: { cause: error, kind: \"launchFailed\" }, success: false });\n\t\t});\n\n\t\tchild.once(\"close\", (code, signal) => {\n\t\t\tresolve(classifySpawnClose(code ?? undefined, signal ?? undefined));\n\t\t});\n\t});\n}\n","/** Credential flags that may be supplied on the CLI and translated to env-var overrides. */\ninterface CredentialFlags {\n\t/** Roblox Open Cloud API key override; translates to BEDROCK_API_KEY when defined. */\n\treadonly apiKey?: string;\n\t/** GitHub token override; translates to BEDROCK_GITHUB_TOKEN when defined. */\n\treadonly githubToken?: string;\n}\n\n/**\n * Map CLI credential flags to their corresponding env-var names, omitting\n * entries whose flag is `undefined`.\n * @param flags - CLI credential flag values to translate.\n * @returns An immutable record of env-var names to their override values.\n */\nexport function buildCredentialOverrides(flags: CredentialFlags): Readonly<Record<string, string>> {\n\tconst overrides: Record<string, string> = {};\n\tif (flags.apiKey !== undefined) {\n\t\toverrides[\"BEDROCK_API_KEY\"] = flags.apiKey;\n\t}\n\n\tif (flags.githubToken !== undefined) {\n\t\toverrides[\"BEDROCK_GITHUB_TOKEN\"] = flags.githubToken;\n\t}\n\n\treturn overrides;\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { buildCredentialOverrides } from \"./credential-environment-overrides.ts\";\nimport type { Spawner, SpawnInvocation, SpawnLaunchCause } from \"./spawner.ts\";\n\n/**\n * Parsed deploy arguments forwarded to a `.bedrock/<command>.ts` override\n * script. Credential flags are translated into env-var overrides by the\n * dispatcher so secrets never reach the child's argv.\n */\nexport interface OverrideInvocation {\n\t/** Optional `--api-key` value; translated to `BEDROCK_API_KEY` in env. */\n\treadonly apiKey?: string;\n\t/** Optional `--config <path>` value; forwarded unchanged in argv when present. */\n\treadonly configFile?: string;\n\t/** Target environment for this single override invocation. */\n\treadonly environment: string;\n\t/** Optional `--github-token` value; translated to `BEDROCK_GITHUB_TOKEN` in env. */\n\treadonly githubToken?: string;\n\t/** Path to the override script file to invoke. */\n\treadonly overridePath: string;\n}\n\n/**\n * Failure modes returned by {@link dispatchOverride}.\n *\n * - `launchFailed` — the child process could not be started (e.g. `bun`\n * missing, permission denied). Wraps the {@link SpawnLaunchCause} the\n * underlying spawner surfaced so callers can render a precise diagnostic.\n * - `nonZeroExit` — the child started, ran, and exited with a non-zero\n * exit code. Callers should propagate `exitCode` into the CLI's own\n * process exit code so CI failure modes mirror the override's outcome.\n */\nexport type SpawnOverrideError =\n\t| { readonly cause: SpawnLaunchCause; readonly kind: \"launchFailed\" }\n\t| { readonly exitCode: number; readonly kind: \"nonZeroExit\" };\n\n/**\n * Dispatch a single `.bedrock/<command>.ts` override invocation through the\n * supplied {@link Spawner}. Encapsulates the spawn protocol:\n *\n * - argv = `[overridePath, \"--env\", environment]`, with `\"--config\", configFile`\n * appended when supplied.\n * - `apiKey` becomes the `BEDROCK_API_KEY` env-var override; `githubToken`\n * becomes `BEDROCK_GITHUB_TOKEN`. Neither value appears in argv.\n * - `BEDROCK_CLI=1` is always set in the env. The override's `deploy()`\n * reads this on the `getEnv` seam to default to the clack progress\n * adapter; absent that downstream wiring, the variable is a forward-\n * compatible signal a future caller can act on.\n *\n * The dispatcher itself reads no ambient state: every input arrives via the\n * `invocation` argument and the `Spawner` port is the only side-effect seam.\n *\n * @param invocation - Path, environment, and parsed deploy-flag inputs.\n * @param spawner - Port the dispatcher hands the resolved\n * {@link SpawnInvocation} to.\n * @returns `Ok(undefined)` when the child exited zero; otherwise an\n * {@link SpawnOverrideError} discriminating launch vs non-zero exit.\n *\n * @example\n *\n * ```ts\n * import { dispatchOverride, type Spawner } from \"@bedrock-rbx/core\";\n *\n * const spawner: Spawner = {\n * async spawn() {\n * return { data: 0, success: true };\n * },\n * };\n *\n * return dispatchOverride(\n * {\n * environment: \"production\",\n * overridePath: \"/abs/.bedrock/deploy.ts\",\n * },\n * spawner,\n * ).then((result) => {\n * expect(result.success).toBeTrue();\n * });\n * ```\n */\nexport async function dispatchOverride(\n\tinvocation: OverrideInvocation,\n\tspawner: Spawner,\n): Promise<Result<void, SpawnOverrideError>> {\n\tconst args = [invocation.overridePath, \"--env\", invocation.environment];\n\tif (invocation.configFile !== undefined) {\n\t\targs.push(\"--config\", invocation.configFile);\n\t}\n\n\tconst credentialOverrides = buildCredentialOverrides(invocation);\n\n\tconst launched = await spawner.spawn({\n\t\targs,\n\t\tcommand: \"bun\",\n\t\tenvOverrides: { ...credentialOverrides, BEDROCK_CLI: \"1\" },\n\t});\n\tif (!launched.success) {\n\t\treturn { err: { cause: launched.err.cause, kind: \"launchFailed\" }, success: false };\n\t}\n\n\tconst exitCode = launched.data;\n\tif (exitCode !== 0) {\n\t\treturn { err: { exitCode, kind: \"nonZeroExit\" }, success: false };\n\t}\n\n\treturn { data: undefined, success: true };\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { type } from \"arktype\";\n\nimport { asResourceKey } from \"../../types/ids.ts\";\nimport type { DeveloperProductDesiredInput } from \"../flatten.ts\";\nimport { hashIconLocales, iconHashesEqual, iconMap } from \"../icons.ts\";\nimport type { DeveloperProductDesiredState, ResourceCurrentState } from \"../resources.ts\";\nimport { OPTIONAL_ROBUX_PRICE, type ResolvedConfig } from \"../schema.ts\";\nimport type { BuildDesiredError, KindIo, ResourceKindModule } from \"./module.ts\";\n\nconst OPTIONAL_BOOLEAN = \"boolean | undefined\";\n\nconst entrySchema = type({\n\t\"name\": \"string\",\n\t\"description\": \"string\",\n\t\"icon?\": iconMap,\n\t\"isRegionalPricingEnabled?\": OPTIONAL_BOOLEAN,\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n\t\"redacted?\": \"boolean | undefined\",\n\t\"storePageEnabled?\": OPTIONAL_BOOLEAN,\n});\n\nfunction flatten(config: ResolvedConfig): ReadonlyArray<DeveloperProductDesiredInput> {\n\treturn Object.entries(config.products ?? {}).map<DeveloperProductDesiredInput>(\n\t\t([key, entry]) => {\n\t\t\tconst base: DeveloperProductDesiredInput = {\n\t\t\t\tkey: asResourceKey(key),\n\t\t\t\tname: entry.name,\n\t\t\t\tdescription: entry.description,\n\t\t\t\tisRegionalPricingEnabled: entry.isRegionalPricingEnabled,\n\t\t\t\tkind: \"developerProduct\",\n\t\t\t\tprice: entry.price,\n\t\t\t\tstorePageEnabled: entry.storePageEnabled,\n\t\t\t};\n\t\t\treturn entry.icon === undefined ? base : { ...base, icon: entry.icon };\n\t\t},\n\t);\n}\n\nasync function normalize(\n\tinput: DeveloperProductDesiredInput,\n\tio: KindIo,\n): Promise<Result<DeveloperProductDesiredState, BuildDesiredError>> {\n\tconst base: DeveloperProductDesiredState = {\n\t\tkey: input.key,\n\t\tname: input.name,\n\t\tdescription: input.description,\n\t\tisRegionalPricingEnabled: input.isRegionalPricingEnabled,\n\t\tkind: \"developerProduct\",\n\t\tprice: input.price,\n\t\tstorePageEnabled: input.storePageEnabled,\n\t};\n\n\tif (input.icon === undefined) {\n\t\treturn { data: base, success: true };\n\t}\n\n\tconst hashes = await hashIconLocales({ key: input.key, icon: input.icon }, io);\n\tif (!hashes.success) {\n\t\treturn hashes;\n\t}\n\n\treturn {\n\t\tdata: { ...base, icon: input.icon, iconFileHashes: hashes.data },\n\t\tsuccess: true,\n\t};\n}\n\nfunction changedFieldsBetween(\n\tdesired: DeveloperProductDesiredState,\n\tcurrent: ResourceCurrentState<\"developerProduct\">,\n): ReadonlyArray<string> {\n\treturn [\n\t\t...(desired.description === current.description ? [] : [\"description\"]),\n\t\t...(desired.icon?.[\"en-us\"] === current.icon?.[\"en-us\"] ? [] : [\"icon\"]),\n\t\t...(iconHashesEqual(current.iconFileHashes, desired.iconFileHashes)\n\t\t\t? []\n\t\t\t: [\"iconFileHashes\"]),\n\t\t...(desired.name === current.name ? [] : [\"name\"]),\n\t\t...(desired.price === current.price ? [] : [\"price\"]),\n\t\t...(desired.isRegionalPricingEnabled === undefined ||\n\t\tdesired.isRegionalPricingEnabled === current.isRegionalPricingEnabled\n\t\t\t? []\n\t\t\t: [\"isRegionalPricingEnabled\"]),\n\t\t...(desired.storePageEnabled === undefined ||\n\t\tdesired.storePageEnabled === current.storePageEnabled\n\t\t\t? []\n\t\t\t: [\"storePageEnabled\"]),\n\t];\n}\n\nfunction fieldsEqual(\n\tdesired: DeveloperProductDesiredState,\n\tcurrent: ResourceCurrentState<\"developerProduct\">,\n): boolean {\n\treturn changedFieldsBetween(desired, current).length === 0;\n}\n\nfunction assertReconcilable(\n\tcurrent: ResourceCurrentState<\"developerProduct\">,\n\tdesired: DeveloperProductDesiredState,\n): Result<undefined, BuildDesiredError> {\n\tif (current.iconFileHashes !== undefined && desired.iconFileHashes === undefined) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tkey: desired.key,\n\t\t\t\tkind: \"iconRemovalRejected\",\n\t\t\t\tmessage: `developer product '${desired.key}' had an icon recorded in state, but the desired entry no longer declares one. The Roblox developer-product API has no documented way to unset an icon; remove the resource entry to delete the product, or restore the icon path to keep the existing image.`,\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: undefined, success: true };\n}\n\n/**\n * Resource-kind module for Roblox developer products. Owns the entry\n * schema, flattening, icon-hash normalization, drift-equality, and the\n * pre-reconcile icon-removal rejection for the `developerProduct` kind.\n */\nexport const developerProductKind: ResourceKindModule<\"developerProduct\"> = {\n\tassertReconcilable,\n\tchangedFieldsBetween,\n\tentrySchema,\n\tfieldsEqual,\n\tflatten,\n\tkind: \"developerProduct\",\n\tnormalize,\n};\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { type } from \"arktype\";\n\nimport { asResourceKey } from \"../../types/ids.ts\";\nimport type { GamePassDesiredInput } from \"../flatten.ts\";\nimport { hashIconLocales, iconHashesEqual, iconMap } from \"../icons.ts\";\nimport type { GamePassDesiredState, ResourceCurrentState } from \"../resources.ts\";\nimport { OPTIONAL_ROBUX_PRICE, type ResolvedConfig } from \"../schema.ts\";\nimport type { BuildDesiredError, KindIo, ResourceKindModule } from \"./module.ts\";\n\nconst entrySchema = type({\n\t\"name\": \"string\",\n\t\"description\": \"string\",\n\t\"icon\": iconMap,\n\t\"price?\": OPTIONAL_ROBUX_PRICE,\n\t\"redacted?\": \"boolean | undefined\",\n});\n\nfunction flatten(config: ResolvedConfig): ReadonlyArray<GamePassDesiredInput> {\n\treturn Object.entries(config.passes ?? {}).map<GamePassDesiredInput>(([key, entry]) => {\n\t\treturn {\n\t\t\tkey: asResourceKey(key),\n\t\t\tname: entry.name,\n\t\t\tdescription: entry.description,\n\t\t\ticon: entry.icon,\n\t\t\tkind: \"gamePass\",\n\t\t\tprice: entry.price,\n\t\t};\n\t});\n}\n\nasync function normalize(\n\tinput: GamePassDesiredInput,\n\tio: KindIo,\n): Promise<Result<GamePassDesiredState, BuildDesiredError>> {\n\tconst hashes = await hashIconLocales({ key: input.key, icon: input.icon }, io);\n\tif (!hashes.success) {\n\t\treturn hashes;\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\tkey: input.key,\n\t\t\tname: input.name,\n\t\t\tdescription: input.description,\n\t\t\ticon: input.icon,\n\t\t\ticonFileHashes: hashes.data,\n\t\t\tkind: \"gamePass\",\n\t\t\tprice: input.price,\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction changedFieldsBetween(\n\tdesired: GamePassDesiredState,\n\tcurrent: ResourceCurrentState<\"gamePass\">,\n): ReadonlyArray<string> {\n\treturn [\n\t\t...(desired.description === current.description ? [] : [\"description\"]),\n\t\t...(desired.icon[\"en-us\"] === current.icon[\"en-us\"] ? [] : [\"icon\"]),\n\t\t...(iconHashesEqual(current.iconFileHashes, desired.iconFileHashes)\n\t\t\t? []\n\t\t\t: [\"iconFileHashes\"]),\n\t\t...(desired.name === current.name ? [] : [\"name\"]),\n\t\t...(desired.price === current.price ? [] : [\"price\"]),\n\t];\n}\n\nfunction fieldsEqual(\n\tdesired: GamePassDesiredState,\n\tcurrent: ResourceCurrentState<\"gamePass\">,\n): boolean {\n\treturn changedFieldsBetween(desired, current).length === 0;\n}\n\n/**\n * Resource-kind module for Roblox game passes. Owns the entry schema,\n * flattening, icon-hash normalization, and drift-equality for the\n * `gamePass` kind.\n */\nexport const gamePassKind: ResourceKindModule<\"gamePass\"> = {\n\tchangedFieldsBetween,\n\tentrySchema,\n\tfieldsEqual,\n\tflatten,\n\tkind: \"gamePass\",\n\tnormalize,\n};\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { type } from \"arktype\";\n\nimport { asResourceKey, asRobloxAssetId, asSha256Hex } from \"../../types/ids.ts\";\nimport type { PlaceDesiredInput } from \"../flatten.ts\";\nimport { PLACE_MANAGED_METADATA_FIELDS } from \"../resources.ts\";\nimport type { PlaceDesiredState, ResourceCurrentState } from \"../resources.ts\";\nimport { OPTIONAL_POSITIVE_INTEGER } from \"../schema.ts\";\nimport type { ResolvedConfig } from \"../schema.ts\";\nimport { sha256Hex } from \"./hash.ts\";\nimport type { BuildDesiredError, KindIo, ResourceKindModule } from \"./module.ts\";\nimport { readBytes } from \"./read-bytes.ts\";\n\nconst entrySchema = type({\n\t\"description?\": \"string | undefined\",\n\t\"displayName?\": \"string | undefined\",\n\t\"filePath\": \"string\",\n\t\"placeId\": \"string.digits\",\n\t\"serverSize?\": OPTIONAL_POSITIVE_INTEGER,\n}).onUndeclaredKey(\"reject\");\n\nfunction flatten(config: ResolvedConfig): ReadonlyArray<PlaceDesiredInput> {\n\treturn Object.entries(config.places ?? {}).map<PlaceDesiredInput>(([key, entry]) => {\n\t\treturn {\n\t\t\tkey: asResourceKey(key),\n\t\t\tdescription: entry.description,\n\t\t\tdisplayName: entry.displayName,\n\t\t\tfilePath: entry.filePath,\n\t\t\tkind: \"place\",\n\t\t\tplaceId: asRobloxAssetId(entry.placeId),\n\t\t\tserverSize: entry.serverSize,\n\t\t};\n\t});\n}\n\nasync function normalize(\n\tinput: PlaceDesiredInput,\n\tio: KindIo,\n): Promise<Result<PlaceDesiredState, BuildDesiredError>> {\n\tconst read = await readBytes({ key: input.key, filePath: input.filePath }, io);\n\tif (!read.success) {\n\t\treturn read;\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\tkey: input.key,\n\t\t\tdescription: input.description,\n\t\t\tdisplayName: input.displayName,\n\t\t\tfileHash: asSha256Hex(await sha256Hex(read.data)),\n\t\t\tfilePath: input.filePath,\n\t\t\tkind: \"place\",\n\t\t\tplaceId: input.placeId,\n\t\t\tserverSize: input.serverSize,\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction changedFieldsBetween(\n\tdesired: PlaceDesiredState,\n\tcurrent: ResourceCurrentState<\"place\">,\n): ReadonlyArray<string> {\n\treturn [\n\t\t...(desired.fileHash === current.fileHash ? [] : [\"fileHash\"]),\n\t\t...(desired.filePath === current.filePath ? [] : [\"filePath\"]),\n\t\t...(desired.placeId === current.placeId ? [] : [\"placeId\"]),\n\t\t...PLACE_MANAGED_METADATA_FIELDS.filter((field) => {\n\t\t\tconst desiredValue = desired[field];\n\t\t\treturn desiredValue !== undefined && desiredValue !== current[field];\n\t\t}),\n\t];\n}\n\nfunction fieldsEqual(desired: PlaceDesiredState, current: ResourceCurrentState<\"place\">): boolean {\n\treturn changedFieldsBetween(desired, current).length === 0;\n}\n\n/**\n * Resource-kind module for Roblox places. Owns the entry schema,\n * flattening, file-hash normalization, and drift-equality for the `place`\n * kind.\n */\nexport const placeKind: ResourceKindModule<\"place\"> = {\n\tchangedFieldsBetween,\n\tentrySchema,\n\tfieldsEqual,\n\tflatten,\n\tkind: \"place\",\n\tnormalize,\n};\n","import type { Result } from \"@bedrock-rbx/ocale\";\nimport type { SocialLink } from \"@bedrock-rbx/ocale/universes\";\n\nimport { type } from \"arktype\";\n\nimport { asRobloxAssetId } from \"../../types/ids.ts\";\nimport type { UniverseDesiredInput } from \"../flatten.ts\";\nimport {\n\tcopyDeclaredSocialLinks,\n\ttype ResourceCurrentState,\n\tSOCIAL_LINK_FIELDS,\n\tUNIVERSE_MANAGED_FLAGS,\n\tUNIVERSE_SINGLETON_KEY,\n\ttype UniverseDesiredState,\n} from \"../resources.ts\";\nimport type { ResolvedConfig } from \"../schema.ts\";\nimport type { BuildDesiredError, KindIo, ResourceKindModule } from \"./module.ts\";\n\nconst OPTIONAL_BOOLEAN = \"boolean | undefined\";\n\nconst socialLink = type({\n\ttitle: \"string\",\n\turi: \"string\",\n}).onUndeclaredKey(\"reject\");\n\nconst socialLinkOrUndefined = socialLink.or(\"undefined\");\n\nconst entrySchema = type({\n\t\"consoleEnabled?\": OPTIONAL_BOOLEAN,\n\t\"desktopEnabled?\": OPTIONAL_BOOLEAN,\n\t\"discordSocialLink?\": socialLinkOrUndefined,\n\t\"displayName?\": \"string | undefined\",\n\t\"facebookSocialLink?\": socialLinkOrUndefined,\n\t\"guildedSocialLink?\": socialLinkOrUndefined,\n\t\"mobileEnabled?\": OPTIONAL_BOOLEAN,\n\t\"privateServerPriceRobux?\": \"number.integer >= 0 | undefined\",\n\t\"robloxGroupSocialLink?\": socialLinkOrUndefined,\n\t\"tabletEnabled?\": OPTIONAL_BOOLEAN,\n\t\"twitchSocialLink?\": socialLinkOrUndefined,\n\t\"twitterSocialLink?\": socialLinkOrUndefined,\n\t\"universeId\": \"string.digits\",\n\t\"voiceChatEnabled?\": OPTIONAL_BOOLEAN,\n\t\"vrEnabled?\": OPTIONAL_BOOLEAN,\n\t\"youtubeSocialLink?\": socialLinkOrUndefined,\n}).onUndeclaredKey(\"reject\");\n\nfunction flatten(config: ResolvedConfig): ReadonlyArray<UniverseDesiredInput> {\n\tconst entry = config.universe;\n\tif (entry === undefined) {\n\t\treturn [];\n\t}\n\n\tconst base: UniverseDesiredInput = {\n\t\tkey: UNIVERSE_SINGLETON_KEY,\n\t\tconsoleEnabled: entry.consoleEnabled,\n\t\tdesktopEnabled: entry.desktopEnabled,\n\t\tdisplayName: entry.displayName,\n\t\tkind: \"universe\",\n\t\tmobileEnabled: entry.mobileEnabled,\n\t\ttabletEnabled: entry.tabletEnabled,\n\t\tuniverseId: asRobloxAssetId(entry.universeId),\n\t\tvoiceChatEnabled: entry.voiceChatEnabled,\n\t\tvrEnabled: entry.vrEnabled,\n\t\t...copyDeclaredSocialLinks(entry),\n\t};\n\n\treturn [\n\t\t\"privateServerPriceRobux\" in entry\n\t\t\t? { ...base, privateServerPriceRobux: entry.privateServerPriceRobux }\n\t\t\t: base,\n\t];\n}\n\nfunction buildBaseDesired(input: UniverseDesiredInput): UniverseDesiredState {\n\tconst base: UniverseDesiredState = {\n\t\tkey: input.key,\n\t\tconsoleEnabled: input.consoleEnabled,\n\t\tdesktopEnabled: input.desktopEnabled,\n\t\tdisplayName: input.displayName,\n\t\tkind: \"universe\",\n\t\tmobileEnabled: input.mobileEnabled,\n\t\ttabletEnabled: input.tabletEnabled,\n\t\tuniverseId: input.universeId,\n\t\tvoiceChatEnabled: input.voiceChatEnabled,\n\t\tvrEnabled: input.vrEnabled,\n\t\t...copyDeclaredSocialLinks(input),\n\t};\n\n\treturn \"privateServerPriceRobux\" in input\n\t\t? { ...base, privateServerPriceRobux: input.privateServerPriceRobux }\n\t\t: base;\n}\n\nasync function normalize(\n\tinput: UniverseDesiredInput,\n\t_io: KindIo,\n): Promise<Result<UniverseDesiredState, BuildDesiredError>> {\n\treturn { data: buildBaseDesired(input), success: true };\n}\n\nfunction socialLinkEqual(a: SocialLink | undefined, b: SocialLink | undefined): boolean {\n\tif (a === undefined) {\n\t\treturn b === undefined;\n\t}\n\n\tif (b === undefined) {\n\t\treturn false;\n\t}\n\n\treturn a.title === b.title && a.uri === b.uri;\n}\n\nfunction changedFieldsBetween(\n\tdesired: UniverseDesiredState,\n\tcurrent: ResourceCurrentState<\"universe\">,\n): ReadonlyArray<string> {\n\treturn [\n\t\t...(desired.universeId === current.universeId ? [] : [\"universeId\"]),\n\t\t...UNIVERSE_MANAGED_FLAGS.filter((flag) => {\n\t\t\tconst isDesiredEnabled = desired[flag];\n\t\t\treturn isDesiredEnabled !== undefined && isDesiredEnabled !== current[flag];\n\t\t}),\n\t\t...(desired.displayName === undefined || desired.displayName === current.displayName\n\t\t\t? []\n\t\t\t: [\"displayName\"]),\n\t\t...(\"privateServerPriceRobux\" in desired &&\n\t\tdesired.privateServerPriceRobux !== current.privateServerPriceRobux\n\t\t\t? [\"privateServerPriceRobux\"]\n\t\t\t: []),\n\t\t...SOCIAL_LINK_FIELDS.filter(\n\t\t\t(field) => field in desired && !socialLinkEqual(desired[field], current[field]),\n\t\t),\n\t];\n}\n\nfunction fieldsEqual(\n\tdesired: UniverseDesiredState,\n\tcurrent: ResourceCurrentState<\"universe\">,\n): boolean {\n\treturn changedFieldsBetween(desired, current).length === 0;\n}\n\n/**\n * Resource-kind module for the singleton Roblox universe. Owns the entry\n * schema, flattening, pass-through normalize (no file I/O), and\n * drift-equality for the `universe` kind.\n */\nexport const universeKind: ResourceKindModule<\"universe\"> = {\n\tchangedFieldsBetween,\n\tentrySchema,\n\tfieldsEqual,\n\tflatten,\n\tkind: \"universe\",\n\tnormalize,\n};\n","import { developerProductKind } from \"./developer-product.ts\";\nimport { gamePassKind } from \"./game-pass.ts\";\nimport type { KindRegistry } from \"./module.ts\";\nimport { placeKind } from \"./place.ts\";\nimport { universeKind } from \"./universe.ts\";\n\n/**\n * Default {@link KindRegistry} composing every resource kind bedrock ships\n * out of the box. Iteration order (`gamePass`, `place`, `universe`,\n * `developerProduct`) matches the order `flattenConfig` emits entries\n * today, preserving the observable order of generated operations.\n *\n * @example\n *\n * ```ts\n * import { defaultKindRegistry } from \"@bedrock-rbx/core\";\n *\n * expect(defaultKindRegistry.gamePass.kind).toBe(\"gamePass\");\n * expect(defaultKindRegistry.place.kind).toBe(\"place\");\n * expect(defaultKindRegistry.universe.kind).toBe(\"universe\");\n * expect(defaultKindRegistry.developerProduct.kind).toBe(\"developerProduct\");\n * ```\n */\nexport const defaultKindRegistry: KindRegistry = {\n\tdeveloperProduct: developerProductKind,\n\tgamePass: gamePassKind,\n\tplace: placeKind,\n\tuniverse: universeKind,\n};\n","import { defaultKindRegistry } from \"./kinds/index.ts\";\nimport type { ResourceKindModule } from \"./kinds/module.ts\";\nimport type { Operation } from \"./operations.ts\";\nimport type { ResourceCurrentState, ResourceDesiredState, ResourceKind } from \"./resources.ts\";\n\n/**\n * Computes the operations required to reconcile `current` state with `desired`\n * state. Pure and synchronous: no I/O, no side effects, no `Result` wrapper.\n *\n * Each entry in `desired` is matched to `current` by `(kind, key)`: resources\n * are uniquely identified by that pair, so a `place` and a `universe` keyed\n * `\"main\"` are independent slots. A `(kind, key)` pair present only in\n * `desired` produces a `create` op; a pair present in both produces an\n * `update` op if any declared field differs or a `noop` op if every field\n * matches.\n *\n * Ops appear in the order their desired entries appear in the input array.\n * `applyOps` regroups them into Phase 1 (universe) and Phase 2 (everything\n * else) when dispatching; the execution order within Phase 2 is not\n * guaranteed because Phase 2 dispatches concurrently. Persisted state-file\n * order is determined by the merge in `deploy.runReconcile` (which retains\n * prior-snapshot positions for unchanged keys), not by this diff output.\n *\n * @param desired - Declared desired state from user config, already normalized\n * (file hashes computed, nullable wire values mapped to `undefined`).\n * @param current - Last-known current state from the state file.\n * @returns Operations to reconcile the two snapshots.\n *\n * @example\n *\n * ```ts\n * import {\n * asResourceKey,\n * asRobloxAssetId,\n * asSha256Hex,\n * diff,\n * type GamePassDesiredState,\n * type ResourceCurrentState,\n * } from \"@bedrock-rbx/core\";\n *\n * const hash = asSha256Hex(\n * \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n * );\n *\n * const unchanged: GamePassDesiredState = {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * iconFileHashes: { \"en-us\": hash },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * price: 500,\n * };\n * const drifted: GamePassDesiredState = {\n * ...unchanged,\n * key: asResourceKey(\"legend-pass\"),\n * name: \"Legend Pass (renamed)\",\n * };\n * const fresh: GamePassDesiredState = {\n * ...unchanged,\n * key: asResourceKey(\"rookie-pass\"),\n * name: \"Rookie Pass\",\n * };\n *\n * const current: ReadonlyArray<ResourceCurrentState> = [\n * {\n * ...unchanged,\n * outputs: {\n * assetId: asRobloxAssetId(\"111\"),\n * iconAssetIds: { \"en-us\": asRobloxAssetId(\"222\") },\n * },\n * },\n * {\n * ...drifted,\n * name: \"Legend Pass\",\n * outputs: {\n * assetId: asRobloxAssetId(\"333\"),\n * iconAssetIds: { \"en-us\": asRobloxAssetId(\"444\") },\n * },\n * },\n * ];\n *\n * const ops = diff([unchanged, drifted, fresh], current);\n *\n * expect(ops.map((op) => op.type)).toEqual([\"noop\", \"update\", \"create\"]);\n *\n * const updateOp = ops[1]!;\n * if (updateOp.type === \"update\") {\n * expect(updateOp.changedFields).toStrictEqual([\"name\"]);\n * }\n * ```\n */\nexport function diff(\n\tdesired: ReadonlyArray<ResourceDesiredState>,\n\tcurrent: ReadonlyArray<ResourceCurrentState>,\n): ReadonlyArray<Operation> {\n\tconst currentByKey = new Map(current.map((entry) => [compositeKey(entry), entry]));\n\treturn desired.map((entry) => operationFor(entry, currentByKey.get(compositeKey(entry))));\n}\n\nfunction compositeKey(resource: { readonly key: string; readonly kind: ResourceKind }): string {\n\treturn `${resource.kind}:${resource.key}`;\n}\n\nfunction operationFor(\n\tdesired: ResourceDesiredState,\n\tcurrent: ResourceCurrentState | undefined,\n): Operation {\n\tif (current === undefined) {\n\t\treturn { key: desired.key, desired, type: \"create\" };\n\t}\n\n\tconst module = defaultKindRegistry[desired.kind] as ResourceKindModule<ResourceKind>;\n\tconst changedFields = module.changedFieldsBetween(desired, current);\n\tif (changedFields.length === 0) {\n\t\treturn { key: desired.key, kind: desired.kind, type: \"noop\" };\n\t}\n\n\treturn { key: desired.key, changedFields, current, desired, type: \"update\" };\n}\n","/**\n * Default template applied when a project enables display-name prefixing\n * without supplying its own `displayNamePrefix.format`. Yields outputs\n * like `[STAGING] ` for an environment whose `label` is `\"staging\"`.\n */\nexport const DEFAULT_PREFIX_FORMAT = \"[{LABEL}] \";\n\nconst PLACEHOLDER_PATTERN = /\\{(LABEL|Label|label)\\}/g;\n\n/**\n * Render the prefix that selectEnvironment prepends to declared display\n * names when a project enables `displayNamePrefix`. The template\n * recognizes three placeholders:\n *\n * - `{label}`: label as written.\n * - `{LABEL}`: upper-cased label.\n * - `{Label}`: capitalized label (first character upper, rest as written).\n *\n * Other characters in the template flow through verbatim.\n *\n * @param label - Environment label declared on `EnvironmentEntry.label`.\n * @param format - Template string. Falls back to\n * {@link DEFAULT_PREFIX_FORMAT} when omitted.\n * @returns The rendered prefix string.\n *\n * @example\n *\n * ```ts\n * import { renderDisplayNamePrefix } from \"@bedrock-rbx/core\";\n *\n * expect(renderDisplayNamePrefix(\"staging\")).toBe(\"[STAGING] \");\n * expect(renderDisplayNamePrefix(\"staging\", \"{Label}: \")).toBe(\"Staging: \");\n * expect(renderDisplayNamePrefix(\"dev\", \"{LABEL}-{label}\")).toBe(\"DEV-dev\");\n * ```\n */\nexport function renderDisplayNamePrefix(label: string, format?: string): string {\n\tconst template = format ?? DEFAULT_PREFIX_FORMAT;\n\treturn template.replace(PLACEHOLDER_PATTERN, (_match, kind) => {\n\t\tif (kind === \"LABEL\") {\n\t\t\treturn label.toUpperCase();\n\t\t}\n\n\t\tif (kind === \"Label\") {\n\t\t\treturn capitalize(label);\n\t\t}\n\n\t\treturn label;\n\t});\n}\n\nfunction capitalize(value: string): string {\n\treturn value.charAt(0).toUpperCase() + value.slice(1);\n}\n","import type { SocialLink } from \"@bedrock-rbx/ocale/universes\";\n\nimport type { ResourceKey, RobloxAssetId } from \"../types/ids.ts\";\nimport { defaultKindRegistry } from \"./kinds/index.ts\";\nimport type { ResourceKindModule } from \"./kinds/module.ts\";\nimport type { ResourceKind } from \"./resources.ts\";\nimport type { DeveloperProductEntry, GamePassEntry, ResolvedConfig } from \"./schema.ts\";\n\n/**\n * Pre-I/O game-pass input the flattener emits. Extends the authored\n * `GamePassEntry` with the tag discriminator and the `ResourceKey`-branded\n * key so `buildDesired` can consume a flat tagged list and layer on the\n * SHA-256 icon digest.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, type GamePassDesiredInput } from \"@bedrock-rbx/core\";\n *\n * const input: GamePassDesiredInput = {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * price: 500,\n * };\n *\n * expect(input.kind).toBe(\"gamePass\");\n * ```\n */\nexport interface GamePassDesiredInput extends Readonly<GamePassEntry> {\n\t/** User-supplied handle, already validated against the `ResourceKey` brand. */\n\treadonly key: ResourceKey;\n\t/** Discriminator tag for the `ResourceDesiredInput` union. */\n\treadonly kind: \"gamePass\";\n}\n\n/**\n * Pre-I/O place input the flattener emits. Carries the resolved place\n * fields (`filePath` from the root, `placeId` from the per-environment\n * overlay) plus the optional metadata fields and the tag discriminator and\n * the `ResourceKey`-branded key, so `buildDesired` can consume a flat\n * tagged list and layer on the SHA-256 file digest.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, asRobloxAssetId, type PlaceDesiredInput } from \"@bedrock-rbx/core\";\n *\n * const input: PlaceDesiredInput = {\n * description: undefined,\n * displayName: \"Start Place\",\n * filePath: \"places/start.rbxl\",\n * key: asResourceKey(\"start-place\"),\n * kind: \"place\",\n * placeId: asRobloxAssetId(\"4711\"),\n * serverSize: 50,\n * };\n *\n * expect(input.kind).toBe(\"place\");\n * expect(input.displayName).toBe(\"Start Place\");\n * ```\n */\nexport interface PlaceDesiredInput {\n\t/** User-supplied handle, already validated against the `ResourceKey` brand. */\n\treadonly key: ResourceKey;\n\t/** User-facing place description; `undefined` leaves the server value untouched. */\n\treadonly description: string | undefined;\n\t/** User-facing place name; `undefined` leaves the server value untouched. */\n\treadonly displayName: string | undefined;\n\t/** Path to the `.rbxl` or `.rbxlx` file; read by `buildDesired`. */\n\treadonly filePath: string;\n\t/** Discriminator tag for the `ResourceDesiredInput` union. */\n\treadonly kind: \"place\";\n\t/** Existing Roblox place ID, validated and branded at flatten time. */\n\treadonly placeId: RobloxAssetId;\n\t/** Maximum players per server; `undefined` leaves the server value untouched. */\n\treadonly serverSize: number | undefined;\n}\n\n/**\n * Pre-I/O universe input the flattener emits. Carries the fixed singleton\n * key (`\"main\"`) and the branded `universeId` so `buildDesired` can hand a\n * single tagged record downstream without a shape divergence for the\n * singleton kind.\n *\n * @example\n *\n * ```ts\n * import { asRobloxAssetId, UNIVERSE_SINGLETON_KEY, type UniverseDesiredInput } from \"@bedrock-rbx/core\";\n *\n * const input: UniverseDesiredInput = {\n * consoleEnabled: undefined,\n * desktopEnabled: true,\n * displayName: undefined,\n * key: UNIVERSE_SINGLETON_KEY,\n * kind: \"universe\",\n * mobileEnabled: undefined,\n * tabletEnabled: undefined,\n * universeId: asRobloxAssetId(\"1234567890\"),\n * voiceChatEnabled: true,\n * vrEnabled: undefined,\n * };\n *\n * expect(input.kind).toBe(\"universe\");\n * expect(input.key).toBe(\"main\");\n * expect(input.desktopEnabled).toBeTrue();\n * ```\n */\nexport interface UniverseDesiredInput {\n\t/** Synthesized singleton key (`\"main\"`), already validated against the `ResourceKey` brand. */\n\treadonly key: ResourceKey;\n\t/** Whether console players can join; `undefined` leaves the server value untouched. */\n\treadonly consoleEnabled: boolean | undefined;\n\t/** Whether desktop players can join; `undefined` leaves the server value untouched. */\n\treadonly desktopEnabled: boolean | undefined;\n\t/** Discord social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly discordSocialLink?: SocialLink | undefined;\n\t/**\n\t * Display name for the universe. `undefined` leaves the server value\n\t * untouched. The driver routes declared updates through\n\t * `PlacesClient.update` because the universe PATCH endpoint treats\n\t * `displayName` as read-only.\n\t */\n\treadonly displayName: string | undefined;\n\t/** Facebook social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly facebookSocialLink?: SocialLink | undefined;\n\t/** Guilded social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly guildedSocialLink?: SocialLink | undefined;\n\t/**\n\t * Locale-keyed experience-icon paths copied from the user-supplied\n\t * `UniverseEntry`. Absent when the user did not declare an icon block.\n\t */\n\treadonly icon?: Record<\"en-us\", string>;\n\t/** Discriminator tag for the `ResourceDesiredInput` union. */\n\treadonly kind: \"universe\";\n\t/** Whether mobile players can join; `undefined` leaves the server value untouched. */\n\treadonly mobileEnabled: boolean | undefined;\n\t/**\n\t * Private-server price in Robux. A present key with `undefined`\n\t * clears the server value (ocale emits JSON `null`); an absent key\n\t * leaves the server value untouched.\n\t */\n\treadonly privateServerPriceRobux?: number | undefined;\n\t/** Roblox Group social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly robloxGroupSocialLink?: SocialLink | undefined;\n\t/** Whether tablet players can join; `undefined` leaves the server value untouched. */\n\treadonly tabletEnabled: boolean | undefined;\n\t/** Twitch social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly twitchSocialLink?: SocialLink | undefined;\n\t/** Twitter social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly twitterSocialLink?: SocialLink | undefined;\n\t/** Existing Roblox universe ID, validated and branded at flatten time. */\n\treadonly universeId: RobloxAssetId;\n\t/** Whether voice chat is enabled; `undefined` leaves the server value untouched. */\n\treadonly voiceChatEnabled: boolean | undefined;\n\t/** Whether VR players can join; `undefined` leaves the server value untouched. */\n\treadonly vrEnabled: boolean | undefined;\n\t/** YouTube social link; tri-state (absent/undefined/set) — see `UniverseDesiredState`. */\n\treadonly youtubeSocialLink?: SocialLink | undefined;\n}\n\n/**\n * Pre-I/O developer-product input the flattener emits. Extends the authored\n * `DeveloperProductEntry` with the tag discriminator and the\n * `ResourceKey`-branded key so `buildDesired` can consume a flat tagged\n * list.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, type DeveloperProductDesiredInput } from \"@bedrock-rbx/core\";\n *\n * const input: DeveloperProductDesiredInput = {\n * description: \"Stocks the player up with 1,000 premium gems.\",\n * key: asResourceKey(\"gem-pack\"),\n * kind: \"developerProduct\",\n * name: \"Gem Pack\",\n * };\n *\n * expect(input.kind).toBe(\"developerProduct\");\n * ```\n */\nexport interface DeveloperProductDesiredInput extends Readonly<DeveloperProductEntry> {\n\t/** User-supplied handle, already validated against the `ResourceKey` brand. */\n\treadonly key: ResourceKey;\n\t/** Discriminator tag for the `ResourceDesiredInput` union. */\n\treadonly kind: \"developerProduct\";\n}\n\n/**\n * Flat tagged input for `buildDesired`. One member per resource kind; future\n * kinds widen this union as they land.\n */\nexport type ResourceDesiredInput =\n\t| DeveloperProductDesiredInput\n\t| GamePassDesiredInput\n\t| PlaceDesiredInput\n\t| UniverseDesiredInput;\n\n/**\n * Turn a resolved `Config` into a flat, tagged list of resource inputs.\n *\n * Pure and infallible: validation and per-environment overlay merging\n * have already happened upstream (typically via `selectEnvironment`), so\n * every invariant this function relies on is guaranteed by the input\n * shape. Entries appear in the insertion order of each collection;\n * `passes` are emitted before `places`.\n *\n * @param config - Resolved config returned by `selectEnvironment`.\n * @returns Flat tagged list ready for `buildDesired`.\n * @example\n *\n * ```ts\n * import { flattenConfig, selectEnvironment, type Config } from \"@bedrock-rbx/core\";\n *\n * const config: Config = {\n * environments: {\n * production: { places: { \"start-place\": { placeId: \"4711\" } } },\n * },\n * passes: {\n * \"vip-pass\": {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * name: \"VIP Pass\",\n * price: 500,\n * },\n * },\n * places: { \"start-place\": { filePath: \"places/start.rbxl\" } },\n * };\n *\n * const resolved = selectEnvironment(config, \"production\");\n * expect(resolved.success).toBeTrue();\n * if (resolved.success) {\n * const inputs = flattenConfig(resolved.data);\n * expect(inputs.map((input) => input.kind)).toEqual([\"gamePass\", \"place\"]);\n * expect(inputs.map((input) => input.key)).toEqual([\"vip-pass\", \"start-place\"]);\n * }\n * ```\n */\nexport function flattenConfig(config: ResolvedConfig): ReadonlyArray<ResourceDesiredInput> {\n\tconst modules = Object.values(defaultKindRegistry) as ReadonlyArray<\n\t\tResourceKindModule<ResourceKind>\n\t>;\n\treturn modules.flatMap((module) => module.flatten(config));\n}\n","import { createHash } from \"node:crypto\";\n\nimport { asResourceKey, type ResourceKey } from \"../types/ids.ts\";\nimport { REDACTED_ICON_PATH } from \"./redacted-icon.ts\";\nimport type { ResourceKind } from \"./resources.ts\";\nimport type {\n\tDeveloperProductEntry,\n\tGamePassEntry,\n\tRedactedDeveloperProductOverride,\n\tRedactedEnvironmentOverride,\n\tRedactedGamePassOverride,\n\tRedactedPlaceOverride,\n\tResolvedConfig,\n\tResolvedPlaceEntry,\n} from \"./schema.ts\";\n\n/** Default placeholder name pushed for a redacted game-pass. */\nexport const REDACTED_PASS_NAME = \"Redacted Pass\";\n\n/**\n * Common prefix used to build the default name pushed for a redacted\n * developer-product. The full default produced by {@link defaultRedactedProductName}\n * is `${REDACTED_PRODUCT_NAME} ${suffix}`, where `suffix` is a 6-hex-char\n * digest of the resource key (see {@link redactedNameSuffix}). The suffix is\n * required because Roblox enforces per-universe uniqueness on\n * developer-product names, so a shared bare placeholder would collide across\n * multiple redacted entries. The prefix avoids the word `Redacted` and the\n * `#` separator because Roblox's text-moderation filter has been observed\n * silently replacing names matching `Redacted Product #<hex>` with\n * `########################`, which then causes downstream `DuplicateProductName`\n * errors when other redacted entries are moderated to the same string.\n */\nexport const REDACTED_PRODUCT_NAME = \"Hidden Product\";\n\n/** Default placeholder description pushed for any redacted resource. */\nexport const REDACTED_DESCRIPTION = \"\";\n\n/**\n * Default placeholder Robux price pushed for a redacted game-pass or\n * developer-product whose config price is defined. Off-sale resources\n * (`price === undefined`) keep their off-sale state through redaction so a\n * hidden product is never accidentally listed for sale.\n */\nexport const REDACTED_PRICE = 99_999;\n\n/**\n * Per-resource annotation surfaced in preview output for entries that are\n * redacted in the active environment. `hasRealValueEdits` is true when the\n * pre-redaction merged config carries real display values that diverge from\n * the placeholders bedrock pushes, so the renderer can warn the author that\n * their config edits are intentionally not flowing through to Open Cloud.\n */\nexport interface RedactionAnnotation {\n\t/** Resource key the annotation describes. */\n\treadonly key: ResourceKey;\n\t/** True when any real display field differs from the kind's placeholder default. */\n\treadonly hasRealValueEdits: boolean;\n\t/** Resource kind, so the renderer can format `kind:key` consistently with op output. */\n\treadonly kind: ResourceKind;\n}\n\n/**\n * Per-resource env-overlay redaction layers, keyed by kind. Each entry maps\n * a resource key to its env-overlay `redacted` value (boolean or per-field\n * override). `selectEnvironment` extracts these from env-overlay entries\n * before the rest of the overlay is defu-merged onto the root, so the\n * env-resource layer can compose field-by-field with the root layer in\n * {@link applyRedaction}.\n */\nexport interface EnvironmentResourceRedaction {\n\t/** Per-pass env-overlay redaction values keyed by resource key. */\n\treadonly passes?: EnvironmentResourceLayer<RedactedGamePassOverride>;\n\t/** Per-place env-overlay redaction values keyed by resource key. */\n\treadonly places?: EnvironmentResourceLayer<RedactedPlaceOverride>;\n\t/** Per-product env-overlay redaction values keyed by resource key. */\n\treadonly products?: EnvironmentResourceLayer<RedactedDeveloperProductOverride>;\n}\n\ntype RedactionLayer<Override> = boolean | Override | undefined;\n\ntype EnvironmentResourceLayer<Override> = Readonly<Record<string, RedactionLayer<Override>>>;\n\ntype EnvironmentLevel = boolean | RedactedEnvironmentOverride | undefined;\n\n/**\n * Aggregated redaction layers consumed by {@link applyRedaction}. The\n * `envLevel` layer applies to every redactable resource in the env;\n * `envResource` carries per-resource env-overlay overrides keyed by kind.\n */\ninterface RedactionInputs {\n\t/** Env-level redaction layer. Boolean toggle or cross-kind override object. */\n\treadonly envLevel?: EnvironmentLevel;\n\t/** Per-resource env-overlay redaction layers, keyed by kind. */\n\treadonly envResource?: EnvironmentResourceRedaction;\n}\n\ninterface ProductRedactionInputs {\n\treadonly key: string;\n\treadonly entry: DeveloperProductEntry;\n\treadonly override: RedactedDeveloperProductOverride;\n}\n\ninterface ResolvedEntry<Entry, Override> {\n\treadonly key: string;\n\treadonly entry: Entry;\n\treadonly override: Override | undefined;\n}\n\nconst PASS_PRODUCT_ENV_FIELDS = [\n\t\"description\",\n\t\"icon\",\n\t\"name\",\n\t\"price\",\n] as const satisfies ReadonlyArray<keyof RedactedEnvironmentOverride>;\n\nconst PLACE_ENV_FIELDS = [\"description\", \"displayName\"] as const satisfies ReadonlyArray<\n\tkeyof RedactedEnvironmentOverride\n>;\n\ninterface RedactCollectionInputs<Entry, Override> {\n\treadonly collection: Readonly<Record<string, Entry>> | undefined;\n\treadonly environmentForKind: RedactionLayer<Override>;\n\treadonly envResource: EnvironmentResourceLayer<Override> | undefined;\n\treadonly redact: (item: { entry: Entry; key: string; override: Override }) => Entry;\n}\n\ninterface RedactKindInputs<Entry, Override> {\n\treadonly collection: Readonly<Record<string, Entry>> | undefined;\n\treadonly envLevel: EnvironmentLevel;\n\treadonly envResource: EnvironmentResourceLayer<Override> | undefined;\n}\n\n/**\n * Six-character lowercase hex digest of `SHA-256(key)`, used as the\n * disambiguating suffix on a redacted developer-product's default `name`.\n * Stable across config edits (driven only by the bedrock resource key, not\n * declaration order) and opaque to a Roblox player browsing the marketplace.\n * A natural collision is caught before any apply-side driver I/O by `assertAllReconcilable`.\n *\n * @param key - Bedrock resource key for the developer product being redacted.\n * @returns The first six lowercase hex characters of the SHA-256 digest of `key`.\n */\nexport function redactedNameSuffix(key: string): string {\n\treturn createHash(\"sha256\").update(key).digest(\"hex\").slice(0, 6);\n}\n\n/**\n * Default redacted name for a developer product with the given resource key.\n * Combines {@link REDACTED_PRODUCT_NAME} with {@link redactedNameSuffix} so\n * each redacted entry resolves to a unique value the upstream API will accept.\n *\n * @param key - Bedrock resource key for the developer product being redacted.\n * @returns The placeholder name pushed to Roblox for this product.\n */\nexport function defaultRedactedProductName(key: string): string {\n\treturn `${REDACTED_PRODUCT_NAME} ${redactedNameSuffix(key)}`;\n}\n\n/**\n * Pure transform that substitutes bedrock-supplied placeholder content for\n * every resource whose effective redaction state is truthy. Three layers\n * compose field-by-field per resource: env-resource (most-specific, from\n * `inputs.envResource`), root-resource (the `redacted` field on the\n * passed-in entry), and env-level (least-specific, `inputs.envLevel`).\n * The first non-undefined value sets state (`false` carves out); object\n * layers then contribute fields with the most-specific layer winning per\n * field, and bedrock defaults fill any field nobody set. Runs between\n * env-overlay merge and display-name prefix render so the rest of the\n * pipeline (flatten, normalize, diff, apply) operates on already-redacted\n * values and needs no special-case redaction logic.\n *\n * @param config - Post-merge `ResolvedConfig` produced by `selectEnvironment`.\n * @param inputs - Aggregated redaction layers. Omit to skip redaction\n * entirely. See {@link RedactionInputs} for the shape.\n * @returns A `ResolvedConfig` whose redacted entries carry placeholder\n * values; non-redacted entries pass through verbatim, and the input is\n * not mutated.\n */\nexport function applyRedaction(config: ResolvedConfig, inputs?: RedactionInputs): ResolvedConfig {\n\tconst environmentLevel = inputs?.envLevel;\n\tconst environmentResource = inputs?.envResource;\n\tconst passes = redactPasses({\n\t\tcollection: config.passes,\n\t\tenvLevel: environmentLevel,\n\t\tenvResource: environmentResource?.passes,\n\t});\n\tconst places = redactPlaces({\n\t\tcollection: config.places,\n\t\tenvLevel: environmentLevel,\n\t\tenvResource: environmentResource?.places,\n\t});\n\tconst products = redactProducts({\n\t\tcollection: config.products,\n\t\tenvLevel: environmentLevel,\n\t\tenvResource: environmentResource?.products,\n\t});\n\n\tif (passes === config.passes && places === config.places && products === config.products) {\n\t\treturn config;\n\t}\n\n\treturn {\n\t\t...config,\n\t\t...(passes === undefined ? {} : { passes }),\n\t\t...(places === undefined ? {} : { places }),\n\t\t...(products === undefined ? {} : { products }),\n\t};\n}\n\n/**\n * Inspect the pre-redaction merged config and produce one annotation per\n * resource flagged `redacted: true` at either the root entry or its\n * env-overlay counterpart. Callers thread the result into plan output so\n * authors can see which resources are redacted in the active environment\n * and whether their real-value edits are being suppressed.\n *\n * Operates on the pre-redaction view because the post-redaction config no\n * longer carries the real `name`/`description`/`icon` values needed to\n * detect divergence from the placeholder defaults.\n *\n * @param merged - `ResolvedConfig` produced by environment overlay merge,\n * before `applyRedaction` has substituted placeholders.\n * @param environmentResource - Per-kind env-overlay redaction layers\n * extracted from the active env entry. Omit when the caller has no\n * env-overlay layer.\n * @returns Zero or more annotations, one per redacted resource. Empty when\n * the config declares no redacted resources.\n */\nexport function collectRedactionAnnotations(\n\tmerged: ResolvedConfig,\n\tenvironmentResource?: EnvironmentResourceRedaction,\n): ReadonlyArray<RedactionAnnotation> {\n\tconst passes = Object.entries(merged.passes ?? {})\n\t\t.filter(\n\t\t\t([key, entry]) =>\n\t\t\t\tentry.redacted === true || environmentResource?.passes?.[key] === true,\n\t\t)\n\t\t.map(([key, entry]): RedactionAnnotation => {\n\t\t\treturn {\n\t\t\t\tkey: asResourceKey(key),\n\t\t\t\thasRealValueEdits: passHasRealValueEdits(entry),\n\t\t\t\tkind: \"gamePass\",\n\t\t\t};\n\t\t});\n\tconst products = Object.entries(merged.products ?? {})\n\t\t.filter(\n\t\t\t([key, entry]) =>\n\t\t\t\tentry.redacted === true || environmentResource?.products?.[key] === true,\n\t\t)\n\t\t.map(([key, entry]): RedactionAnnotation => {\n\t\t\treturn {\n\t\t\t\tkey: asResourceKey(key),\n\t\t\t\thasRealValueEdits: productHasRealValueEdits(key, entry),\n\t\t\t\tkind: \"developerProduct\",\n\t\t\t};\n\t\t});\n\n\treturn [...passes, ...products];\n}\n\nfunction pickEnvironmentFields<Field extends keyof RedactedEnvironmentOverride>(\n\tenvironmentLevel: EnvironmentLevel,\n\tfields: ReadonlyArray<Field>,\n): RedactionLayer<Pick<RedactedEnvironmentOverride, Field>> {\n\tif (environmentLevel === undefined || typeof environmentLevel === \"boolean\") {\n\t\treturn environmentLevel;\n\t}\n\n\treturn Object.fromEntries(fields.map((field) => [field, environmentLevel[field]])) as Pick<\n\t\tRedactedEnvironmentOverride,\n\t\tField\n\t>;\n}\n\n/**\n * Walk redaction layers most-specific to least-specific and produce the\n * effective per-field override for one resource. Returns `undefined` when the\n * resource is not redacted; returns a (possibly empty) object when it is.\n * State step: the first non-undefined layer sets state -- `false` carves out,\n * `true` or object enables. Fields step: walk every object layer in the same\n * order, taking the first value per field. A field's value may itself be\n * `undefined` (the env-level projection produced by {@link pickEnvironmentFields}\n * includes every projected key, even when the env override left it absent);\n * downstream per-kind redact functions collapse those back to bedrock\n * placeholder defaults via `??`.\n *\n * @template Override - Per-kind override type the resource accepts.\n * @param layers - Layers ordered most-specific (index 0) to least-specific.\n * @returns The effective override, or `undefined` when not redacted.\n */\nfunction resolveEffectiveOverride<Override extends object>(\n\tlayers: ReadonlyArray<RedactionLayer<Override>>,\n): Override | undefined {\n\tconst firstNonUndefined = layers.find((layer) => layer !== undefined);\n\tif (firstNonUndefined === undefined || firstNonUndefined === false) {\n\t\treturn undefined;\n\t}\n\n\tconst effective: Record<string, unknown> = {};\n\tfor (const layer of layers) {\n\t\tif (typeof layer !== \"object\") {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (const [field, value] of Object.entries(layer)) {\n\t\t\tif (!(field in effective)) {\n\t\t\t\teffective[field] = value;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn effective as Override;\n}\n\nfunction resolveEntries<\n\tEntry extends { readonly redacted?: RedactionLayer<Override> },\n\tOverride extends object,\n>(inputs: {\n\treadonly collection: Readonly<Record<string, Entry>>;\n\treadonly environmentForKind: RedactionLayer<Override>;\n\treadonly envResource: EnvironmentResourceLayer<Override> | undefined;\n}): ReadonlyArray<ResolvedEntry<Entry, Override>> {\n\tconst { collection, environmentForKind, envResource } = inputs;\n\treturn Object.entries(collection).map(([key, entry]) => {\n\t\treturn {\n\t\t\tkey,\n\t\t\tentry,\n\t\t\toverride: resolveEffectiveOverride<Override>([\n\t\t\t\tenvResource?.[key],\n\t\t\t\tentry.redacted,\n\t\t\t\tenvironmentForKind,\n\t\t\t]),\n\t\t};\n\t});\n}\n\nfunction redactCollection<\n\tEntry extends { readonly redacted?: RedactionLayer<Override> },\n\tOverride extends object,\n>(inputs: RedactCollectionInputs<Entry, Override>): Readonly<Record<string, Entry>> | undefined {\n\tconst { collection, environmentForKind, envResource, redact } = inputs;\n\tif (collection === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst resolved = resolveEntries<Entry, Override>({\n\t\tcollection,\n\t\tenvironmentForKind,\n\t\tenvResource,\n\t});\n\n\tif (resolved.every((item) => item.override === undefined)) {\n\t\treturn collection;\n\t}\n\n\treturn Object.fromEntries(\n\t\tresolved.map((item) => {\n\t\t\treturn item.override === undefined\n\t\t\t\t? ([item.key, item.entry] as const)\n\t\t\t\t: ([\n\t\t\t\t\t\titem.key,\n\t\t\t\t\t\tredact({ key: item.key, entry: item.entry, override: item.override }),\n\t\t\t\t\t] as const);\n\t\t}),\n\t);\n}\n\nfunction redactPass(entry: GamePassEntry, override: RedactedGamePassOverride): GamePassEntry {\n\treturn {\n\t\t...entry,\n\t\tname: override.name ?? REDACTED_PASS_NAME,\n\t\tdescription: override.description ?? REDACTED_DESCRIPTION,\n\t\ticon: override.icon ?? { \"en-us\": REDACTED_ICON_PATH },\n\t\t...(entry.price === undefined ? {} : { price: override.price ?? REDACTED_PRICE }),\n\t};\n}\n\nfunction redactPasses(\n\tinputs: RedactKindInputs<GamePassEntry, RedactedGamePassOverride>,\n): ResolvedConfig[\"passes\"] {\n\tconst { collection, envLevel, envResource } = inputs;\n\treturn redactCollection<GamePassEntry, RedactedGamePassOverride>({\n\t\tcollection,\n\t\tenvironmentForKind: pickEnvironmentFields(envLevel, PASS_PRODUCT_ENV_FIELDS),\n\t\tenvResource,\n\t\tredact: (item) => redactPass(item.entry, item.override),\n\t});\n}\n\nfunction redactPlace(\n\tentry: ResolvedPlaceEntry,\n\toverride: RedactedPlaceOverride,\n): ResolvedPlaceEntry {\n\treturn {\n\t\t...entry,\n\t\tdescription: override.description ?? REDACTED_DESCRIPTION,\n\t\tdisplayName: override.displayName ?? entry.displayName,\n\t};\n}\n\nfunction redactPlaces(\n\tinputs: RedactKindInputs<ResolvedPlaceEntry, RedactedPlaceOverride>,\n): ResolvedConfig[\"places\"] {\n\tconst { collection, envLevel, envResource } = inputs;\n\treturn redactCollection<ResolvedPlaceEntry, RedactedPlaceOverride>({\n\t\tcollection,\n\t\tenvironmentForKind: pickEnvironmentFields(envLevel, PLACE_ENV_FIELDS),\n\t\tenvResource,\n\t\tredact: (item) => redactPlace(item.entry, item.override),\n\t});\n}\n\nfunction redactProduct(inputs: ProductRedactionInputs): DeveloperProductEntry {\n\tconst { key, entry, override } = inputs;\n\treturn {\n\t\t...entry,\n\t\tname: override.name ?? defaultRedactedProductName(key),\n\t\tdescription: override.description ?? REDACTED_DESCRIPTION,\n\t\ticon: override.icon ?? { \"en-us\": REDACTED_ICON_PATH },\n\t\t...(entry.price === undefined ? {} : { price: override.price ?? REDACTED_PRICE }),\n\t};\n}\n\nfunction redactProducts(\n\tinputs: RedactKindInputs<DeveloperProductEntry, RedactedDeveloperProductOverride>,\n): ResolvedConfig[\"products\"] {\n\tconst { collection, envLevel, envResource } = inputs;\n\treturn redactCollection<DeveloperProductEntry, RedactedDeveloperProductOverride>({\n\t\tcollection,\n\t\tenvironmentForKind: pickEnvironmentFields(envLevel, PASS_PRODUCT_ENV_FIELDS),\n\t\tenvResource,\n\t\tredact: redactProduct,\n\t});\n}\n\nfunction passHasRealValueEdits(entry: GamePassEntry): boolean {\n\treturn (\n\t\tentry.name !== REDACTED_PASS_NAME ||\n\t\tentry.description !== REDACTED_DESCRIPTION ||\n\t\tentry.icon[\"en-us\"] !== REDACTED_ICON_PATH ||\n\t\t(entry.price !== undefined && entry.price !== REDACTED_PRICE)\n\t);\n}\n\nfunction productHasRealValueEdits(key: string, entry: DeveloperProductEntry): boolean {\n\t// A redacted product's `name` is a placeholder when it equals either the\n\t// suffixed default for this key (what `applyRedaction` synthesizes) or\n\t// the bare `REDACTED_PRODUCT_NAME` constant (what an author may have\n\t// hand-typed). Any other value, including `Hidden Product Deluxe` or a\n\t// suffix that doesn't match this key's hash, is treated as a real edit.\n\tconst isPlaceholderName =\n\t\tentry.name === defaultRedactedProductName(key) || entry.name === REDACTED_PRODUCT_NAME;\n\treturn (\n\t\t!isPlaceholderName ||\n\t\tentry.description !== REDACTED_DESCRIPTION ||\n\t\t(entry.icon !== undefined && entry.icon[\"en-us\"] !== REDACTED_ICON_PATH) ||\n\t\t(entry.price !== undefined && entry.price !== REDACTED_PRICE)\n\t);\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { defu } from \"defu\";\n\nimport { renderDisplayNamePrefix } from \"./display-name-prefix.ts\";\nimport { applyRedaction, type EnvironmentResourceRedaction } from \"./redact-resources.ts\";\nimport type {\n\tConfig,\n\tDeveloperProductEntry,\n\tEnvironmentEntry,\n\tGamePassEntry,\n\tResolvedConfig,\n\tResolvedPlaceEntry,\n\tResolvedUniverseEntry,\n\tUniverseEntry,\n\tWithoutKey,\n} from \"./schema.ts\";\n\n/**\n * Failure surfaced when `selectEnvironment` is asked for an environment\n * name that is not a key of `config.environments`. Carries the list of\n * declared names so callers can render a \"did you mean?\" hint or a\n * close-match suggestion.\n */\nexport interface UnknownEnvironmentError {\n\t/** Environment names that the config actually declared. */\n\treadonly declared: ReadonlyArray<string>;\n\t/** Environment name the caller asked for. */\n\treadonly environment: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"unknownEnvironment\";\n}\n\n/**\n * Failure surfaced when a merged place entry is missing a required field.\n * Two paths reach this error: a root place declared without a matching\n * per-environment overlay supplying `placeId`, and an overlay-only place\n * declared under `environments.X.places` with no matching root entry to\n * supply `filePath`. Surfacing both at the resolution boundary attributes\n * the missing field to the offending entry's key instead of letting\n * `buildDesired` crash with a generic `fileReadFailed` later on.\n */\nexport interface IncompletePlaceEntryError {\n\t/** ResourceKey of the place entry that is missing a required field. */\n\treadonly key: string;\n\t/** Environment whose overlay was projected onto the config. */\n\treadonly environment: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"incompletePlaceEntry\";\n\t/** Field that the merged entry lacks. */\n\treadonly missingField: \"filePath\" | \"placeId\";\n}\n\n/**\n * Failure surfaced when a merged `universe` block lacks `universeId`.\n * The schema-level XOR rule normally prevents this by requiring\n * `universeId` either at the root or on every per-environment overlay;\n * this error remains as a typed safety net for callers that bypass\n * `validateConfig` and hand a `Config` to `selectEnvironment` directly.\n */\nexport interface IncompleteUniverseEntryError {\n\t/** Environment whose overlay was projected onto the config. */\n\treadonly environment: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"incompleteUniverseEntry\";\n\t/** Field that the merged entry lacks. V1 only surfaces `\"universeId\"`. */\n\treadonly missingField: \"universeId\";\n}\n\n/**\n * Failure surfaced when a merged `passes` entry is missing a required\n * field. The most common path here is an overlay-only pass declared\n * under `environments.X.passes` with no matching root entry: the overlay\n * shape is `Partial<GamePassEntry>`, so a typo on the ResourceKey\n * silently produces an incomplete entry that would otherwise be filled\n * in by `applyRedaction` (when `redacted: true` is set) and pushed as a\n * phantom placeholder pass. Surfacing the missing field at the\n * resolution boundary keeps that case attributable instead of letting\n * normalize fail later with a less specific error.\n */\nexport interface IncompletePassEntryError {\n\t/** ResourceKey of the pass entry that is missing a required field. */\n\treadonly key: string;\n\t/** Environment whose overlay was projected onto the config. */\n\treadonly environment: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"incompletePassEntry\";\n\t/** Field that the merged entry lacks. */\n\treadonly missingField: \"description\" | \"icon\" | \"name\";\n}\n\n/** Failure modes returned by {@link selectEnvironment}. */\nexport type SelectEnvironmentError =\n\t| IncompletePassEntryError\n\t| IncompletePlaceEntryError\n\t| IncompleteUniverseEntryError\n\t| UnknownEnvironmentError;\n\n/** Successful return shape for {@link selectMergedEnvironment}. */\ninterface MergedEnvironment {\n\t/** Per-environment entry that the merge projected onto the root config. */\n\treadonly entry: EnvironmentEntry;\n\t/**\n\t * Post-merge, pre-redaction `ResolvedConfig`. Resources that opt into\n\t * redaction still carry their real `name`, `description`, and `icon`\n\t * values, so callers can inspect divergence from placeholder defaults\n\t * before {@link selectEnvironment} substitutes them.\n\t */\n\treadonly merged: ResolvedConfig;\n}\n\n/**\n * Project a `Config` onto a single environment up to the pre-redaction\n * merge boundary. Looks up the env entry, deep-merges its resource overlay\n * over the root config, and runs the same pass, place, and universe\n * completeness checks {@link selectEnvironment} runs, so the returned\n * `merged` config honours the full `ResolvedConfig` contract. Real\n * `name`, `description`, and `icon` values on redacted resources stay\n * intact, letting callers inspect divergence from placeholder defaults\n * before {@link selectEnvironment} substitutes them.\n *\n * @param config - Validated project config.\n * @param environment - Environment name to project onto.\n * @returns The matched env entry plus the merged config, or any of the\n * `SelectEnvironmentError` failure modes.\n */\nexport function selectMergedEnvironment(\n\tconfig: Config,\n\tenvironment: string,\n): Result<MergedEnvironment, SelectEnvironmentError> {\n\tconst entry = config.environments[environment];\n\tif (entry === undefined) {\n\t\treturn { err: unknownEnvironment(config, environment), success: false };\n\t}\n\n\tconst merged = mergeOverlays(config, entry);\n\tconst incompletePass = findIncompletePass(merged, environment);\n\tif (incompletePass !== undefined) {\n\t\treturn { err: incompletePass, success: false };\n\t}\n\n\tconst incompletePlace = findIncompletePlace(merged, environment);\n\tif (incompletePlace !== undefined) {\n\t\treturn { err: incompletePlace, success: false };\n\t}\n\n\tconst incompleteUniverse = findIncompleteUniverse(merged, environment);\n\tif (incompleteUniverse !== undefined) {\n\t\treturn { err: incompleteUniverse, success: false };\n\t}\n\n\treturn { data: { entry, merged }, success: true };\n}\n\n/**\n * Build the per-resource env-overlay redaction layer that `applyRedaction`\n * and `collectRedactionAnnotations` consume. Reads each redactable kind off\n * the environment entry and projects every entry's `redacted` field into\n * the layer; omits kinds the env entry does not declare.\n *\n * @param entry - Environment entry whose overlay redaction values to extract.\n * @returns A `EnvironmentResourceRedaction` ready to pass downstream.\n */\nexport function extractResourceRedaction(entry: EnvironmentEntry): EnvironmentResourceRedaction {\n\treturn {\n\t\t...(entry.passes ? { passes: extractRedactionLayer(entry.passes) } : {}),\n\t\t...(entry.places ? { places: extractRedactionLayer(entry.places) } : {}),\n\t\t...(entry.products ? { products: extractRedactionLayer(entry.products) } : {}),\n\t};\n}\n\n/**\n * Project a validated `Config` onto a single environment. Looks up the\n * matching `environments[environment]` entry, deep-merges its resource\n * overlay (`passes`, `places`, `universe`) over the root config via defu,\n * and applies the env-level state override when present (the env entry's\n * `state` field wins; otherwise the root `state` flows through).\n *\n * Pure: no I/O. Returns a `ResolvedConfig` ready to feed into downstream\n * functions (`flattenConfig`, `buildDefaultRegistry`, `resolveStateConfig`).\n * The post-merge view promotes `places` from `Record<string, PlaceEntry>`\n * (root: file-paths only) to `Record<string, ResolvedPlaceEntry>` (root +\n * overlay merged). `environments` and `extends` are passed through\n * unchanged because they preserve the shape relationship to `Config`;\n * downstream consumers do not read them post-merge.\n *\n * Defu's merge semantics are deliberate: keyed-map collections merge by\n * key (so a place declared in both root and overlay produces a single\n * entry whose overlay-supplied fields win), and `null` / `undefined` in\n * the overlay are skipped (so the overlay never deletes a root field).\n * State has its own resolution path (a single replacement, not a\n * deep-merge) because it is a tagged union: a deep-merge of\n * `{ backend: \"s3\" }` over `{ backend: \"gist\", gistId }` would produce\n * a malformed `{ backend: \"s3\", gistId }`.\n *\n * State is left absent when neither the env override nor the root block\n * provides one. Callers that require a resolved `StateConfig` should\n * route through `resolveStateConfig` or `buildStatePort`; the absent\n * case surfaces as a typed `stateNotConfigured` there.\n *\n * Limitation in v1: a per-environment universe overlay that introduces a\n * brand-new universe block may still have optional fields missing, since\n * the overlay type only requires the identity-bearing key. The resolver\n * surfaces the entry as-is; the universe driver reports the missing\n * field when it tries to consume the entry. Universe is a singleton with\n * 20+ optional fields, so the same `incompletePlaceEntry`-style validation\n * is deferred to a separate follow-up.\n *\n * When the project sets a `displayNamePrefix` (or omits it, in which case\n * prefixing defaults to enabled) and the chosen environment declares a\n * non-empty `label`, the resolver renders the configured template via\n * `renderDisplayNamePrefix` and prepends the result to `universe.displayName`\n * and every declared place `displayName`. An undeclared `displayName`, an\n * empty/absent label, or an explicit `displayNamePrefix.enabled: false` all\n * skip prefixing for the affected fields.\n *\n * @example\n *\n * ```ts\n * import { selectEnvironment } from \"@bedrock-rbx/core\";\n * import type { Config } from \"@bedrock-rbx/core/config\";\n *\n * const config: Config = {\n * environments: {\n * production: { universe: { universeId: \"999\" } },\n * },\n * state: { backend: \"gist\", gistId: \"abc123\" },\n * universe: { voiceChatEnabled: true },\n * };\n *\n * const result = selectEnvironment(config, \"production\");\n *\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.universe?.universeId).toBe(\"999\");\n * expect(result.data.universe?.voiceChatEnabled).toBeTrue();\n * expect(result.data.state?.backend).toBe(\"gist\");\n * }\n * ```\n *\n * @param config - Validated project config carrying at least one\n * environment under `environments`.\n * @param environment - Environment name to project onto. Must be a key\n * of `config.environments`.\n * @returns `Ok(ResolvedConfig)` with the merged resource fields and the\n * resolved state, or `Err(SelectEnvironmentError)` describing why the\n * projection failed.\n */\nexport function selectEnvironment(\n\tconfig: Config,\n\tenvironment: string,\n): Result<ResolvedConfig, SelectEnvironmentError> {\n\tconst mergedResult = selectMergedEnvironment(config, environment);\n\tif (!mergedResult.success) {\n\t\treturn mergedResult;\n\t}\n\n\tconst { entry, merged } = mergedResult.data;\n\treturn { data: redactAndPrefix({ config, entry, merged }), success: true };\n}\n\nfunction findIncompletePass(\n\tmerged: ResolvedConfig,\n\tenvironment: string,\n): IncompletePassEntryError | undefined {\n\tconst { passes } = merged;\n\tif (passes === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst candidates: Record<string, Partial<GamePassEntry>> = passes;\n\tfor (const [key, entry] of Object.entries(candidates)) {\n\t\tif (entry.name === undefined) {\n\t\t\treturn { key, environment, kind: \"incompletePassEntry\", missingField: \"name\" };\n\t\t}\n\n\t\tif (entry.description === undefined) {\n\t\t\treturn {\n\t\t\t\tkey,\n\t\t\t\tenvironment,\n\t\t\t\tkind: \"incompletePassEntry\",\n\t\t\t\tmissingField: \"description\",\n\t\t\t};\n\t\t}\n\n\t\tif (entry.icon === undefined) {\n\t\t\treturn { key, environment, kind: \"incompletePassEntry\", missingField: \"icon\" };\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction mergeEntry<Resolved extends object>(\n\toverlay: Partial<Resolved>,\n\tbase: Partial<Resolved> | undefined,\n): Resolved {\n\t// Precondition for the cast: every public success path of\n\t// `selectEnvironment` MUST run completeness validation (today only\n\t// `findIncompletePlace`) before exposing the merged record to a caller.\n\t// The cast trades compile-time soundness for the freedom to surface\n\t// partial entries to a validator that can attribute the missing field to\n\t// a typed error. New resource kinds that adopt this merge pattern owe\n\t// their own `findIncomplete<Kind>Entry` validator before returning.\n\t//\n\t// defu treats `undefined` as the empty object, so an overlay-only entry\n\t// (no matching root) flows through unchanged. defu's return type is\n\t// `MergeObjects<Partial<Resolved>, Partial<Resolved>>` which the compiler\n\t// cannot prove equals `Resolved`.\n\treturn defu(overlay, base ?? {}) as Resolved;\n}\n\nfunction mergeKeyedRecord<Resolved extends object>(\n\toverlay: Record<string, Partial<Resolved>> | undefined,\n\tbase: Record<string, Partial<Resolved>> | undefined,\n): Record<string, Resolved> | undefined {\n\tif (overlay === undefined) {\n\t\t// Same precondition as `mergeEntry`: passing the base record straight\n\t\t// through is sound only when the caller validates completeness on the\n\t\t// returned record before publishing it.\n\t\treturn base as Record<string, Resolved> | undefined;\n\t}\n\n\treturn {\n\t\t...((base ?? {}) as Record<string, Resolved>),\n\t\t...Object.fromEntries(\n\t\t\tObject.entries(overlay).map(([key, partial]) => {\n\t\t\t\treturn [key, mergeEntry<Resolved>(partial, base?.[key])];\n\t\t\t}),\n\t\t),\n\t};\n}\n\nfunction mergeUniverse(\n\toverlay: Partial<UniverseEntry> | undefined,\n\tbase: undefined | UniverseEntry,\n): ResolvedUniverseEntry | undefined {\n\tif (overlay === undefined && base === undefined) {\n\t\treturn undefined;\n\t}\n\n\t// Precondition for the cast: see `mergeEntry`. The schema-level XOR rule\n\t// guarantees a present `universeId` post-merge whenever the result is\n\t// non-empty, and `findIncompleteUniverse` re-verifies the invariant on\n\t// the success path. The `defu` call type-resolves to a wider partial\n\t// because both sides declare `universeId` as optional; the cast collapses\n\t// it to the resolved shape.\n\treturn defu(overlay ?? {}, base ?? {}) as ResolvedUniverseEntry;\n}\n\nfunction stripRedacted<T extends { readonly redacted?: unknown }>(\n\toverlay: Readonly<Record<string, T>> | undefined,\n): Record<string, WithoutKey<T, \"redacted\">> | undefined {\n\tif (overlay === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn Object.fromEntries(\n\t\tObject.entries(overlay).map(([key, entryValue]) => {\n\t\t\tconst { redacted: _redacted, ...rest } = entryValue;\n\t\t\treturn [key, rest];\n\t\t}),\n\t);\n}\n\nfunction mergeOverlays(config: Config, entry: EnvironmentEntry): ResolvedConfig {\n\tconst passes = mergeKeyedRecord<GamePassEntry>(stripRedacted(entry.passes), config.passes);\n\tconst places = mergeKeyedRecord<ResolvedPlaceEntry>(stripRedacted(entry.places), config.places);\n\tconst products = mergeKeyedRecord<DeveloperProductEntry>(\n\t\tstripRedacted(entry.products),\n\t\tconfig.products,\n\t);\n\tconst universe = mergeUniverse(entry.universe, config.universe);\n\tconst state = entry.state ?? config.state;\n\n\tconst {\n\t\tplaces: _placesRoot,\n\t\tproducts: _productsRoot,\n\t\tuniverse: _universeRoot,\n\t\t...rest\n\t} = config;\n\n\treturn {\n\t\t...rest,\n\t\t...(passes === undefined ? {} : { passes }),\n\t\t...(places === undefined ? {} : { places }),\n\t\t...(products === undefined ? {} : { products }),\n\t\t...(state === undefined ? {} : { state }),\n\t\t...(universe === undefined ? {} : { universe }),\n\t};\n}\n\nfunction unknownEnvironment(config: Config, environment: string): UnknownEnvironmentError {\n\treturn {\n\t\tdeclared: Object.keys(config.environments),\n\t\tenvironment,\n\t\tkind: \"unknownEnvironment\",\n\t};\n}\n\nfunction findIncompleteUniverse(\n\tprojected: ResolvedConfig,\n\tenvironment: string,\n): IncompleteUniverseEntryError | undefined {\n\tconst { universe } = projected;\n\tif (universe === undefined) {\n\t\treturn undefined;\n\t}\n\n\t// `universe` is typed as `ResolvedUniverseEntry` (universeId required)\n\t// because the merge boundary already promised completeness; this routine\n\t// exists to honour that promise at runtime, so it widens the view back to\n\t// `Partial<ResolvedUniverseEntry>` for the duration of the check.\n\tconst candidate: Partial<ResolvedUniverseEntry> = universe;\n\tif (candidate.universeId === undefined) {\n\t\treturn { environment, kind: \"incompleteUniverseEntry\", missingField: \"universeId\" };\n\t}\n\n\treturn undefined;\n}\n\nfunction findIncompletePlace(\n\tprojected: ResolvedConfig,\n\tenvironment: string,\n): IncompletePlaceEntryError | undefined {\n\tconst { places } = projected;\n\tif (places === undefined) {\n\t\treturn undefined;\n\t}\n\n\t// `places` is typed as `Record<string, ResolvedPlaceEntry>` because the\n\t// merge boundary already promised completeness; this routine exists to\n\t// honour that promise at runtime, so it widens the view back to\n\t// `Partial<ResolvedPlaceEntry>` for the duration of the check.\n\tconst candidates: Record<string, Partial<ResolvedPlaceEntry>> = places;\n\tfor (const [key, entry] of Object.entries(candidates)) {\n\t\tif (entry.placeId === undefined) {\n\t\t\treturn {\n\t\t\t\tkey,\n\t\t\t\tenvironment,\n\t\t\t\tkind: \"incompletePlaceEntry\",\n\t\t\t\tmissingField: \"placeId\",\n\t\t\t};\n\t\t}\n\n\t\tif (entry.filePath === undefined) {\n\t\t\treturn {\n\t\t\t\tkey,\n\t\t\t\tenvironment,\n\t\t\t\tkind: \"incompletePlaceEntry\",\n\t\t\t\tmissingField: \"filePath\",\n\t\t\t};\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction extractRedactionLayer<Value>(\n\toverlay: Readonly<Record<string, { readonly redacted?: Value }>>,\n): Readonly<Record<string, Value>> {\n\tconst layer: Record<string, Value> = {};\n\tfor (const [key, entryValue] of Object.entries(overlay)) {\n\t\tif (entryValue.redacted !== undefined) {\n\t\t\tlayer[key] = entryValue.redacted;\n\t\t}\n\t}\n\n\treturn layer;\n}\n\nfunction resolvePrefix(config: Config, entry: EnvironmentEntry): string | undefined {\n\tif (config.displayNamePrefix?.enabled === false) {\n\t\treturn undefined;\n\t}\n\n\tconst { label } = entry;\n\tif (label === undefined || label === \"\") {\n\t\treturn undefined;\n\t}\n\n\treturn renderDisplayNamePrefix(label, config.displayNamePrefix?.format);\n}\n\nfunction applyUniversePrefix(\n\tuniverse: ResolvedUniverseEntry | undefined,\n\tprefix: string | undefined,\n): ResolvedUniverseEntry | undefined {\n\tif (universe === undefined || prefix === undefined || universe.displayName === undefined) {\n\t\treturn universe;\n\t}\n\n\treturn { ...universe, displayName: prefix + universe.displayName };\n}\n\nfunction applyPlacesPrefix(\n\tplaces: Record<string, ResolvedPlaceEntry> | undefined,\n\tprefix: string | undefined,\n): Record<string, ResolvedPlaceEntry> | undefined {\n\tif (places === undefined || prefix === undefined) {\n\t\treturn places;\n\t}\n\n\treturn Object.fromEntries(\n\t\tObject.entries(places).map(([key, place]) => {\n\t\t\tif (place.displayName === undefined) {\n\t\t\t\treturn [key, place];\n\t\t\t}\n\n\t\t\treturn [key, { ...place, displayName: prefix + place.displayName }];\n\t\t}),\n\t);\n}\n\nfunction redactAndPrefix(inputs: {\n\treadonly config: Config;\n\treadonly entry: EnvironmentEntry;\n\treadonly merged: ResolvedConfig;\n}): ResolvedConfig {\n\tconst { config, entry, merged } = inputs;\n\tconst redacted = applyRedaction(merged, {\n\t\tenvLevel: entry.redacted,\n\t\tenvResource: extractResourceRedaction(entry),\n\t});\n\tconst prefix = resolvePrefix(config, entry);\n\tconst places = applyPlacesPrefix(redacted.places, prefix);\n\tconst universe = applyUniversePrefix(redacted.universe, prefix);\n\n\treturn {\n\t\t...redacted,\n\t\t...(places === undefined ? {} : { places }),\n\t\t...(universe === undefined ? {} : { universe }),\n\t};\n}\n","/* eslint-disable max-lines -- exhaustive per-ResourceKind dispatch (applyOne, dispatchByKind, createSucceededEvent) plus progress emission helpers keep the apply pipeline cohesive in one module; splitting would scatter related code without improving navigation. */\nimport { ApiError, type OpenCloudError, type Result } from \"@bedrock-rbx/ocale\";\n\nimport type { Operation } from \"../core/operations.ts\";\nimport type {\n\tDeveloperProductDesiredState,\n\tGamePassDesiredState,\n\tPlaceDesiredState,\n\tResourceCurrentState,\n\tResourceDesiredState,\n\tResourceKind,\n\tUniverseDesiredState,\n} from \"../core/resources.ts\";\nimport type { ProgressEvent, ProgressPort } from \"../ports/progress-port.ts\";\nimport type { DriverRegistry, ResourceDriver } from \"../ports/resource-driver.ts\";\nimport type { ResourceKey } from \"../types/ids.ts\";\n\n/**\n * Optional wiring `applyOps` uses to emit per-resource and aggregate progress\n * events. When omitted, `applyOps` runs silently (backward-compatible with\n * pre-progress callers).\n */\nexport interface ApplyOpsReporting {\n\t/** Environment name stamped on every emitted event. */\n\treadonly environment: string;\n\t/** Sink the apply pipeline pushes events into. */\n\treadonly progress: ProgressPort;\n}\n\n/**\n * Failure surfaced by `applyOps` when an operation cannot be applied.\n * Plain-data discriminated union; narrow on `kind`, do not `instanceof` it.\n * One `ApplyError` describes one failing op; the surrounding\n * `AggregateApplyError` carries the full batch outcome (every survivor and\n * every failure).\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, type ApplyError } from \"@bedrock-rbx/core\";\n *\n * function describe(err: ApplyError): string {\n * switch (err.kind) {\n * case \"driverFailure\": {\n * return `driver failed for ${err.key}: ${err.cause.message}`;\n * }\n * case \"unexpectedThrow\": {\n * return `unexpected error for ${err.key}`;\n * }\n * case \"updateUnsupported\": {\n * return `update not supported for ${err.key}`;\n * }\n * }\n * }\n *\n * const err: ApplyError = {\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"updateUnsupported\",\n * };\n *\n * expect(describe(err)).toBe(\"update not supported for vip-pass\");\n * ```\n */\nexport type ApplyError =\n\t| {\n\t\t\treadonly cause: OpenCloudError;\n\t\t\treadonly key: ResourceKey;\n\t\t\treadonly kind: \"driverFailure\";\n\t }\n\t| {\n\t\t\treadonly cause: unknown;\n\t\t\treadonly key: ResourceKey;\n\t\t\treadonly kind: \"unexpectedThrow\";\n\t }\n\t| {\n\t\t\treadonly key: ResourceKey;\n\t\t\treadonly kind: \"updateUnsupported\";\n\t };\n\n/**\n * Aggregate outcome returned by `applyOps` when one or more ops fail.\n * `applied` is the survivor set in Phase 1 then Phase 2 input order.\n * `failures` is the non-empty list of `ApplyError`s, one per failing op,\n * grouped the same way.\n *\n * @example\n *\n * ```ts\n * import { asResourceKey, type AggregateApplyError } from \"@bedrock-rbx/core\";\n *\n * function summarize(err: AggregateApplyError): string {\n * return `${err.applied.length} survived, ${err.failures.length} failed`;\n * }\n *\n * const err: AggregateApplyError = {\n * applied: [],\n * failures: [{ key: asResourceKey(\"vip-pass\"), kind: \"updateUnsupported\" }],\n * };\n *\n * expect(summarize(err)).toBe(\"0 survived, 1 failed\");\n * ```\n */\nexport interface AggregateApplyError {\n\t/** Survivors persisted to state, in Phase 1 then Phase 2 input order. */\n\treadonly applied: ReadonlyArray<ResourceCurrentState>;\n\t/** Per-op failures, at least one, in Phase 1 then Phase 2 input order. */\n\treadonly failures: readonly [ApplyError, ...ReadonlyArray<ApplyError>];\n}\n\ntype NonNoopOp = Exclude<Operation, { readonly type: \"noop\" }>;\ntype DeveloperProductOp = NonNoopOp & { readonly desired: DeveloperProductDesiredState };\ntype GamePassOp = NonNoopOp & { readonly desired: GamePassDesiredState };\ntype PlaceOp = NonNoopOp & { readonly desired: PlaceDesiredState };\ntype UniverseOp = NonNoopOp & { readonly desired: UniverseDesiredState };\n\ninterface OutcomePair {\n\treadonly op: NonNoopOp;\n\treadonly outcome: Result<ResourceCurrentState, ApplyError>;\n}\n\ninterface DispatchInPhasesInput {\n\treadonly phase1: ReadonlyArray<NonNoopOp>;\n\treadonly phase2: ReadonlyArray<NonNoopOp>;\n\treadonly registry: DriverRegistry;\n\treadonly reporting: ApplyOpsReporting | undefined;\n}\n\ninterface ApplySummaryInput {\n\treadonly end: number;\n\treadonly failures: ReadonlyArray<ApplyError>;\n\treadonly noopCount: number;\n\treadonly pairs: ReadonlyArray<OutcomePair>;\n\treadonly reporting: ApplyOpsReporting | undefined;\n\treadonly start: number;\n}\n\ninterface CreateSucceededInput {\n\treadonly key: ResourceKey;\n\treadonly environment: string;\n\treadonly state: ResourceCurrentState;\n}\n\ninterface TerminalEventInput {\n\treadonly environment: string;\n\treadonly op: NonNoopOp;\n\treadonly outcome: Result<ResourceCurrentState, ApplyError>;\n}\n\ninterface ReportAndDispatchInput {\n\treadonly op: NonNoopOp;\n\treadonly registry: DriverRegistry;\n\treadonly reporting: ApplyOpsReporting | undefined;\n}\n\n/**\n * Dispatch reconciliation operations to their matching drivers in two phases\n * with continue-on-failure semantics. Phase 1 runs universe ops sequentially\n * (singleton per environment; sequencing it before everything else avoids the\n * `displayName` race against the root `Place`). Phase 2 dispatches every\n * remaining non-noop op concurrently via `Promise.all`; every op is\n * attempted regardless of earlier failures.\n *\n * Behaviour:\n * - `create` operations route to `registry[op.desired.kind].create`.\n * - `update` operations route to `registry[op.desired.kind].update` when the\n * driver exposes it; otherwise they yield an `updateUnsupported`\n * `ApplyError` without invoking the driver.\n * - `noop` operations are skipped entirely (no I/O, no dispatch).\n * - A driver that throws outside its `Result` contract is caught at the\n * dispatch boundary and translated to an `unexpectedThrow` `ApplyError`\n * scoped to that op alone; the rest of the batch keeps running.\n *\n * On Ok the returned array carries driver outputs for every non-noop op\n * in phase order: Phase 1 universe entries first, then Phase 2 entries in\n * their input order. Noops are not represented; callers needing a full\n * post-apply snapshot merge with the pre-apply current state keyed by\n * `ResourceKey`.\n *\n * On Err the aggregate carries every survivor in `applied` (Phase 1 first,\n * then Phase 2 input order) and every failure in `failures` with the same\n * grouping. Neither array reflects completion order.\n *\n * @param ops - Reconciliation operations produced by `diff`, applied in\n * declaration order.\n * @param registry - Per-kind driver table; dispatch uses `op.desired.kind`\n * as the index.\n * @param reporting - Optional progress wiring. When supplied, `applyOps`\n * emits one `resourceOpStarted` and one terminal event per non-noop op,\n * one `resourceOpNoop` per noop op, and a final `applySummary` carrying\n * the per-type counts and the wall-clock apply duration. When omitted,\n * no events fire.\n * @returns `Ok(state)` when every op succeeded; otherwise\n * `Err(AggregateApplyError)` with the survivors and the non-empty\n * failures tuple.\n * @example\n *\n * ```ts\n * import { applyOps, type DriverRegistry } from \"@bedrock-rbx/core\";\n *\n * const noopRegistry: DriverRegistry = {\n * developerProduct: { create: async () => ({ err: new Error(\"stub\") as never, success: false }) },\n * gamePass: { create: async () => ({ err: new Error(\"stub\") as never, success: false }) },\n * place: { create: async () => ({ err: new Error(\"stub\") as never, success: false }) },\n * universe: { create: async () => ({ err: new Error(\"stub\") as never, success: false }) },\n * };\n *\n * return applyOps([], noopRegistry).then((result) => {\n * expect(result).toStrictEqual({ data: [], success: true });\n * });\n * ```\n */\n// eslint-disable-next-line better-max-params/better-max-params -- additive optional progress hook on the established two-positional-deps shape; folding into an options bag would break every caller for no semantic gain.\nexport async function applyOps(\n\tops: ReadonlyArray<Operation>,\n\tregistry: DriverRegistry,\n\treporting?: ApplyOpsReporting,\n): Promise<Result<ReadonlyArray<ResourceCurrentState>, AggregateApplyError>> {\n\tconst start = Date.now();\n\tconst { noopCount, phase1, phase2 } = partitionAndEmitNoops(ops, reporting);\n\tconst pairs = await dispatchInPhases({ phase1, phase2, registry, reporting });\n\tconst end = Date.now();\n\n\tconst { applied, failures } = partitionOutcomes(pairs.map((pair) => pair.outcome));\n\temitApplySummary({ end, failures, noopCount, pairs, reporting, start });\n\n\tconst [head, ...tail] = failures;\n\tif (head === undefined) {\n\t\treturn { data: applied, success: true };\n\t}\n\n\treturn { err: { applied, failures: [head, ...tail] }, success: false };\n}\n\nfunction driverFailure(\n\tkey: ResourceKey,\n\tcause: OpenCloudError,\n): Result<ResourceCurrentState, ApplyError> {\n\treturn { err: { key, cause, kind: \"driverFailure\" }, success: false };\n}\n\nfunction kindMismatch(key: ResourceKey, mismatch: { actual: string; expected: string }): ApiError {\n\treturn new ApiError(\n\t\t`internal: operation kind mismatch for ${key}: expected ${mismatch.expected}, got ${mismatch.actual}`,\n\t\t{ statusCode: 0 },\n\t);\n}\n\nasync function applyOne<K extends ResourceKind>(\n\top: NonNoopOp & { readonly desired: Extract<ResourceDesiredState, { kind: K }> },\n\tdriver: ResourceDriver<K>,\n): Promise<Result<ResourceCurrentState, ApplyError>> {\n\tif (op.type === \"create\") {\n\t\tconst created = await driver.create(op.desired);\n\t\treturn created.success ? created : driverFailure(op.key, created.err);\n\t}\n\n\tif (driver.update === undefined) {\n\t\treturn { err: { key: op.key, kind: \"updateUnsupported\" }, success: false };\n\t}\n\n\tif (op.current.kind !== op.desired.kind) {\n\t\treturn driverFailure(\n\t\t\top.key,\n\t\t\tkindMismatch(op.key, { actual: op.current.kind, expected: op.desired.kind }),\n\t\t);\n\t}\n\n\tconst updated = await driver.update(op.current as ResourceCurrentState<K>, op.desired);\n\treturn updated.success ? updated : driverFailure(op.key, updated.err);\n}\n\nasync function dispatchByKind(\n\top: NonNoopOp,\n\tregistry: DriverRegistry,\n): Promise<Result<ResourceCurrentState, ApplyError>> {\n\t// Exhaustive switch: adding a new ResourceKind is a compile error here\n\t// until an arm lands. Each arm casts because custom type narrowing does\n\t// not propagate through a non-distributive union.\n\tswitch (op.desired.kind) {\n\t\tcase \"developerProduct\": {\n\t\t\treturn applyOne(op as DeveloperProductOp, registry.developerProduct);\n\t\t}\n\t\tcase \"gamePass\": {\n\t\t\treturn applyOne(op as GamePassOp, registry.gamePass);\n\t\t}\n\t\tcase \"place\": {\n\t\t\treturn applyOne(op as PlaceOp, registry.place);\n\t\t}\n\t\tcase \"universe\": {\n\t\t\treturn applyOne(op as UniverseOp, registry.universe);\n\t\t}\n\t}\n}\n\nasync function dispatchOp(\n\top: NonNoopOp,\n\tregistry: DriverRegistry,\n): Promise<Result<ResourceCurrentState, ApplyError>> {\n\ttry {\n\t\treturn await dispatchByKind(op, registry);\n\t} catch (err) {\n\t\treturn { err: { key: op.key, cause: err, kind: \"unexpectedThrow\" }, success: false };\n\t}\n}\n\n/* eslint-disable-next-line max-lines-per-function -- exhaustive per-ResourceKind switch with literal returns required for per-kind narrowing of `outputs`; consolidating would either reintroduce casts or hide the discriminator. */\nfunction createSucceededEvent(input: CreateSucceededInput): ProgressEvent {\n\tconst { key, environment, state } = input;\n\tswitch (state.kind) {\n\t\tcase \"developerProduct\": {\n\t\t\treturn {\n\t\t\t\tkey,\n\t\t\t\tenvironment,\n\t\t\t\tkind: \"resourceOpSucceeded\",\n\t\t\t\topType: \"create\",\n\t\t\t\toutputs: state.outputs,\n\t\t\t\tresourceKind: \"developerProduct\",\n\t\t\t};\n\t\t}\n\t\tcase \"gamePass\": {\n\t\t\treturn {\n\t\t\t\tkey,\n\t\t\t\tenvironment,\n\t\t\t\tkind: \"resourceOpSucceeded\",\n\t\t\t\topType: \"create\",\n\t\t\t\toutputs: state.outputs,\n\t\t\t\tresourceKind: \"gamePass\",\n\t\t\t};\n\t\t}\n\t\tcase \"place\": {\n\t\t\treturn {\n\t\t\t\tkey,\n\t\t\t\tenvironment,\n\t\t\t\tkind: \"resourceOpSucceeded\",\n\t\t\t\topType: \"create\",\n\t\t\t\toutputs: state.outputs,\n\t\t\t\tresourceKind: \"place\",\n\t\t\t};\n\t\t}\n\t\tcase \"universe\": {\n\t\t\treturn {\n\t\t\t\tkey,\n\t\t\t\tenvironment,\n\t\t\t\tkind: \"resourceOpSucceeded\",\n\t\t\t\topType: \"create\",\n\t\t\t\toutputs: state.outputs,\n\t\t\t\tresourceKind: \"universe\",\n\t\t\t};\n\t\t}\n\t}\n}\n\nfunction toTerminalEvent(input: TerminalEventInput): ProgressEvent {\n\tconst { environment, op, outcome } = input;\n\tif (!outcome.success) {\n\t\treturn {\n\t\t\tkey: op.key,\n\t\t\tenvironment,\n\t\t\terror: outcome.err,\n\t\t\tkind: \"resourceOpFailed\",\n\t\t\topType: op.type,\n\t\t\tresourceKind: op.desired.kind,\n\t\t};\n\t}\n\n\tif (op.type === \"update\") {\n\t\treturn {\n\t\t\tkey: op.key,\n\t\t\tchangedFields: op.changedFields,\n\t\t\tenvironment,\n\t\t\tkind: \"resourceOpSucceeded\",\n\t\t\topType: \"update\",\n\t\t\tresourceKind: op.desired.kind,\n\t\t};\n\t}\n\n\treturn createSucceededEvent({ key: op.key, environment, state: outcome.data });\n}\n\nasync function reportAndDispatch(input: ReportAndDispatchInput): Promise<OutcomePair> {\n\tconst { op, registry, reporting } = input;\n\tif (reporting !== undefined) {\n\t\treporting.progress.emit({\n\t\t\tkey: op.key,\n\t\t\tenvironment: reporting.environment,\n\t\t\tkind: \"resourceOpStarted\",\n\t\t\topType: op.type,\n\t\t\tresourceKind: op.desired.kind,\n\t\t});\n\t}\n\n\tconst outcome = await dispatchOp(op, registry);\n\tif (reporting !== undefined) {\n\t\treporting.progress.emit(\n\t\t\ttoTerminalEvent({ environment: reporting.environment, op, outcome }),\n\t\t);\n\t}\n\n\treturn { op, outcome };\n}\n\nasync function dispatchInPhases(input: DispatchInPhasesInput): Promise<ReadonlyArray<OutcomePair>> {\n\tconst phase1Pairs: Array<OutcomePair> = [];\n\tfor (const op of input.phase1) {\n\t\tphase1Pairs.push(\n\t\t\tawait reportAndDispatch({ op, registry: input.registry, reporting: input.reporting }),\n\t\t);\n\t}\n\n\tconst phase2Pairs = await Promise.all(\n\t\tinput.phase2.map(async (op) => {\n\t\t\treturn reportAndDispatch({ op, registry: input.registry, reporting: input.reporting });\n\t\t}),\n\t);\n\treturn [...phase1Pairs, ...phase2Pairs];\n}\n\nfunction emitApplySummary(input: ApplySummaryInput): void {\n\tif (input.reporting === undefined) {\n\t\treturn;\n\t}\n\n\tconst created = input.pairs.filter(\n\t\t(pair) => pair.outcome.success && pair.op.type === \"create\",\n\t).length;\n\tconst updated = input.pairs.filter(\n\t\t(pair) => pair.outcome.success && pair.op.type === \"update\",\n\t).length;\n\tinput.reporting.progress.emit({\n\t\tcreated,\n\t\tdurationMs: input.end - input.start,\n\t\tenvironment: input.reporting.environment,\n\t\tfailed: input.failures.length,\n\t\tkind: \"applySummary\",\n\t\tnoop: input.noopCount,\n\t\tupdated,\n\t});\n}\n\nfunction partitionOutcomes(outcomes: ReadonlyArray<Result<ResourceCurrentState, ApplyError>>): {\n\treadonly applied: ReadonlyArray<ResourceCurrentState>;\n\treadonly failures: ReadonlyArray<ApplyError>;\n} {\n\tconst applied = outcomes.flatMap((outcome) => (outcome.success ? [outcome.data] : []));\n\tconst failures = outcomes.flatMap((outcome) => (outcome.success ? [] : [outcome.err]));\n\treturn { applied, failures };\n}\n\nfunction emitNoop(\n\top: Extract<Operation, { readonly type: \"noop\" }>,\n\treporting: ApplyOpsReporting | undefined,\n): void {\n\tif (reporting === undefined) {\n\t\treturn;\n\t}\n\n\treporting.progress.emit({\n\t\tkey: op.key,\n\t\tenvironment: reporting.environment,\n\t\tkind: \"resourceOpNoop\",\n\t\tresourceKind: op.kind,\n\t});\n}\n\nfunction partitionAndEmitNoops(\n\tops: ReadonlyArray<Operation>,\n\treporting: ApplyOpsReporting | undefined,\n): {\n\treadonly noopCount: number;\n\treadonly phase1: ReadonlyArray<NonNoopOp>;\n\treadonly phase2: ReadonlyArray<NonNoopOp>;\n} {\n\tconst phase1: Array<NonNoopOp> = [];\n\tconst phase2: Array<NonNoopOp> = [];\n\tlet noopCount = 0;\n\tfor (const op of ops) {\n\t\tif (op.type === \"noop\") {\n\t\t\tnoopCount += 1;\n\t\t\temitNoop(op, reporting);\n\t\t} else if (op.desired.kind === \"universe\") {\n\t\t\tphase1.push(op);\n\t\t} else {\n\t\t\tphase2.push(op);\n\t\t}\n\t}\n\n\treturn { noopCount, phase1, phase2 };\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\nimport { DeveloperProductsClient } from \"@bedrock-rbx/ocale/developer-products\";\nimport { GamePassesClient } from \"@bedrock-rbx/ocale/game-passes\";\nimport { PlacesClient } from \"@bedrock-rbx/ocale/places\";\nimport { UniversesClient } from \"@bedrock-rbx/ocale/universes\";\n\nimport { createDeveloperProductDriver } from \"../adapters/developer-product-driver.ts\";\nimport { createGamePassDriver } from \"../adapters/game-pass-driver.ts\";\nimport { createPlaceDriver } from \"../adapters/place-driver.ts\";\nimport { createUniverseDriver } from \"../adapters/universe-driver.ts\";\nimport type { ResolvedConfig } from \"../core/schema.ts\";\nimport type { DriverRegistry } from \"../ports/resource-driver.ts\";\nimport { asRobloxAssetId } from \"../types/ids.ts\";\nimport type { MissingCredentialError } from \"./build-state-port.ts\";\n\n/**\n * Failure surfaced when default-constructing a registry needs a config\n * field that is not present. The deploy boundary wraps this in a\n * `DeployError` so the caller sees a typed Result instead of a downstream\n * driver error.\n */\nexport interface RegistryConfigError {\n\t/** Suggested fix routed back to the caller. */\n\treadonly hint: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"registryConfigMissing\";\n\t/** Which config field was missing. */\n\treadonly missing: \"universeId\";\n}\n\n/** Inputs for {@link buildDefaultRegistry}. */\ninterface BuildDefaultRegistryDeps {\n\t/** Resolved project config; supplies `universe.universeId` and is read for nothing else. */\n\treadonly config: ResolvedConfig;\n\t/** Reads an environment variable; injected so tests stay free of `process.env`. */\n\treadonly getEnv: (name: string) => string | undefined;\n\t/** Reader plumbed into kind-specific drivers that ingest file bytes. */\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n}\n\ninterface AssembleRegistryInputs {\n\treadonly apiKey: string;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\treadonly universeId: ReturnType<typeof asRobloxAssetId>;\n}\n\n/**\n * Construct the default `DriverRegistry` from `config.universe.universeId`\n * and `BEDROCK_API_KEY`. Reads the API key via the injected `getEnv` seam\n * and surfaces `missingCredential` or `registryConfigMissing` as typed\n * Results instead of throwing.\n *\n * @example\n *\n * ```ts\n * import { buildDefaultRegistry } from \"@bedrock-rbx/core\";\n *\n * const registry = buildDefaultRegistry({\n * config: {\n * environments: { production: {} },\n * state: { backend: \"gist\", gistId: \"abc\" },\n * universe: { universeId: \"1234567890\" },\n * },\n * getEnv: () => \"rbx-test\",\n * readFile: async () => new Uint8Array(),\n * });\n *\n * expect(registry.success).toBeTrue();\n * ```\n *\n * @param deps - Validated config plus credential and file-reader seams.\n * @returns A `DriverRegistry` on success, or a typed Err describing the\n * missing API key or the missing universe declaration.\n */\nexport function buildDefaultRegistry(\n\tdeps: BuildDefaultRegistryDeps,\n): Result<DriverRegistry, MissingCredentialError | RegistryConfigError> {\n\tconst apiKey = deps.getEnv(\"BEDROCK_API_KEY\");\n\tif (apiKey === undefined) {\n\t\treturn missingApiKey();\n\t}\n\n\tconst rawUniverseId = deps.config.universe?.universeId;\n\tif (rawUniverseId === undefined) {\n\t\treturn missingUniverseId();\n\t}\n\n\treturn {\n\t\tdata: assembleRegistry({\n\t\t\tapiKey,\n\t\t\treadFile: deps.readFile,\n\t\t\tuniverseId: asRobloxAssetId(rawUniverseId),\n\t\t}),\n\t\tsuccess: true,\n\t};\n}\n\nfunction missingApiKey(): Result<DriverRegistry, MissingCredentialError> {\n\treturn {\n\t\terr: {\n\t\t\tkind: \"missingCredential\",\n\t\t\tpurpose: \"registry\",\n\t\t\tvariable: \"BEDROCK_API_KEY\",\n\t\t},\n\t\tsuccess: false,\n\t};\n}\n\nfunction missingUniverseId(): Result<DriverRegistry, RegistryConfigError> {\n\treturn {\n\t\terr: {\n\t\t\thint: \"declare universe.universeId in bedrock.config.ts\",\n\t\t\tkind: \"registryConfigMissing\",\n\t\t\tmissing: \"universeId\",\n\t\t},\n\t\tsuccess: false,\n\t};\n}\n\nfunction assembleRegistry(inputs: AssembleRegistryInputs): DriverRegistry {\n\tconst { apiKey, readFile, universeId } = inputs;\n\tconst developerProducts = new DeveloperProductsClient({ apiKey });\n\tconst gamePasses = new GamePassesClient({ apiKey });\n\tconst places = new PlacesClient({ apiKey });\n\tconst universes = new UniversesClient({ apiKey });\n\n\treturn {\n\t\tdeveloperProduct: createDeveloperProductDriver({\n\t\t\tclient: developerProducts,\n\t\t\treadFile,\n\t\t\tuniverseId,\n\t\t}),\n\t\tgamePass: createGamePassDriver({ client: gamePasses, readFile, universeId }),\n\t\tplace: createPlaceDriver({ client: places, readFile, universeId }),\n\t\tuniverse: createUniverseDriver({ places, universes }),\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport type { ResourceDesiredInput } from \"../core/flatten.ts\";\nimport { defaultKindRegistry } from \"../core/kinds/index.ts\";\nimport type { BuildDesiredError, ResourceKindModule } from \"../core/kinds/module.ts\";\nimport type { ResourceDesiredState, ResourceKind } from \"../core/resources.ts\";\n\nexport type { BuildDesiredError } from \"../core/kinds/module.ts\";\n\n/**\n * Layer file I/O onto a flat tagged list of resource inputs to produce\n * `ResourceDesiredState`.\n *\n * For each input, reads the file bytes via the injected `readFile`, computes\n * the SHA-256 hex digest, and assembles the branded desired-state record\n * that `diff` consumes. Entries are processed sequentially so the first\n * failure's attribution is deterministic.\n *\n * @param inputs - Flat tagged resource inputs from `flattenConfig`.\n * @param readFile - Reads file bytes for a given path; rejection becomes a\n * `fileReadFailed` Err.\n * @returns `Ok` with the desired-state array (same length and order as\n * `inputs`), or `Err` with the first I/O failure.\n * @example\n *\n * ```ts\n * import { asResourceKey, buildDesired } from \"@bedrock-rbx/core\";\n *\n * async function readFile(): Promise<Uint8Array> {\n * return new Uint8Array([1, 2, 3]);\n * }\n *\n * return buildDesired(\n * [\n * {\n * description: \"Grants VIP perks.\",\n * icon: { \"en-us\": \"assets/vip-icon.png\" },\n * key: asResourceKey(\"vip-pass\"),\n * kind: \"gamePass\",\n * name: \"VIP Pass\",\n * price: 500,\n * },\n * ],\n * readFile,\n * ).then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data).toHaveLength(1);\n * expect(result.data[0]!.kind).toBe(\"gamePass\");\n * }\n * });\n * ```\n */\nexport async function buildDesired(\n\tinputs: ReadonlyArray<ResourceDesiredInput>,\n\treadFile: (path: string) => Promise<Uint8Array>,\n): Promise<Result<ReadonlyArray<ResourceDesiredState>, BuildDesiredError>> {\n\tconst desired: Array<ResourceDesiredState> = [];\n\tconst io = { readFile };\n\tfor (const input of inputs) {\n\t\t// Registry index returns a union of per-kind modules; widening its\n\t\t// type parameter lets us call normalize without per-kind\n\t\t// discriminator narrowing. Safe because input.kind pins which\n\t\t// module is selected.\n\t\tconst module = defaultKindRegistry[input.kind] as ResourceKindModule<ResourceKind>;\n\t\tconst normalized = await module.normalize(input, io);\n\t\tif (!normalized.success) {\n\t\t\treturn normalized;\n\t\t}\n\n\t\tdesired.push(normalized.data);\n\t}\n\n\treturn { data: desired, success: true };\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { createGistStateAdapter, type GistFetch } from \"../adapters/gist-state-adapter.ts\";\nimport { type GistStateConfig, isGistStateConfig, type StateConfig } from \"../core/schema.ts\";\nimport type { StatePort } from \"../ports/state-port.ts\";\n\n/**\n * Failure surfaced when a default-constructed adapter cannot find a\n * required environment variable. The deploy boundary wraps this in a\n * `DeployError` so the caller sees a typed Result instead of an\n * exception or a confusing downstream HTTP error.\n */\nexport interface MissingCredentialError {\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"missingCredential\";\n\t/** Whether the credential was needed for the state backend or the driver registry. */\n\treadonly purpose: \"registry\" | \"stateBackend\";\n\t/** Environment variable name the default-construction path tried to read. */\n\treadonly variable: string;\n}\n\n/**\n * Failure surfaced when the dispatch helper sees a `state.backend` value\n * it does not recognize. The hint points at `opts.statePort` so the\n * caller can pass a custom adapter as an escape hatch.\n */\nexport interface UnsupportedBackendError {\n\t/** Backend name read from `state.backend`. */\n\treadonly backend: string;\n\t/** Suggested escape hatch routed back to the caller. */\n\treadonly hint: string;\n\t/** Literal discriminator for narrowing. */\n\treadonly kind: \"unsupportedBackend\";\n}\n\n/** Inputs for {@link buildStatePort}. */\ninterface BuildStatePortDeps {\n\t/** Optional `fetch` seam plumbed through to the gist adapter for tests. */\n\treadonly fetch?: GistFetch | undefined;\n\t/** Reads an environment variable; injected so tests stay free of `process.env`. */\n\treadonly getEnv: (name: string) => string | undefined;\n\t/** Resolved state configuration for the target environment. */\n\treadonly stateConfig: StateConfig;\n}\n\nconst STATE_PORT_HINT = \"pass a custom statePort via opts.statePort\";\n\n/**\n * Construct a `StatePort` from a resolved `StateConfig`. Dispatches on\n * `stateConfig.backend` to the matching builtin adapter; reads the\n * required credential from `getEnv` and surfaces `missingCredential` or\n * `unsupportedBackend` as typed Results.\n *\n * @example\n *\n * ```ts\n * import { buildStatePort } from \"@bedrock-rbx/core\";\n *\n * const port = buildStatePort({\n * fetch: async () =>\n * new Response(JSON.stringify({ files: {} }), { status: 200 }),\n * getEnv: (name) => (name === \"BEDROCK_GITHUB_TOKEN\" ? \"ghp_example\" : undefined),\n * stateConfig: { backend: \"gist\", gistId: \"abc123\" },\n * });\n *\n * expect(port.success).toBeTrue();\n * ```\n *\n * @param deps - Resolved state config plus credential-injection seams.\n * @returns A `StatePort` on success, or a typed Err describing the\n * missing credential or the unsupported backend.\n */\nexport function buildStatePort(\n\tdeps: BuildStatePortDeps,\n): Result<StatePort, MissingCredentialError | UnsupportedBackendError> {\n\tif (isGistStateConfig(deps.stateConfig)) {\n\t\treturn buildGistStatePort(deps.stateConfig, deps);\n\t}\n\n\treturn {\n\t\terr: {\n\t\t\tbackend: deps.stateConfig.backend,\n\t\t\thint: STATE_PORT_HINT,\n\t\t\tkind: \"unsupportedBackend\",\n\t\t},\n\t\tsuccess: false,\n\t};\n}\n\nfunction buildGistStatePort(\n\tstateConfig: GistStateConfig,\n\tdeps: BuildStatePortDeps,\n): Result<StatePort, MissingCredentialError> {\n\tconst token = deps.getEnv(\"BEDROCK_GITHUB_TOKEN\") ?? deps.getEnv(\"GITHUB_TOKEN\");\n\tif (token === undefined) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tkind: \"missingCredential\",\n\t\t\t\tpurpose: \"stateBackend\",\n\t\t\t\tvariable: \"BEDROCK_GITHUB_TOKEN\",\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn {\n\t\tdata: createGistStateAdapter({\n\t\t\tfetch: deps.fetch,\n\t\t\tgistId: stateConfig.gistId,\n\t\t\ttoken,\n\t\t}),\n\t\tsuccess: true,\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport type { ResourceKey } from \"../types/ids.ts\";\nimport { defaultKindRegistry } from \"./kinds/index.ts\";\nimport type { BuildDesiredError, ResourceKindModule } from \"./kinds/module.ts\";\nimport type { ResourceCurrentState, ResourceDesiredState, ResourceKind } from \"./resources.ts\";\n\n/**\n * Batch reconcilability check that runs after `buildDesired` and before\n * `diff`. Walks paired `(kind, key)` entries and dispatches to each\n * kind module's optional `assertReconcilable` hook so kind-specific\n * rejections (e.g. Removing a developer-product icon, which the upstream\n * API has no documented unset path for) surface as typed errors before\n * `diff` runs and before any apply-side driver I/O is attempted.\n *\n * Pure and synchronous. Current-only entries (no matching desired) are\n * ignored: their reconciliation is `diff`'s concern, not this seam's.\n *\n * @param desired - Desired state from `buildDesired`.\n * @param current - Prior current state from the state port.\n * @returns `Ok(undefined)` when every paired entry passes its kind-level\n * reconcilability check, or the first `Err` returned by a hook.\n */\nexport function assertAllReconcilable(\n\tdesired: ReadonlyArray<ResourceDesiredState>,\n\tcurrent: ReadonlyArray<ResourceCurrentState>,\n): Result<undefined, BuildDesiredError> {\n\tconst collision = detectProductNameCollision(desired);\n\tif (collision !== undefined) {\n\t\treturn collision;\n\t}\n\n\tconst currentByKey = new Map(current.map((entry) => [compositeKey(entry), entry]));\n\tfor (const entry of desired) {\n\t\tconst matched = currentByKey.get(compositeKey(entry));\n\t\tif (matched === undefined) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst module = defaultKindRegistry[entry.kind] as ResourceKindModule<ResourceKind>;\n\t\tconst check = module.assertReconcilable?.(matched, entry);\n\t\tif (check !== undefined && !check.success) {\n\t\t\treturn check;\n\t\t}\n\t}\n\n\treturn { data: undefined, success: true };\n}\n\nfunction compositeKey(resource: { readonly key: string; readonly kind: ResourceKind }): string {\n\treturn `${resource.kind}:${resource.key}`;\n}\n\nfunction detectProductNameCollision(\n\tdesired: ReadonlyArray<ResourceDesiredState>,\n): Result<undefined, BuildDesiredError> | undefined {\n\tconst seenByName = new Map<string, ResourceKey>();\n\tfor (const entry of desired) {\n\t\tif (entry.kind !== \"developerProduct\") {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst prior = seenByName.get(entry.name);\n\t\tif (prior === undefined) {\n\t\t\tseenByName.set(entry.name, entry.key);\n\t\t\tcontinue;\n\t\t}\n\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tkeys: [prior, entry.key],\n\t\t\t\tkind: \"redactedNameCollision\",\n\t\t\t\tmessage: `developer products '${prior}' and '${entry.key}' both resolve to the wire name '${entry.name}'. Roblox enforces per-universe uniqueness on developer-product names, so the second update would be rejected as DuplicateProductName. Set 'redacted: { name: \"<unique>\" }' on one of them to disambiguate.`,\n\t\t\t\tresolvedName: entry.name,\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn undefined;\n}\n","const LUAU_BOOTSTRAP_TEMP_PREFIX = \"bedrock-luau-bootstrap-\";\n\n/**\n * Build the `mkdtempSync` prefix used for Luau bootstrap directories.\n *\n * Embedding `process.pid` scopes the directory to its creator. Concurrent\n * processes share `tmpdir()`, so without a pid component they would observe\n * each other's in-flight bootstrap dirs whenever any caller scans the temp\n * directory.\n * @param pid - Process id to embed in the prefix; pass `process.pid` from\n * production callers.\n * @returns The full prefix to pass to `mkdtempSync`, including the trailing\n * separator that `mkdtempSync` appends its random suffix to.\n */\nexport function bootstrapDirectoryPrefix(pid: number): string {\n\treturn `${LUAU_BOOTSTRAP_TEMP_PREFIX}${pid}-`;\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { execFile } from \"node:child_process\";\nimport { mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { basename, dirname, join } from \"node:path\";\nimport process from \"node:process\";\n\nimport type { LuauEvaluationError, LuauEvaluator } from \"../ports/luau-evaluator.ts\";\nimport { bootstrapDirectoryPrefix } from \"../shell/load-config-internal.ts\";\n\nconst SENTINEL_BASE = \"__BEDROCK_LUAU_\";\nconst OK_PREFIX = `${SENTINEL_BASE}OK__`;\nconst ERR_PREFIX = `${SENTINEL_BASE}ERR__`;\n\nconst LUTE_BOOTSTRAP_LUAU = `--!strict\nlocal json = require(\"@std/json\")\nlocal process = require(\"@std/process\")\nlocal io = require(\"@std/io\")\n\nlocal function emit(kind, payload)\n io.write(\"${SENTINEL_BASE}\" .. kind .. \"__\")\n io.write(json.serialize(payload))\nend\n\nlocal userBasename = process.args[2]\n-- The user file lives in a different directory from this bootstrap, so we\n-- require it via the @user alias defined in the .luaurc written alongside.\nlocal req = \"@user/\" .. string.gsub(userBasename, \"%.luau$\", \"\")\n\nlocal loadOk, modOrErr = pcall(require, req)\nif not loadOk then\n emit(\"ERR\", { kind = \"loadFailed\", message = tostring(modOrErr) })\n return\nend\n\nlocal value = if type(modOrErr) == \"function\" then modOrErr() else modOrErr\n\nlocal encOk, encoded = pcall(json.serialize, value)\nif not encOk then\n emit(\"ERR\", { kind = \"serializeFailed\", message = tostring(encoded) })\n return\nend\n\nio.write(\"${OK_PREFIX}\")\nio.write(encoded)\n`;\n\nconst LUAU_RUNTIME_HINT =\n\t\"install lute (e.g. `mise install` with `github:luau-lang/lute`) or set BEDROCK_LUTE_PATH to the binary.\";\n\ninterface LuteRunOptions {\n\treadonly bin: string;\n\treadonly bootstrapPath: string;\n\treadonly userBasename: string;\n}\n\nfunction isEnoentError(error: unknown): boolean {\n\treturn error instanceof Error && \"code\" in error && error.code === \"ENOENT\";\n}\n\n// 5s is generous for evaluating a config file: real configs run in\n// milliseconds, and a value this high is meant to catch infinite loops in\n// user code (or a hung lute) without surprising slow-startup environments.\nconst LUTE_BOOTSTRAP_TIMEOUT_MS = 5_000;\n\n/**\n * Build the default `LuauEvaluator` adapter that shells out to the `lute`\n * runtime. Reads `BEDROCK_LUTE_PATH` from `process.env` once per call to pick\n * the binary, so tests can override it via env var without rebuilding the\n * adapter.\n * @returns A `LuauEvaluator` that spawns `lute run` per call.\n */\nexport function createLuteLuauEvaluator(): LuauEvaluator {\n\treturn evaluateLuauWithLute;\n}\n\nfunction setupBootstrapDirectory(userCwd: string): string {\n\tconst bootstrapDirectory = mkdtempSync(join(tmpdir(), bootstrapDirectoryPrefix(process.pid)));\n\twriteFileSync(join(bootstrapDirectory, \"bootstrap.luau\"), LUTE_BOOTSTRAP_LUAU);\n\t// Lute resolves `require` relative to the calling script's directory, not\n\t// the process cwd. The bootstrap lives in a temp dir, so we expose the\n\t// user's directory via a `.luaurc` alias that the bootstrap requires by\n\t// name.\n\twriteFileSync(\n\t\tjoin(bootstrapDirectory, \".luaurc\"),\n\t\tJSON.stringify({ aliases: { user: userCwd } }),\n\t);\n\treturn bootstrapDirectory;\n}\n\nasync function runLuteBootstrap(runOptions: LuteRunOptions): Promise<string> {\n\tconst { bin, bootstrapPath, userBasename } = runOptions;\n\treturn new Promise((resolve, reject) => {\n\t\texecFile(\n\t\t\tbin,\n\t\t\t[\"run\", bootstrapPath, userBasename],\n\t\t\t{ encoding: \"utf8\", timeout: LUTE_BOOTSTRAP_TIMEOUT_MS },\n\t\t\t(error, stdout) => {\n\t\t\t\tif (error instanceof Error) {\n\t\t\t\t\treject(error);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve(stdout);\n\t\t\t},\n\t\t);\n\t});\n}\n\nfunction parseBootstrapOutput(stdout: string): Record<string, unknown> {\n\tif (stdout.startsWith(ERR_PREFIX)) {\n\t\t// The bootstrap contract pairs ERR_PREFIX with `{ kind, message }`. Pass\n\t\t// the raw envelope through as the error text rather than reach into the\n\t\t// JSON: any unwrapping logic we add here is paranoid with no test\n\t\t// surface (the bootstrap is the only producer and always conforms).\n\t\tthrow new Error(stdout.slice(ERR_PREFIX.length));\n\t}\n\n\t// Stdout that doesn't carry the OK prefix is unsupported; let JSON.parse\n\t// surface a SyntaxError rather than guard a defensive fallback that has\n\t// no observable behaviour.\n\tconst parsed = JSON.parse(stdout.slice(OK_PREFIX.length));\n\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\tthrow new TypeError(\"Luau config must return a table at the root\");\n\t}\n\n\treturn parsed;\n}\n\nasync function evaluateLuauWithLute(\n\tabsPath: string,\n): Promise<Result<Record<string, unknown>, LuauEvaluationError>> {\n\tconst overridePath = process.env[\"BEDROCK_LUTE_PATH\"];\n\tconst lute = overridePath !== undefined && overridePath.length > 0 ? overridePath : \"lute\";\n\tconst bootstrapDirectory = setupBootstrapDirectory(dirname(absPath));\n\ttry {\n\t\tconst stdout = await runLuteBootstrap({\n\t\t\tbin: lute,\n\t\t\tbootstrapPath: join(bootstrapDirectory, \"bootstrap.luau\"),\n\t\t\tuserBasename: basename(absPath),\n\t\t});\n\t\treturn { data: parseBootstrapOutput(stdout), success: true };\n\t} catch (err) {\n\t\tif (isEnoentError(err)) {\n\t\t\treturn {\n\t\t\t\terr: { hint: LUAU_RUNTIME_HINT, kind: \"missingRuntime\" },\n\t\t\t\tsuccess: false,\n\t\t\t};\n\t\t}\n\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn { err: { kind: \"evaluationFailed\", message }, success: false };\n\t} finally {\n\t\trmSync(bootstrapDirectory, { recursive: true });\n\t}\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { loadConfig as c12LoadConfig } from \"c12\";\nimport { existsSync, readdirSync, statSync } from \"node:fs\";\nimport { isAbsolute, join, resolve as resolvePath } from \"node:path\";\nimport process from \"node:process\";\n\nimport { createLuteLuauEvaluator } from \"../adapters/lute-luau-evaluator.ts\";\nimport type { ConfigError } from \"../core/config-error.ts\";\nimport { type Config, validateConfig } from \"../core/schema.ts\";\nimport type { LuauEvaluationError, LuauEvaluator } from \"../ports/luau-evaluator.ts\";\n\n/**\n * Options for {@link loadConfig}. Matches a subset of c12's loader options;\n * additional fields land with the issues that introduce each flow.\n */\nexport interface LoadConfigOptions {\n\t/**\n\t * Path to a specific config file to load, including its extension.\n\t * Resolved relative to `cwd` when not absolute. Loaded as-is with no\n\t * extension search; if the file does not exist at the given path,\n\t * `loadConfig` returns `fileNotFound`. When omitted, `loadConfig`\n\t * discovers `bedrock.config.{ts,js,...}` from `cwd`, falling back to\n\t * `.bedrock/bedrock.config.{ts,js,...}` when the project root has\n\t * none.\n\t */\n\treadonly configFile?: string;\n\t/**\n\t * Directory to search from. Defaults to `process.cwd()` at call time, so\n\t * each invocation sees the current working directory.\n\t */\n\treadonly cwd?: string;\n}\n\ninterface LoadConfigDeps {\n\treadonly evaluator: LuauEvaluator;\n}\n\ninterface LuauResolveResult {\n\t/* eslint-disable-next-line flawless/naming-convention -- c12 reads `_configFile` to detect that a config file was found. */\n\treadonly _configFile: string;\n\treadonly config: Record<string, unknown>;\n\treadonly configFile: string;\n\treadonly cwd: string;\n}\n\n/**\n * Internal entrypoint that lets tests inject a fake `LuauEvaluator`. The\n * public {@link loadConfig} wraps this with the real lute adapter; the rest\n * of the loader pipeline is identical.\n *\n * @param deps - Injected dependencies. Only the evaluator is configurable.\n * @param options - Same loader options accepted by {@link loadConfig}.\n * @returns Same `Result<Config, ConfigError>` shape as `loadConfig`.\n */\nexport async function loadConfigWith(\n\tdeps: LoadConfigDeps,\n\toptions?: LoadConfigOptions,\n): Promise<Result<Config, ConfigError>> {\n\tconst cwd = options?.cwd ?? process.cwd();\n\tconst explicit = resolveExplicitConfigFile(cwd, options?.configFile);\n\tif (!explicit.success) {\n\t\treturn explicit;\n\t}\n\n\tconst configFile = explicit.data ?? discoverConfigFallback(cwd);\n\n\tlet resolved: Awaited<ReturnType<typeof c12LoadConfig<Record<string, unknown>>>>;\n\ttry {\n\t\tresolved = await c12LoadConfig<Record<string, unknown>>({\n\t\t\tname: \"bedrock\",\n\t\t\tcwd,\n\t\t\tresolve: makeLuauResolver({\n\t\t\t\tcallerConfigFile: configFile,\n\t\t\t\tdefaultCwd: cwd,\n\t\t\t\tevaluator: deps.evaluator,\n\t\t\t}),\n\t\t\t...(configFile === undefined ? {} : { configFile }),\n\t\t});\n\t} catch (err) {\n\t\treturn { err: attributeLoadError(err, cwd), success: false };\n\t}\n\n\tif (resolved._configFile === undefined) {\n\t\treturn { err: { kind: \"fileNotFound\", searchedFrom: cwd }, success: false };\n\t}\n\n\treturn validateConfig(resolved.config, resolved._configFile);\n}\n\n/**\n * Discover, parse, and validate the project config.\n *\n * Looks for `bedrock.config.{ts,js,mjs,cjs,yaml,yml,json,luau}`,\n * `.bedrockrc*`, and `package.json#bedrock` starting at `options.cwd` (or the\n * current working directory). When no config sits at the project root, the\n * loader also probes `.bedrock/bedrock.config.*` so users can colocate the\n * file with their other `.bedrock/` artifacts. The project root always wins\n * on collision. Returns a fresh, mutable `Config` on every call so\n * long-running scripts see up-to-date values.\n *\n * When the exported default is a function (sync or async), `loadConfig`\n * invokes it with an empty `ConfigContext` and awaits the result before\n * validating.\n *\n * Errors return via `Result`:\n * - `fileNotFound` - no config file was discovered under the search path.\n * - `parseFailed` - a config file was found but could not be parsed (for\n * example, malformed YAML or JSON).\n * - `validationFailed` - a file was found and parsed, but its content did\n * not satisfy the runtime schema.\n * - `configFunctionFailed` - a function-form config threw or its returned\n * promise rejected while being invoked.\n *\n * @param options - Loader options.\n * @returns `Ok` with the validated `Config`, or `Err` with a `ConfigError`.\n * @example\n *\n * ```ts\n * import { loadConfig } from \"@bedrock-rbx/core\";\n *\n * return loadConfig({\n * configFile: \"bedrock.staging.config.yaml\",\n * cwd: \"/path/that/does/not/have/a/config\",\n * }).then((result) => {\n * expect(result.success).toBeFalse();\n * if (!result.success) {\n * expect(result.err.kind).toBe(\"fileNotFound\");\n * }\n * });\n * ```\n */\nexport async function loadConfig(\n\toptions?: LoadConfigOptions,\n): Promise<Result<Config, ConfigError>> {\n\treturn loadConfigWith({ evaluator: createLuteLuauEvaluator() }, options);\n}\n\nfunction resolveConfigPath(cwd: string, configFile: string): string {\n\treturn isAbsolute(configFile) ? configFile : join(cwd, configFile);\n}\n\nfunction isExistingFile(path: string): boolean {\n\ttry {\n\t\treturn statSync(path).isFile();\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Resolve a c12 `resolve` source string to an absolute path of a Luau\n * config file, if one applies. Handles two shapes:\n *\n * - Explicit `.luau` paths (`extends: \"./base.luau\"` or absolute) - always\n * ours when the file exists.\n * - The `\".\"` auto-discovery source - claim only when `bedrock.config.luau`\n * is present in the search directory.\n *\n * Returns `undefined` for every other shape so c12 falls through to its\n * built-in resolution.\n * @param source - The c12 `resolve` source string (`\".\"` for auto-discovery,\n * or a relative or absolute path for explicit `extends` references).\n * @param cwd - The directory to resolve relative paths against and to search\n * for `bedrock.config.luau` in.\n * @returns The absolute path of the matched Luau config, or `undefined` to\n * defer to c12's built-in resolution.\n */\nfunction locateLuauConfig(source: string, cwd: string): string | undefined {\n\tif (source.endsWith(\".luau\")) {\n\t\tconst candidate = isAbsolute(source) ? source : resolvePath(cwd, source);\n\t\treturn existsSync(candidate) ? candidate : undefined;\n\t}\n\n\tif (source === \".\") {\n\t\t// Defer to c12's built-in resolution when a native-format config sits\n\t\t// alongside the Luau one. This matches the documented precedence:\n\t\t// TypeScript / JavaScript / JSON / YAML beat Luau when both are\n\t\t// present. Only claim the Luau file when it's the sole candidate.\n\t\tif (\n\t\t\tNATIVE_CONFIG_EXTENSIONS.some((extension) =>\n\t\t\t\texistsSync(join(cwd, `bedrock.config.${extension}`)),\n\t\t\t)\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst candidate = join(cwd, LUAU_CONFIG_BASENAME);\n\t\treturn existsSync(candidate) ? candidate : undefined;\n\t}\n\n\treturn undefined;\n}\n\nconst NATIVE_CONFIG_EXTENSIONS = [\n\t\"ts\",\n\t\"mts\",\n\t\"cts\",\n\t\"js\",\n\t\"mjs\",\n\t\"cjs\",\n\t\"json\",\n\t\"yaml\",\n\t\"yml\",\n] as const;\n\n// Discovery probes both the project root and `.bedrock/`. Native formats\n// outrank Luau wherever they coexist; root outranks `.bedrock/`.\nconst DISCOVERY_EXTENSIONS = [...NATIVE_CONFIG_EXTENSIONS, \"luau\"] as const;\n\nconst BEDROCK_CONFIG_DIRECTORY = \".bedrock\";\n\ninterface PickLuauTargetContext {\n\treadonly callerConfigFile: string | undefined;\n\treadonly cwd: string;\n}\n\ninterface LuauResolverDeps {\n\treadonly callerConfigFile: string | undefined;\n\treadonly defaultCwd: string;\n\treadonly evaluator: LuauEvaluator;\n}\n\n/**\n * Internal-only wrapper used at the c12 boundary: makeLuauResolver maps an\n * evaluator `Err` into this throwable, which `attributeLoadError` unwraps\n * directly. This keeps the port on the `Result` contract per ADR-009 while\n * still satisfying c12's exception-based `resolve` callback.\n */\nclass EvaluatorThrow extends Error {\n\tpublic readonly configError: ConfigError;\n\n\tconstructor(configError: ConfigError) {\n\t\tsuper();\n\t\tthis.configError = configError;\n\t}\n}\n\nfunction resolveExplicitConfigFile(\n\tcwd: string,\n\tconfigFile: string | undefined,\n): Result<string | undefined, ConfigError> {\n\tif (configFile === undefined) {\n\t\treturn { data: undefined, success: true };\n\t}\n\n\tconst resolved = resolveConfigPath(cwd, configFile);\n\tif (!isExistingFile(resolved)) {\n\t\treturn { err: { kind: \"fileNotFound\", searchedFrom: cwd }, success: false };\n\t}\n\n\treturn { data: resolved, success: true };\n}\n\nfunction findConfigInDirectory(directory: string): string | undefined {\n\tfor (const extension of DISCOVERY_EXTENSIONS) {\n\t\tconst candidate = join(directory, `bedrock.config.${extension}`);\n\t\tif (isExistingFile(candidate)) {\n\t\t\treturn candidate;\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Pick the `.bedrock/bedrock.config.*` fallback only when no `bedrock.config.*`\n * exists at the project root, letting c12 run its own discovery so a root file\n * always wins. Other c12 sources (`.bedrockrc`, `package.json#bedrock`) are\n * still merged in by c12 either way; the configFile we hand back wins\n * overlapping keys per c12's standard layering precedence.\n * @param cwd - The directory to search.\n * @returns Absolute path of the `.bedrock/` candidate, or `undefined` to defer\n * to c12's own discovery on the project root.\n */\nfunction discoverConfigFallback(cwd: string): string | undefined {\n\tif (findConfigInDirectory(cwd) !== undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn findConfigInDirectory(join(cwd, BEDROCK_CONFIG_DIRECTORY));\n}\n\n/**\n * Decide which Luau file the resolver should evaluate for a given c12 source,\n * or `undefined` to defer to c12's built-in loaders.\n *\n * When the caller named a specific configFile, c12 always invokes us with\n * source === \".\" for the main config (and normalizes its own\n * options.configFile to \"bedrock.config\", so we cannot inspect it). Route\n * the explicit path through our evaluator if it points at a `.luau` file;\n * otherwise defer.\n * @param source - The c12 `resolve` source string.\n * @param context - Caller-side state: the resolved cwd to search in, and the\n * caller's configFile path (or undefined when no explicit path was supplied).\n * @returns The absolute path of the Luau file to evaluate, or `undefined`.\n */\nfunction pickLuauTarget(source: string, context: PickLuauTargetContext): string | undefined {\n\tconst { callerConfigFile, cwd } = context;\n\tif (source === \".\" && callerConfigFile !== undefined) {\n\t\treturn callerConfigFile.endsWith(\".luau\") ? callerConfigFile : undefined;\n\t}\n\n\treturn locateLuauConfig(source, cwd);\n}\n\nfunction evaluationErrorToConfigError(err: LuauEvaluationError, sourceFile: string): ConfigError {\n\tif (err.kind === \"missingRuntime\") {\n\t\treturn { hint: err.hint, kind: \"luauRuntimeMissing\", sourceFile };\n\t}\n\n\treturn { kind: \"parseFailed\", message: err.message, sourceFile };\n}\n\nfunction makeLuauResolver(\n\tdeps: LuauResolverDeps,\n): (\n\tsource: string,\n\tc12Options: { readonly cwd?: string },\n) => Promise<LuauResolveResult | undefined> {\n\treturn async (source, c12Options) => {\n\t\tconst cwd = c12Options.cwd ?? deps.defaultCwd;\n\t\tconst luauPath = pickLuauTarget(source, { callerConfigFile: deps.callerConfigFile, cwd });\n\t\tif (luauPath === undefined) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = await deps.evaluator(luauPath);\n\t\tif (!result.success) {\n\t\t\tthrow new EvaluatorThrow(evaluationErrorToConfigError(result.err, luauPath));\n\t\t}\n\n\t\treturn {\n\t\t\t_configFile: luauPath,\n\t\t\tconfig: result.data,\n\t\t\tconfigFile: luauPath,\n\t\t\tcwd,\n\t\t};\n\t};\n}\n\nconst LUAU_CONFIG_BASENAME = \"bedrock.config.luau\";\n\n// `.luau` is intentionally absent: Luau errors travel through the evaluator\n// adapter's ERR sentinel, not JS stack frames, so the file path is attributed\n// by `discoverConfigFile` rather than scraped from the throw site.\nconst CONFIG_FILE_IN_FRAME = /[^\\s():\"']*bedrock\\.config\\.(?:ts|js|mjs|cjs|yaml|yml|json)/;\n\nfunction extractConfigFileFromStack(err: unknown): string | undefined {\n\tif (!(err instanceof Error) || err.stack === undefined) {\n\t\treturn undefined;\n\t}\n\n\tfor (const rawLine of err.stack.split(\"\\n\")) {\n\t\tconst line = rawLine.trimStart();\n\t\tif (!line.startsWith(\"at \")) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst match = CONFIG_FILE_IN_FRAME.exec(line);\n\t\tif (match !== null) {\n\t\t\treturn match[0];\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nfunction findConfigEntry(directory: string): string | undefined {\n\tlet entries: ReadonlyArray<string>;\n\ttry {\n\t\tentries = readdirSync(directory);\n\t} catch {\n\t\treturn undefined;\n\t}\n\n\tconst match = entries.toSorted().find((entry) => entry.startsWith(\"bedrock.config.\"));\n\treturn match === undefined ? undefined : join(directory, match);\n}\n\nfunction discoverConfigFile(cwd: string): string | undefined {\n\treturn findConfigEntry(cwd) ?? findConfigEntry(join(cwd, BEDROCK_CONFIG_DIRECTORY));\n}\n\nfunction attributeLoadError(err: unknown, cwd: string): ConfigError {\n\tif (err instanceof EvaluatorThrow) {\n\t\treturn err.configError;\n\t}\n\n\tconst message = err instanceof Error ? err.message : String(err);\n\tconst frameFile = extractConfigFileFromStack(err);\n\tif (frameFile !== undefined) {\n\t\treturn { kind: \"configFunctionFailed\", message, sourceFile: frameFile };\n\t}\n\n\treturn { kind: \"parseFailed\", message, sourceFile: discoverConfigFile(cwd) ?? cwd };\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { readFile as nodeReadFile } from \"node:fs/promises\";\nimport process from \"node:process\";\n\nimport { createDefaultProgressAdapter } from \"../adapters/clack-progress-adapter.ts\";\nimport type { GistFetch } from \"../adapters/gist-state-adapter.ts\";\nimport { createNoOpProgressAdapter } from \"../adapters/no-op-progress-adapter.ts\";\nimport { assertAllReconcilable } from \"../core/assert-all-reconcilable.ts\";\nimport type { ConfigError } from \"../core/config-error.ts\";\nimport { diff } from \"../core/diff.ts\";\nimport { flattenConfig } from \"../core/flatten.ts\";\nimport { resolveStateConfig, type StateNotConfiguredError } from \"../core/resolve-state-config.ts\";\nimport type { ResourceCurrentState } from \"../core/resources.ts\";\nimport type { Config, ResolvedConfig } from \"../core/schema.ts\";\nimport {\n\ttype IncompletePassEntryError,\n\ttype IncompletePlaceEntryError,\n\ttype IncompleteUniverseEntryError,\n\tselectEnvironment,\n\ttype UnknownEnvironmentError,\n} from \"../core/select-environment.ts\";\nimport type { BedrockState, StateError } from \"../core/state.ts\";\nimport type { ProgressPort } from \"../ports/progress-port.ts\";\nimport type { DriverRegistry } from \"../ports/resource-driver.ts\";\nimport type { StatePort } from \"../ports/state-port.ts\";\nimport { type AggregateApplyError, applyOps } from \"./apply-ops.ts\";\nimport { buildDefaultRegistry, type RegistryConfigError } from \"./build-default-registry.ts\";\nimport { buildDesired, type BuildDesiredError } from \"./build-desired.ts\";\nimport {\n\tbuildStatePort,\n\ttype MissingCredentialError,\n\ttype UnsupportedBackendError,\n} from \"./build-state-port.ts\";\nimport { loadConfig as defaultLoadConfig, type LoadConfigOptions } from \"./load-config.ts\";\n\n/**\n * Inputs for `deploy`. Every field except `environment` is optional;\n * omitted dependencies are default-constructed from the project config\n * and the environment variables `BEDROCK_GITHUB_TOKEN` and `BEDROCK_API_KEY`.\n */\nexport interface DeployOptions {\n\t/** Pre-loaded, optionally-mutated project config. Omit to call `loadConfig()` automatically. */\n\treadonly config?: Config;\n\t/** Environment name; threaded into `StatePort.read` and the persisted snapshot. */\n\treadonly environment: string;\n\t/** `fetch` override plumbed into the default-constructed gist adapter when `statePort` is omitted. */\n\treadonly fetch?: GistFetch;\n\t/** Reads an environment variable; defaults to `(name) => process.env[name]`. */\n\treadonly getEnv?: (name: string) => string | undefined;\n\t/** Loader invoked when `config` is omitted; defaults to `loadConfig` from this package. */\n\treadonly loadConfig?: (options?: LoadConfigOptions) => Promise<Result<Config, ConfigError>>;\n\t/**\n\t * Optional sink for per-resource and aggregate progress events. When\n\t * supplied, `applyOps` emits one started/terminal pair per non-noop op\n\t * (plus per-noop and summary events), and `deploy` emits `stateWritten`\n\t * after a successful state-write. Omit to run silently.\n\t */\n\treadonly progress?: ProgressPort;\n\t/** Reads file bytes for resources that have file-backed inputs. Defaults to `node:fs/promises.readFile`. */\n\treadonly readFile?: (path: string) => Promise<Uint8Array>;\n\t/** Per-kind driver table consulted for create / update dispatch. Default-constructed from `BEDROCK_API_KEY` when omitted. */\n\treadonly registry?: DriverRegistry;\n\t/** Backend used to read the prior snapshot and persist the new one. Default-constructed from `config.state` and `BEDROCK_GITHUB_TOKEN` when omitted. */\n\treadonly statePort?: StatePort;\n}\n\n/**\n * Failure surfaced by `deploy`. Stage-tagged so callers can branch on\n * `kind` to distinguish reconciliation failures (`stateReadFailed`,\n * `applyFailed`, ...) from default-construction failures\n * (`configLoadFailed`, `stateNotConfigured`, `unknownEnvironment`,\n * `incompletePlaceEntry`, `incompleteUniverseEntry`, `missingCredential`,\n * `unsupportedBackend`, `registryConfigMissing`).\n */\nexport type DeployError =\n\t| IncompletePassEntryError\n\t| IncompletePlaceEntryError\n\t| IncompleteUniverseEntryError\n\t| MissingCredentialError\n\t| RegistryConfigError\n\t| StateNotConfiguredError\n\t| UnknownEnvironmentError\n\t| UnsupportedBackendError\n\t| { readonly cause: AggregateApplyError; readonly kind: \"applyFailed\" }\n\t| { readonly cause: BuildDesiredError; readonly kind: \"buildDesiredFailed\" }\n\t| { readonly cause: ConfigError; readonly kind: \"configLoadFailed\" }\n\t| { readonly cause: StateError; readonly kind: \"stateReadFailed\" }\n\t| {\n\t\t\treadonly cause: StateError;\n\t\t\treadonly kind: \"stateWriteFailed\";\n\t\t\treadonly unsavedState: BedrockState;\n\t };\n\ninterface SnapshotInputs {\n\treadonly applied: Result<ReadonlyArray<ResourceCurrentState>, AggregateApplyError>;\n\treadonly environment: string;\n\treadonly priorResources: ReadonlyArray<ResourceCurrentState>;\n}\n\ninterface FinalizeInputs {\n\treadonly applied: Result<ReadonlyArray<ResourceCurrentState>, AggregateApplyError>;\n\treadonly merged: BedrockState;\n\treadonly written: Result<void, StateError>;\n}\n\ninterface ResolvedDepsBase {\n\treadonly config: ResolvedConfig;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\treadonly registry: DriverRegistry;\n\treadonly statePort: StatePort;\n}\n\ninterface ResolvedDeps extends ResolvedDepsBase {\n\treadonly progress: ProgressPort;\n}\n\ninterface PickRegistryInputs {\n\treadonly config: ResolvedConfig;\n\treadonly options: DeployOptions;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n}\n\ninterface EmitTerminalEventInputs {\n\treadonly environment: string;\n\treadonly progress: ProgressPort;\n\treadonly result: Result<BedrockState, DeployError>;\n}\n\n/**\n * Decide whether `BEDROCK_CLI` should select the clack-backed default\n * progress adapter. Exported for direct unit coverage of the boundary\n * (`undefined` and empty string both flip to no-op; any non-empty value\n * picks clack).\n *\n * @param value - Raw `BEDROCK_CLI` value as returned by `getEnv`.\n * @returns `true` if the clack adapter should be the default.\n */\nexport function isCliEnvironmentFlagSet(value: string | undefined): boolean {\n\treturn value !== undefined && value !== \"\";\n}\n\n/**\n * Run a full reconcile end-to-end. Default-constructs missing deps from\n * the project config and the environment variables `BEDROCK_GITHUB_TOKEN`\n * and `BEDROCK_API_KEY`; emits a terminal `deploySuccess` or `deployFailure`\n * event through the resolved `progress` port. When `progress` is omitted,\n * the default port comes from `BEDROCK_CLI`: a non-empty value selects the\n * clack-backed adapter, any other reading selects the no-op adapter. No\n * environment lookups happen when `statePort`, `registry`, `config`, and\n * `progress` are all supplied explicitly.\n *\n * @param options - Target environment plus optional overrides.\n * @returns The persisted `BedrockState` on success, or a stage-tagged\n * `DeployError` on failure.\n * @example\n *\n * ```ts\n * import { deploy } from \"@bedrock-rbx/core\";\n *\n * return deploy({ environment: \"production\" }).then((result) => {\n * expect(result.success).toBeFalse();\n * if (!result.success) {\n * expect([\"configLoadFailed\", \"stateNotConfigured\"]).toContain(result.err.kind);\n * }\n * });\n * ```\n *\n * @example\n *\n * ```ts\n * import { deploy, type BedrockState, type DriverRegistry, type StatePort } from \"@bedrock-rbx/core\";\n *\n * const store = new Map<string, BedrockState>();\n * const statePort: StatePort = {\n * async read(environment) {\n * return { data: store.get(environment), success: true };\n * },\n * async write(state) {\n * store.set(state.environment, state);\n * return { data: undefined, success: true };\n * },\n * };\n * const registry: DriverRegistry = {\n * developerProduct: {\n * create: async () => { throw new Error(\"unreachable: empty config\"); },\n * },\n * gamePass: { create: async () => { throw new Error(\"unreachable: empty config\"); } },\n * place: { create: async () => { throw new Error(\"unreachable: empty config\"); } },\n * universe: { create: async () => { throw new Error(\"unreachable: empty config\"); } },\n * };\n *\n * return deploy({\n * config: {\n * environments: { production: {} },\n * state: { backend: \"gist\", gistId: \"abc\" },\n * passes: {},\n * },\n * environment: \"production\",\n * registry,\n * statePort,\n * }).then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.environment).toBe(\"production\");\n * expect(result.data.resources).toBeEmpty();\n * }\n * });\n * ```\n */\nexport async function deploy(options: DeployOptions): Promise<Result<BedrockState, DeployError>> {\n\tif (options.progress !== undefined) {\n\t\treturn runAndEmit(options, options.progress);\n\t}\n\n\tif (!isCliEnvironmentFlagSet(getEnvironmentOf(options)(\"BEDROCK_CLI\"))) {\n\t\treturn runAndEmit(options, createNoOpProgressAdapter());\n\t}\n\n\treturn runWithDeferredClackProgress(options);\n}\n\nfunction readProcessEnvironment(name: string): string | undefined {\n\treturn process.env[name];\n}\n\nfunction getEnvironmentOf(options: DeployOptions): (name: string) => string | undefined {\n\treturn options.getEnv ?? readProcessEnvironment;\n}\n\nasync function pickConfig(options: DeployOptions): Promise<Result<Config, DeployError>> {\n\tif (options.config !== undefined) {\n\t\treturn { data: options.config, success: true };\n\t}\n\n\tconst loader = options.loadConfig ?? defaultLoadConfig;\n\tconst loaded = await loader();\n\tif (!loaded.success) {\n\t\treturn { err: { cause: loaded.err, kind: \"configLoadFailed\" }, success: false };\n\t}\n\n\treturn { data: loaded.data, success: true };\n}\n\nfunction pickStatePort(\n\toptions: DeployOptions,\n\tconfig: ResolvedConfig,\n): Result<StatePort, DeployError> {\n\tif (options.statePort !== undefined) {\n\t\treturn { data: options.statePort, success: true };\n\t}\n\n\tconst stateConfig = resolveStateConfig(config, options.environment);\n\tif (!stateConfig.success) {\n\t\treturn { err: stateConfig.err, success: false };\n\t}\n\n\treturn buildStatePort({\n\t\tfetch: options.fetch,\n\t\tgetEnv: getEnvironmentOf(options),\n\t\tstateConfig: stateConfig.data,\n\t});\n}\n\nfunction pickRegistry(inputs: PickRegistryInputs): Result<DriverRegistry, DeployError> {\n\tif (inputs.options.registry !== undefined) {\n\t\treturn { data: inputs.options.registry, success: true };\n\t}\n\n\treturn buildDefaultRegistry({\n\t\tconfig: inputs.config,\n\t\tgetEnv: getEnvironmentOf(inputs.options),\n\t\treadFile: inputs.readFile,\n\t});\n}\n\nasync function resolveDeps(options: DeployOptions): Promise<Result<ResolvedDepsBase, DeployError>> {\n\tconst config = await pickConfig(options);\n\tif (!config.success) {\n\t\treturn config;\n\t}\n\n\tconst selected = selectEnvironment(config.data, options.environment);\n\tif (!selected.success) {\n\t\treturn { err: selected.err, success: false };\n\t}\n\n\tconst effective = selected.data;\n\tconst readFile = options.readFile ?? nodeReadFile;\n\tconst statePort = pickStatePort(options, effective);\n\tif (!statePort.success) {\n\t\treturn statePort;\n\t}\n\n\tconst registry = pickRegistry({ config: effective, options, readFile });\n\tif (!registry.success) {\n\t\treturn registry;\n\t}\n\n\treturn {\n\t\tdata: { config: effective, readFile, registry: registry.data, statePort: statePort.data },\n\t\tsuccess: true,\n\t};\n}\n\nfunction mergeResources(\n\tpre: ReadonlyArray<ResourceCurrentState>,\n\tapplied: ReadonlyArray<ResourceCurrentState>,\n): ReadonlyArray<ResourceCurrentState> {\n\tconst byKey = new Map<string, ResourceCurrentState>();\n\tfor (const resource of pre) {\n\t\tbyKey.set(`${resource.kind}:${resource.key}`, resource);\n\t}\n\n\tfor (const resource of applied) {\n\t\tbyKey.set(`${resource.kind}:${resource.key}`, resource);\n\t}\n\n\treturn [...byKey.values()];\n}\n\nfunction buildSnapshot(inputs: SnapshotInputs): BedrockState {\n\tconst appliedResources = inputs.applied.success\n\t\t? inputs.applied.data\n\t\t: inputs.applied.err.applied;\n\treturn {\n\t\tenvironment: inputs.environment,\n\t\tresources: mergeResources(inputs.priorResources, appliedResources),\n\t\tversion: 1,\n\t};\n}\n\nfunction finalize(inputs: FinalizeInputs): Result<BedrockState, DeployError> {\n\t// Check write before apply: only the write carries `unsavedState`.\n\tif (!inputs.written.success) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tcause: inputs.written.err,\n\t\t\t\tkind: \"stateWriteFailed\",\n\t\t\t\tunsavedState: inputs.merged,\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\tif (!inputs.applied.success) {\n\t\treturn { err: { cause: inputs.applied.err, kind: \"applyFailed\" }, success: false };\n\t}\n\n\treturn { data: inputs.merged, success: true };\n}\n\nasync function runReconcile(\n\tenvironment: string,\n\tdeps: ResolvedDeps,\n): Promise<Result<BedrockState, DeployError>> {\n\tconst desired = await buildDesired(flattenConfig(deps.config), deps.readFile);\n\tif (!desired.success) {\n\t\treturn { err: { cause: desired.err, kind: \"buildDesiredFailed\" }, success: false };\n\t}\n\n\tconst prior = await deps.statePort.read(environment);\n\tif (!prior.success) {\n\t\treturn { err: { cause: prior.err, kind: \"stateReadFailed\" }, success: false };\n\t}\n\n\tconst priorResources = prior.data?.resources ?? [];\n\tconst validated = assertAllReconcilable(desired.data, priorResources);\n\tif (!validated.success) {\n\t\treturn { err: { cause: validated.err, kind: \"buildDesiredFailed\" }, success: false };\n\t}\n\n\tconst ops = diff(desired.data, priorResources);\n\tconst applied = await applyOps(ops, deps.registry, { environment, progress: deps.progress });\n\tconst merged = buildSnapshot({ applied, environment, priorResources });\n\n\tconst written = await deps.statePort.write(merged);\n\tif (written.success) {\n\t\tdeps.progress.emit({ environment, kind: \"stateWritten\" });\n\t}\n\n\treturn finalize({ applied, merged, written });\n}\n\nasync function runDeploy(\n\toptions: DeployOptions,\n\tprogress: ProgressPort,\n): Promise<Result<BedrockState, DeployError>> {\n\tconst resolved = await resolveDeps(options);\n\tif (!resolved.success) {\n\t\treturn resolved;\n\t}\n\n\treturn runReconcile(options.environment, { ...resolved.data, progress });\n}\n\nfunction emitTerminalEvent(inputs: EmitTerminalEventInputs): void {\n\tconst { environment, progress, result } = inputs;\n\tif (result.success) {\n\t\tprogress.emit({\n\t\t\tenvironment,\n\t\t\tkind: \"deploySuccess\",\n\t\t\tresourceCount: result.data.resources.length,\n\t\t});\n\t\treturn;\n\t}\n\n\tprogress.emit({ environment, error: result.err, kind: \"deployFailure\" });\n}\n\nasync function runAndEmit(\n\toptions: DeployOptions,\n\tprogress: ProgressPort,\n): Promise<Result<BedrockState, DeployError>> {\n\tconst result = await runDeploy(options, progress);\n\temitTerminalEvent({ environment: options.environment, progress, result });\n\treturn result;\n}\n\nasync function runWithDeferredClackProgress(\n\toptions: DeployOptions,\n): Promise<Result<BedrockState, DeployError>> {\n\tconst resolved = await resolveDeps(options);\n\tconst labelConfig = resolved.success ? resolved.data.config : options.config;\n\tconst progress = createDefaultProgressAdapter(labelConfig);\n\n\tif (!resolved.success) {\n\t\temitTerminalEvent({ environment: options.environment, progress, result: resolved });\n\t\treturn resolved;\n\t}\n\n\tconst result = await runReconcile(options.environment, { ...resolved.data, progress });\n\temitTerminalEvent({ environment: options.environment, progress, result });\n\treturn result;\n}\n","import { asResourceKey, asRobloxAssetId } from \"../../types/ids.ts\";\nimport type { ResourceKey, Sha256Hex } from \"../../types/ids.ts\";\nimport {\n\ttype ResourceCurrentState,\n\tUNIVERSE_SINGLETON_KEY,\n\ttype UniverseOutputs,\n} from \"../resources.ts\";\nimport type { ResolvedUniverseEntry } from \"../schema.ts\";\nimport type { BedrockState } from \"../state.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\nimport type { PassFoldEntry } from \"./fold-passes.ts\";\nimport type { PlaceFoldEntry } from \"./fold-places.ts\";\nimport type { ProductFoldEntry } from \"./fold-products.ts\";\n\n/**\n * Inputs to {@link buildState}. Bundled into one object because the\n * call site already groups these per-environment values together (the\n * shell threads them in lockstep) and named fields read better than a\n * three-positional-argument signature.\n */\ninterface BuildStateInputs {\n\t/** Environment name; written verbatim onto the state. */\n\treadonly environment: string;\n\t/** Per-kind fold results for this environment. */\n\treadonly folded: EnvironmentFoldResult;\n\t/**\n\t * Per-pass-key locale-keyed icon hashes recomputed from disk by the\n\t * shell. Keys absent from the map fall back to `mantleIconFileHashes`\n\t * from the matching fold entry.\n\t */\n\treadonly passIconHashesByKey: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>;\n\t/**\n\t * Per-product-key locale-keyed icon hashes recomputed from disk by the\n\t * shell. Keys absent from the map fall back to `mantleIconFileHashes`\n\t * from the matching fold entry; products without any icon partner emit\n\t * resources without `iconFileHashes`.\n\t */\n\treadonly productIconHashesByKey: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>;\n}\n\ninterface UniverseResourceInputs {\n\treadonly entry: ResolvedUniverseEntry;\n\treadonly outputs: UniverseOutputs;\n}\n\n/**\n * Compose one environment's folded data into the on-disk `BedrockState`\n * snapshot the migrator's caller writes per environment.\n *\n * The resulting state carries one `kind: \"universe\"` resource (when an\n * experience folded), followed by one `kind: \"place\"` resource per\n * matched place pair, then one `kind: \"gamePass\"` resource per folded\n * pass entry, then one `kind: \"developerProduct\"` resource per folded\n * product entry, in declaration order. Each resource's declared fields\n * mirror its fold output; pass and product resources receive their\n * `iconFileHashes` from the matching `*IconHashesByKey` map (computed by\n * the shell from the icon file's bytes) and fall back to the\n * Mantle-recorded hashes when the map omits the key. Product resources\n * without an icon partner omit `icon` and `iconFileHashes` entirely. The\n * `outputs` field carries the Mantle-recorded identifiers (universe\n * `rootPlaceId`, place `versionNumber`, pass `assetId` and `iconAssetIds`,\n * product `productId` and optional `iconImageAssetId`).\n *\n * @param inputs - Folded data plus recomputed hashes for this environment.\n * @returns A `BedrockState` populated with one resource per folded kind.\n */\nexport function buildState(inputs: BuildStateInputs): BedrockState {\n\treturn {\n\t\tenvironment: inputs.environment,\n\t\tresources: composeResources(inputs),\n\t\tversion: 1,\n\t};\n}\n\nfunction universeResource(inputs: UniverseResourceInputs): ResourceCurrentState<\"universe\"> {\n\tconst { entry, outputs } = inputs;\n\treturn {\n\t\tkey: UNIVERSE_SINGLETON_KEY,\n\t\tconsoleEnabled: entry.consoleEnabled,\n\t\tdesktopEnabled: entry.desktopEnabled,\n\t\tdisplayName: entry.displayName,\n\t\tkind: \"universe\",\n\t\tmobileEnabled: entry.mobileEnabled,\n\t\toutputs,\n\t\ttabletEnabled: entry.tabletEnabled,\n\t\tuniverseId: asRobloxAssetId(entry.universeId),\n\t\tvoiceChatEnabled: entry.voiceChatEnabled,\n\t\tvrEnabled: entry.vrEnabled,\n\t};\n}\n\nfunction placeResource(key: string, fold: PlaceFoldEntry): ResourceCurrentState<\"place\"> {\n\treturn {\n\t\tkey: asResourceKey(key),\n\t\tdescription: fold.entry.description,\n\t\tdisplayName: fold.entry.displayName,\n\t\tfileHash: fold.fileHash,\n\t\tfilePath: fold.entry.filePath,\n\t\tkind: \"place\",\n\t\toutputs: fold.outputs,\n\t\tplaceId: asRobloxAssetId(fold.placeId),\n\t\tserverSize: fold.entry.serverSize,\n\t};\n}\n\nfunction passResource(\n\tfold: PassFoldEntry,\n\ticonFileHashes: Record<\"en-us\", Sha256Hex>,\n): ResourceCurrentState<\"gamePass\"> {\n\treturn {\n\t\tkey: fold.key,\n\t\tname: fold.entry.name,\n\t\tdescription: fold.entry.description,\n\t\ticon: fold.entry.icon,\n\t\ticonFileHashes,\n\t\tkind: \"gamePass\",\n\t\toutputs: fold.outputs,\n\t\tprice: fold.entry.price,\n\t};\n}\n\nfunction productResource(\n\tfold: ProductFoldEntry,\n\tproductIconHashesByKey: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>,\n): ResourceCurrentState<\"developerProduct\"> {\n\tconst base: ResourceCurrentState<\"developerProduct\"> = {\n\t\tkey: fold.key,\n\t\tname: fold.entry.name,\n\t\tdescription: fold.entry.description,\n\t\tisRegionalPricingEnabled: undefined,\n\t\tkind: \"developerProduct\",\n\t\toutputs: fold.outputs,\n\t\tprice: fold.entry.price,\n\t\tstorePageEnabled: undefined,\n\t};\n\n\tif (fold.entry.icon === undefined || fold.mantleIconFileHashes === undefined) {\n\t\treturn base;\n\t}\n\n\treturn {\n\t\t...base,\n\t\ticon: fold.entry.icon,\n\t\ticonFileHashes: productIconHashesByKey.get(fold.key) ?? fold.mantleIconFileHashes,\n\t};\n}\n\nfunction composeResources(inputs: BuildStateInputs): ReadonlyArray<ResourceCurrentState> {\n\tconst { folded, passIconHashesByKey, productIconHashesByKey } = inputs;\n\tconst universeResources: ReadonlyArray<ResourceCurrentState> =\n\t\tfolded.universe === undefined\n\t\t\t? []\n\t\t\t: [\n\t\t\t\t\tuniverseResource({\n\t\t\t\t\t\tentry: folded.universe.entry,\n\t\t\t\t\t\toutputs: folded.universe.outputs,\n\t\t\t\t\t}),\n\t\t\t\t];\n\n\tconst placeResources: ReadonlyArray<ResourceCurrentState> = [...folded.places.entries()].map(\n\t\t([key, entry]) => placeResource(key, entry),\n\t);\n\n\tconst passResources: ReadonlyArray<ResourceCurrentState> = folded.passes.map((entry) => {\n\t\treturn passResource(\n\t\t\tentry,\n\t\t\tpassIconHashesByKey.get(entry.key) ?? entry.mantleIconFileHashes,\n\t\t);\n\t});\n\n\tconst productResources: ReadonlyArray<ResourceCurrentState> = folded.products.map((entry) => {\n\t\treturn productResource(entry, productIconHashesByKey);\n\t});\n\n\treturn [...universeResources, ...placeResources, ...passResources, ...productResources];\n}\n","/**\n * Result of inspecting a display name for a Mantle-style `[LABEL]` prefix.\n * `label` is `undefined` when the input does not match the bracketed-prefix\n * shape Mantle emits; in that case `body` echoes the input unchanged.\n */\ninterface ExtractedDisplayName {\n\t/** Display name with the bracketed prefix and trailing whitespace stripped, or the input verbatim when no prefix matched. */\n\treadonly body: string;\n\t/** Captured label from the bracketed prefix, or `undefined` when the input did not match. */\n\treadonly label: string | undefined;\n}\n\n/**\n * Lift the leading bracketed prefix off a display name produced by Mantle's\n * environment-name stamping. A match requires `[LABEL] body` with a\n * non-empty label, mandatory whitespace after the closing bracket, and a\n * non-empty body. When the input does not match, `label` is `undefined` and\n * `body` is the input verbatim, so callers can round-trip arbitrary\n * display-name shapes without losing data.\n *\n * @param value - Raw display name to inspect.\n * @returns The captured label (or `undefined`) and the unprefixed body.\n */\nexport function extractDisplayNamePrefix(value: string): ExtractedDisplayName {\n\tconst passthrough: ExtractedDisplayName = { body: value, label: undefined };\n\tif (!value.startsWith(\"[\")) {\n\t\treturn passthrough;\n\t}\n\n\tconst closeIndex = value.indexOf(\"]\");\n\tif (closeIndex < 2) {\n\t\treturn passthrough;\n\t}\n\n\tconst afterBracket = value.slice(closeIndex + 1);\n\tconst body = afterBracket.trimStart();\n\tif (body === afterBracket) {\n\t\treturn passthrough;\n\t}\n\n\tif (body === \"\") {\n\t\treturn passthrough;\n\t}\n\n\treturn { body, label: value.slice(1, closeIndex) };\n}\n","import { extractDisplayNamePrefix } from \"./extract-display-name-prefix.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\n\n/**\n * Compute the environment-label candidate that round-trips a Mantle-style\n * `[LABEL]` prefix through bedrock's deploy-time prefix system. Inspects\n * every present `displayName` on the env's universe and place folds; if all\n * extractions agree on the same captured label the function returns it\n * lowercased. Any disagreement (mixed prefixes, prefixed alongside\n * unprefixed, or all unprefixed) yields `undefined`, signalling that the\n * caller should keep raw per-environment displayName overrides instead of\n * promoting a label.\n *\n * @param fold - Per-environment fold result to inspect.\n * @returns Lowercased label string when every displayName shares one\n * bracketed prefix; `undefined` otherwise.\n */\nexport function computeEnvironmentLabel(fold: EnvironmentFoldResult): string | undefined {\n\tconst displayNames = [\n\t\tfold.universe?.entry.displayName,\n\t\t...[...fold.places.values()].map(({ entry }) => entry.displayName),\n\t].filter((displayName): displayName is string => displayName !== undefined);\n\tconst labels = new Set(\n\t\tdisplayNames.map((displayName) => extractDisplayNamePrefix(displayName).label),\n\t);\n\tif (labels.size !== 1) {\n\t\treturn undefined;\n\t}\n\n\tconst [label] = labels;\n\treturn label?.toLowerCase();\n}\n","import type { EnvironmentFoldResult } from \"./fold-environment.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\n\nconst RESOURCE_MISSING_RULE = \"factorize-environments/resource-missing-from-env\";\n\n/**\n * Inputs to {@link collectMissingResourceWarnings}.\n */\ninterface MissingResourceInputs {\n\t/** Per-environment fold results, keyed by environment name. */\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\t/** The chosen primary environment, used as the symmetry baseline. */\n\treadonly primary: { readonly fold: EnvironmentFoldResult; readonly name: string };\n}\n\ninterface AsymmetryContext {\n\treadonly environmentName: string;\n\treadonly fold: EnvironmentFoldResult;\n\treadonly primary: EnvironmentFoldResult;\n}\n\ninterface MissingResourcePaths {\n\treadonly bedrockSegment: string;\n\treadonly environmentName: string;\n\treadonly mantleSegment: string;\n}\n\ninterface KeyedAsymmetrySpec {\n\treadonly bedrockPrefix: string;\n\treadonly mantlePrefix: string;\n\treadonly readKeys: (fold: EnvironmentFoldResult) => ReadonlyArray<string>;\n}\n\nconst KEYED_ASYMMETRY_SPECS: ReadonlyArray<KeyedAsymmetrySpec> = [\n\t{\n\t\tbedrockPrefix: \"places\",\n\t\tmantlePrefix: \"place_\",\n\t\treadKeys: (fold) => [...fold.places.keys()],\n\t},\n\t{\n\t\tbedrockPrefix: \"passes\",\n\t\tmantlePrefix: \"pass_\",\n\t\treadKeys: (fold) => fold.passes.map(({ key }) => key),\n\t},\n\t{\n\t\tbedrockPrefix: \"products\",\n\t\tmantlePrefix: \"product_\",\n\t\treadKeys: (fold) => fold.products.map(({ key }) => key),\n\t},\n];\n\n/**\n * Walk every environment fold and emit one `interpretive` warning per\n * resource the environment lacks relative to the chosen primary, and per\n * resource the environment introduces that the primary lacks. The walk\n * covers the universe singleton plus every keyed kind in\n * {@link KEYED_ASYMMETRY_SPECS}.\n *\n * @param inputs - Per-environment folds and the resolved primary.\n * @returns Flat list of resource-asymmetry warnings.\n */\nexport function collectMissingResourceWarnings(\n\tinputs: MissingResourceInputs,\n): ReadonlyArray<MigrationWarning> {\n\tconst primaryFold = inputs.primary.fold;\n\treturn [...inputs.folds.entries()].flatMap(([name, fold]): ReadonlyArray<MigrationWarning> => {\n\t\tconst context: AsymmetryContext = { environmentName: name, fold, primary: primaryFold };\n\t\treturn [\n\t\t\t...universeAsymmetryWarnings(context),\n\t\t\t...KEYED_ASYMMETRY_SPECS.flatMap((spec) => keyedAsymmetryWarnings(context, spec)),\n\t\t];\n\t});\n}\n\nfunction asymmetricKeys(\n\tenvironmentKeys: ReadonlyArray<string>,\n\tprimaryKeys: ReadonlyArray<string>,\n): ReadonlyArray<string> {\n\tconst environmentSet = new Set(environmentKeys);\n\tconst primarySet = new Set(primaryKeys);\n\tconst onlyInEnvironment = environmentKeys.filter((key) => !primarySet.has(key));\n\tconst onlyInPrimary = primaryKeys.filter((key) => !environmentSet.has(key));\n\treturn [...onlyInEnvironment, ...onlyInPrimary];\n}\n\nfunction missingResourceWarning(paths: MissingResourcePaths): MigrationWarning {\n\treturn {\n\t\tbedrockPath: `environments.${paths.environmentName}.${paths.bedrockSegment}`,\n\t\tkind: \"interpretive\",\n\t\tmantlePath: `${paths.environmentName}.${paths.mantleSegment}`,\n\t\trule: RESOURCE_MISSING_RULE,\n\t};\n}\n\nfunction keyedAsymmetryWarnings(\n\tcontext: AsymmetryContext,\n\tspec: KeyedAsymmetrySpec,\n): ReadonlyArray<MigrationWarning> {\n\treturn asymmetricKeys(spec.readKeys(context.fold), spec.readKeys(context.primary)).map(\n\t\t(key) => {\n\t\t\treturn missingResourceWarning({\n\t\t\t\tbedrockSegment: `${spec.bedrockPrefix}.${key}`,\n\t\t\t\tenvironmentName: context.environmentName,\n\t\t\t\tmantleSegment: `${spec.mantlePrefix}${key}`,\n\t\t\t});\n\t\t},\n\t);\n}\n\nfunction universeAsymmetryWarnings(context: AsymmetryContext): ReadonlyArray<MigrationWarning> {\n\tconst hasEnvironmentUniverse = context.fold.universe !== undefined;\n\tconst hasPrimaryUniverse = context.primary.universe !== undefined;\n\tif (hasEnvironmentUniverse === hasPrimaryUniverse) {\n\t\treturn [];\n\t}\n\n\treturn [\n\t\tmissingResourceWarning({\n\t\t\tbedrockSegment: \"universe\",\n\t\t\tenvironmentName: context.environmentName,\n\t\t\tmantleSegment: \"experience_singleton\",\n\t\t}),\n\t];\n}\n","import type { EnvironmentEntry, PlaceEntry } from \"../schema.ts\";\nimport { extractDisplayNamePrefix } from \"./extract-display-name-prefix.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\nimport type { PlaceFoldEntry } from \"./fold-places.ts\";\n\n/**\n * Inputs to {@link buildRootPlaces}.\n */\ninterface BuildRootPlacesInputs {\n\t/** Per-environment fold results, keyed by environment name. */\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\t/**\n\t * Per-environment label map. An environment whose label is `undefined`\n\t * contributes raw `displayName` values to consensus; otherwise the value\n\t * is stripped via {@link extractDisplayNamePrefix} before comparison.\n\t */\n\treadonly labels: ReadonlyMap<string, string | undefined>;\n\t/** The chosen primary environment's fold; supplies `filePath` for every place entry on root. */\n\treadonly primaryFold: EnvironmentFoldResult;\n}\n\n/**\n * Inputs to {@link buildPlacesOverlay}.\n */\ninterface BuildPlacesOverlayInputs {\n\t/** The per-environment fold whose places are being overlaid. */\n\treadonly fold: EnvironmentFoldResult;\n\t/** The environment's label (or `undefined`); enables prefix-stripping on the overlay's `displayName`. */\n\treadonly label: string | undefined;\n\t/** The already-built root `places` block; matching field values suppress overlay entries. */\n\treadonly rootPlaces: Record<string, PlaceEntry> | undefined;\n}\n\ntype PlaceOverlayEntry = NonNullable<EnvironmentEntry[\"places\"]>[string];\n\ntype OptionalPlaceField = \"description\" | \"serverSize\";\n\ninterface LabeledFold {\n\treadonly fold: EnvironmentFoldResult;\n\treadonly label: string | undefined;\n}\n\ninterface ConsensusInputs<F extends OptionalPlaceField> {\n\treadonly field: F;\n\treadonly folds: ReadonlyArray<LabeledFold>;\n\treadonly placeKey: string;\n}\n\ninterface DisplayNameConsensusInputs {\n\treadonly folds: ReadonlyArray<LabeledFold>;\n\treadonly placeKey: string;\n}\n\ninterface BuildPlaceOverlayEntryInputs {\n\treadonly fold: PlaceFoldEntry;\n\treadonly label: string | undefined;\n\treadonly rootEntry: PlaceEntry | undefined;\n}\n\n/**\n * Project the per-environment place folds into bedrock's root `places`\n * block, giving each optional metadata field (`description`, `displayName`,\n * `serverSize`) a value only when every environment that owns the place\n * key agrees. `displayName` consensus runs on the unprefixed body when an\n * environment carries a label, so a Mantle-stamped `[ENV] Foo` in one\n * environment matches a plain `Foo` in another. Divergent or partially-set\n * fields are omitted from root and surface on each environment's overlay\n * via {@link buildPlacesOverlay}.\n *\n * @param inputs - Per-environment folds, label map, and primary fold.\n * @returns The root `places` block, or `undefined` when the primary has\n * no place folds.\n */\nexport function buildRootPlaces(\n\tinputs: BuildRootPlacesInputs,\n): Record<string, PlaceEntry> | undefined {\n\tconst { folds, labels, primaryFold } = inputs;\n\tif (primaryFold.places.size === 0) {\n\t\treturn undefined;\n\t}\n\n\tconst folded: ReadonlyArray<LabeledFold> = [...folds.entries()].map(([name, fold]) => {\n\t\treturn { fold, label: labels.get(name) };\n\t});\n\treturn Object.fromEntries(\n\t\t[...primaryFold.places.entries()].map(([placeKey, primary]) => [\n\t\t\tplaceKey,\n\t\t\tbuildRootPlaceEntry(primary, { folds: folded, placeKey }),\n\t\t]),\n\t);\n}\n\n/**\n * Build the per-environment overlay for `places`, carrying each field only\n * when the environment's value diverges from the resolved root entry. When\n * the environment has a label, the overlay stores the unprefixed\n * `displayName` body so the deploy-time prefix system reapplies the bracket\n * cleanly without double-prefixing. Fields the environment omits are absent\n * from the overlay so the consumer's defu merge resolves them to the root's\n * (also absent) value rather than overwriting with `undefined`.\n *\n * @param inputs - Fold, label, and the already-built root `places` block.\n * @returns The overlay `places` block, or `undefined` when the fold has\n * no place entries.\n */\nexport function buildPlacesOverlay(\n\tinputs: BuildPlacesOverlayInputs,\n): Record<string, PlaceOverlayEntry> | undefined {\n\tconst { fold, label, rootPlaces } = inputs;\n\tif (fold.places.size === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn Object.fromEntries(\n\t\t[...fold.places.entries()].map(([placeKey, foldEntry]) => {\n\t\t\treturn [\n\t\t\t\tplaceKey,\n\t\t\t\tbuildPlaceOverlayEntry({\n\t\t\t\t\tfold: foldEntry,\n\t\t\t\t\tlabel,\n\t\t\t\t\trootEntry: rootPlaces?.[placeKey],\n\t\t\t\t}),\n\t\t\t];\n\t\t}),\n\t);\n}\n\nfunction placeFieldConsensus<F extends OptionalPlaceField>(\n\tinputs: ConsensusInputs<F>,\n): PlaceEntry[F] | undefined {\n\tconst values = inputs.folds.flatMap(({ fold }) => {\n\t\tconst entry = fold.places.get(inputs.placeKey)?.entry;\n\t\treturn entry === undefined ? [] : [entry[inputs.field]];\n\t});\n\n\tconst [first] = values;\n\treturn values.every((value) => Object.is(first, value)) ? first : undefined;\n}\n\nfunction resolveDisplayName(\n\tdisplayName: string | undefined,\n\tlabel: string | undefined,\n): string | undefined {\n\tif (displayName === undefined || label === undefined) {\n\t\treturn displayName;\n\t}\n\n\treturn extractDisplayNamePrefix(displayName).body;\n}\n\nfunction displayNameConsensus(inputs: DisplayNameConsensusInputs): string | undefined {\n\tconst values = inputs.folds.flatMap(({ fold, label }): ReadonlyArray<string | undefined> => {\n\t\tconst entry = fold.places.get(inputs.placeKey)?.entry;\n\t\treturn entry === undefined ? [] : [resolveDisplayName(entry.displayName, label)];\n\t});\n\n\tconst [first] = values;\n\treturn values.every((value) => Object.is(first, value)) ? first : undefined;\n}\n\nfunction buildRootPlaceEntry(\n\tprimary: PlaceFoldEntry,\n\tconsensusBase: {\n\t\treadonly folds: ReadonlyArray<LabeledFold>;\n\t\treadonly placeKey: string;\n\t},\n): PlaceEntry {\n\tconst description = placeFieldConsensus({ ...consensusBase, field: \"description\" });\n\tconst displayName = displayNameConsensus(consensusBase);\n\tconst serverSize = placeFieldConsensus({ ...consensusBase, field: \"serverSize\" });\n\treturn {\n\t\tfilePath: primary.entry.filePath,\n\t\t...(description !== undefined && { description }),\n\t\t...(displayName !== undefined && { displayName }),\n\t\t...(serverSize !== undefined && { serverSize }),\n\t};\n}\n\nfunction buildPlaceOverlayEntry(inputs: BuildPlaceOverlayEntryInputs): PlaceOverlayEntry {\n\tconst { fold, label, rootEntry } = inputs;\n\tconst { description, displayName: rawDisplayName, filePath, serverSize } = fold.entry;\n\tconst displayName = resolveDisplayName(rawDisplayName, label);\n\treturn {\n\t\tplaceId: fold.placeId,\n\t\t...(filePath !== rootEntry?.filePath && { filePath }),\n\t\t...(!Object.is(rootEntry?.description, description) && { description }),\n\t\t...(!Object.is(rootEntry?.displayName, displayName) && { displayName }),\n\t\t...(!Object.is(rootEntry?.serverSize, serverSize) && { serverSize }),\n\t};\n}\n","import type { DeveloperProductEntry, EnvironmentEntry } from \"../schema.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\nimport type { ProductFoldEntry } from \"./fold-products.ts\";\n\ntype ProductOverlayEntry = NonNullable<EnvironmentEntry[\"products\"]>[string];\n\ntype OptionalProductField = \"icon\" | \"isRegionalPricingEnabled\" | \"price\" | \"storePageEnabled\";\n\ninterface ConsensusInputs<F extends OptionalProductField> {\n\treadonly field: F;\n\treadonly folds: ReadonlyArray<EnvironmentFoldResult>;\n\treadonly productKey: string;\n}\n\ninterface FieldEqualInputs {\n\treadonly field: OptionalProductField;\n\treadonly left: DeveloperProductEntry[OptionalProductField];\n\treadonly right: DeveloperProductEntry[OptionalProductField];\n}\n\n/**\n * Project the per-environment product folds into bedrock's root `products`\n * block, giving each optional field (`icon`, `price`, `isRegionalPricingEnabled`,\n * `storePageEnabled`) a value only when every environment that owns the\n * product key agrees. Required fields (`name`, `description`) fall back to\n * the primary's values; divergence on those surfaces in each environment's\n * overlay. Optional fields that diverge stay off root so an environment that\n * omits an optional field does not silently inherit the primary's value\n * through defu's \"undefined treated as empty\" merge.\n *\n * @param folds - Per-environment fold results, keyed by environment name.\n * @param primaryFold - The chosen primary environment's fold; supplies the\n * product key list and the `name` / `description` fallbacks.\n * @returns The root `products` block, or `undefined` when the primary has\n * no product folds.\n */\nexport function buildRootProducts(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n\tprimaryFold: EnvironmentFoldResult,\n): Record<string, DeveloperProductEntry> | undefined {\n\tif (primaryFold.products.length === 0) {\n\t\treturn undefined;\n\t}\n\n\tconst folded = [...folds.values()];\n\treturn Object.fromEntries(\n\t\tprimaryFold.products.map(({ key, entry }) => [\n\t\t\tkey,\n\t\t\tbuildRootProductEntry(entry, { folds: folded, productKey: key }),\n\t\t]),\n\t);\n}\n\n/**\n * Build the per-environment overlay for `products`, carrying each field only\n * when the environment's value diverges from the resolved root entry. Fields\n * the environment omits are absent from the overlay so the consumer's defu\n * merge resolves them to the root's (also absent) value rather than\n * inheriting a primary value the environment never had.\n *\n * @param fold - The per-environment fold whose products are being overlaid.\n * @param rootProducts - The already-built root `products` block; per-product\n * field values from this map suppress overlay entries that match.\n * @returns The overlay `products` block, or `undefined` when no product\n * diverges from the root.\n */\nexport function buildProductsOverlay(\n\tfold: EnvironmentFoldResult,\n\trootProducts: Record<string, DeveloperProductEntry> | undefined,\n): Record<string, ProductOverlayEntry> | undefined {\n\tconst overlay: Record<string, ProductOverlayEntry> = {};\n\tfor (const { key, entry } of fold.products) {\n\t\tconst productOverlay = buildProductOverlayEntry(entry, rootProducts?.[key]);\n\t\tif (Object.keys(productOverlay).length > 0) {\n\t\t\toverlay[key] = productOverlay;\n\t\t}\n\t}\n\n\treturn Object.keys(overlay).length === 0 ? undefined : overlay;\n}\n\nfunction entryForKey(\n\tfold: EnvironmentFoldResult,\n\tproductKey: string,\n): DeveloperProductEntry | undefined {\n\treturn fold.products.find(({ key }) => key === productKey)?.entry;\n}\n\nfunction asIcon(\n\tvalue: DeveloperProductEntry[OptionalProductField],\n): Record<\"en-us\", string> | undefined {\n\treturn typeof value === \"object\" ? value : undefined;\n}\n\nfunction optionalFieldEqual(inputs: FieldEqualInputs): boolean {\n\tif (inputs.field === \"icon\") {\n\t\treturn Object.is(asIcon(inputs.left)?.[\"en-us\"], asIcon(inputs.right)?.[\"en-us\"]);\n\t}\n\n\treturn Object.is(inputs.left, inputs.right);\n}\n\nfunction productOptionalFieldConsensus<F extends OptionalProductField>(\n\tinputs: ConsensusInputs<F>,\n): DeveloperProductEntry[F] | undefined {\n\tconst values = inputs.folds.flatMap((fold): ReadonlyArray<DeveloperProductEntry[F]> => {\n\t\tconst entry = entryForKey(fold, inputs.productKey);\n\t\treturn entry === undefined ? [] : [entry[inputs.field]];\n\t});\n\n\tconst [first] = values;\n\treturn values.every((value) => {\n\t\treturn optionalFieldEqual({ field: inputs.field, left: first, right: value });\n\t})\n\t\t? first\n\t\t: undefined;\n}\n\nfunction buildRootProductEntry(\n\tprimaryEntry: ProductFoldEntry[\"entry\"],\n\tconsensusBase: {\n\t\treadonly folds: ReadonlyArray<EnvironmentFoldResult>;\n\t\treadonly productKey: string;\n\t},\n): DeveloperProductEntry {\n\tconst icon = productOptionalFieldConsensus({ ...consensusBase, field: \"icon\" });\n\tconst isRegionalPricingEnabled = productOptionalFieldConsensus({\n\t\t...consensusBase,\n\t\tfield: \"isRegionalPricingEnabled\",\n\t});\n\tconst price = productOptionalFieldConsensus({ ...consensusBase, field: \"price\" });\n\tconst isStorePageEnabled = productOptionalFieldConsensus({\n\t\t...consensusBase,\n\t\tfield: \"storePageEnabled\",\n\t});\n\n\treturn {\n\t\tname: primaryEntry.name,\n\t\tdescription: primaryEntry.description,\n\t\t...(icon !== undefined && { icon }),\n\t\t...(isRegionalPricingEnabled !== undefined && { isRegionalPricingEnabled }),\n\t\t...(price !== undefined && { price }),\n\t\t...(isStorePageEnabled !== undefined && { storePageEnabled: isStorePageEnabled }),\n\t};\n}\n\nfunction buildProductOverlayEntry(\n\tentry: ProductFoldEntry[\"entry\"],\n\trootEntry: DeveloperProductEntry | undefined,\n): ProductOverlayEntry {\n\treturn {\n\t\t...(entry.name !== rootEntry?.name && { name: entry.name }),\n\t\t...(entry.description !== rootEntry?.description && { description: entry.description }),\n\t\t...(!Object.is(rootEntry?.icon?.[\"en-us\"], entry.icon?.[\"en-us\"]) && { icon: entry.icon }),\n\t\t...(!Object.is(rootEntry?.price, entry.price) && { price: entry.price }),\n\t\t...(!Object.is(rootEntry?.isRegionalPricingEnabled, entry.isRegionalPricingEnabled) && {\n\t\t\tisRegionalPricingEnabled: entry.isRegionalPricingEnabled,\n\t\t}),\n\t\t...(!Object.is(rootEntry?.storePageEnabled, entry.storePageEnabled) && {\n\t\t\tstorePageEnabled: entry.storePageEnabled,\n\t\t}),\n\t};\n}\n","import type { EnvironmentEntry, UniverseEntry } from \"../schema.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\n\ntype UniverseOverlay = NonNullable<EnvironmentEntry[\"universe\"]>;\n\ninterface UniverseOverlayContext {\n\treadonly fold: EnvironmentFoldResult;\n\treadonly hasDivergentUniverseIds: boolean;\n}\n\n/**\n * Decide whether a fold map carries more than one distinct universeId,\n * which forces the migrator to omit `universeId` from the root universe\n * block and write it on every environment overlay (the schema-level XOR\n * rule rejects the same `universeId` appearing in both places).\n *\n * @param folds - Per-environment fold results.\n * @returns `true` when at least two folds carry different universeIds.\n */\nexport function hasDivergentUniverseIds(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n): boolean {\n\tconst distinctIds = new Set(\n\t\t[...folds.values()].flatMap((fold) => {\n\t\t\treturn fold.universe === undefined ? [] : [fold.universe.entry.universeId];\n\t\t}),\n\t);\n\treturn distinctIds.size > 1;\n}\n\n/**\n * Project the chosen primary environment's universe entry onto bedrock's\n * root `universe` block. Strips `universeId` when universes diverge across\n * environments so the schema-level XOR rule stays satisfied; the per-env\n * overlay carries the id in that case (see {@link buildUniverseOverlay}).\n *\n * @param primaryFold - The chosen primary environment's fold; supplies\n * the universe entry whose shared fields land on root.\n * @param shouldOmitUniverseId - `true` when universes diverge across the\n * input fold map (typically the output of {@link hasDivergentUniverseIds}).\n * @returns The root `universe` block, or `undefined` when the primary has\n * no universe fold or when divergent universes leave nothing to land\n * at root.\n */\nexport function buildRootUniverse(\n\tprimaryFold: EnvironmentFoldResult,\n\tshouldOmitUniverseId: boolean,\n): undefined | UniverseEntry {\n\tconst primaryUniverse = primaryFold.universe?.entry;\n\tif (primaryUniverse === undefined) {\n\t\treturn undefined;\n\t}\n\n\tif (!shouldOmitUniverseId) {\n\t\treturn primaryUniverse;\n\t}\n\n\tconst { universeId: _omittedUniverseId, ...sharedFields } = primaryUniverse;\n\tif (Object.keys(sharedFields).length === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn sharedFields;\n}\n\n/**\n * Build the per-environment overlay for `universe`. Returns the env's\n * `universeId` when universes diverge across environments (schema-level\n * XOR demands one universeId source per env); otherwise returns\n * `undefined` so the env inherits the root universe block.\n *\n * @param context - The fold to overlay onto and the divergence flag.\n * @returns The overlay, or `undefined` when no per-env overlay is needed.\n */\nexport function buildUniverseOverlay(context: UniverseOverlayContext): undefined | UniverseOverlay {\n\tconst foldUniverse = context.fold.universe;\n\tif (foldUniverse === undefined) {\n\t\treturn undefined;\n\t}\n\n\tif (context.hasDivergentUniverseIds) {\n\t\treturn { universeId: foldUniverse.entry.universeId };\n\t}\n\n\treturn undefined;\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport type {\n\tConfig,\n\tDeveloperProductEntry,\n\tEnvironmentEntry,\n\tGamePassEntry,\n\tPlaceEntry,\n\tUniverseEntry,\n} from \"../schema.ts\";\nimport { computeEnvironmentLabel } from \"./environment-label.ts\";\nimport { extractDisplayNamePrefix } from \"./extract-display-name-prefix.ts\";\nimport { collectMissingResourceWarnings } from \"./factorize-environments-warnings.ts\";\nimport { buildPlacesOverlay, buildRootPlaces } from \"./factorize-places.ts\";\nimport { buildProductsOverlay, buildRootProducts } from \"./factorize-products.ts\";\nimport {\n\tbuildRootUniverse,\n\tbuildUniverseOverlay,\n\thasDivergentUniverseIds,\n} from \"./factorize-universe.ts\";\nimport type { EnvironmentFoldResult } from \"./fold-environment.ts\";\nimport type { MigrateError, MigrationWarning } from \"./migration-report.ts\";\n\ninterface FactorizeInputs {\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\treadonly primaryEnvironment: string | undefined;\n}\n\ninterface FactorizeResult {\n\treadonly config: Config;\n\treadonly primaryEnvironment: string;\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ntype PassOverlayEntry = NonNullable<EnvironmentEntry[\"passes\"]>[string];\n\ninterface ResolvedPrimary {\n\treadonly name: string;\n\treadonly fold: EnvironmentFoldResult;\n}\n\ninterface OverlayContext {\n\treadonly hasDivergentUniverseIds: boolean;\n\treadonly labels: ReadonlyMap<string, string | undefined>;\n\treadonly primary: EnvironmentFoldResult | undefined;\n\treadonly rootPlaces: Record<string, PlaceEntry> | undefined;\n\treadonly rootProducts: Record<string, DeveloperProductEntry> | undefined;\n}\n\ninterface EnvironmentEntryInputs {\n\treadonly context: OverlayContext;\n\treadonly fold: EnvironmentFoldResult;\n\treadonly label: string | undefined;\n}\n\ninterface BuildConfigInputs {\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\treadonly primaryFold: EnvironmentFoldResult;\n\treadonly primaryName: string;\n}\n\ninterface LooseConfigForBuild {\n\tenvironments: Record<string, EnvironmentEntry>;\n\tpasses?: Record<string, GamePassEntry>;\n\tplaces?: Record<string, PlaceEntry>;\n\tproducts?: Record<string, DeveloperProductEntry>;\n\tuniverse?: UniverseEntry;\n}\n\n/**\n * Project per-environment fold results into a single bedrock `Config` by\n * factoring the chosen primary environment's resolved values up to the root\n * and recording per-environment overlays for fields that diverge from the\n * primary. Pure: no I/O.\n *\n * Primary selection: a single-environment input auto-picks; any other shape\n * requires `inputs.primaryEnvironment` and returns\n * `Err({ kind: \"primaryEnvironmentRequired\" })` when omitted, or\n * `Err({ kind: \"primaryEnvironmentNotFound\" })` when the supplied name is\n * not in the fold map.\n *\n * @param inputs - Per-environment folds and the optional primary hint.\n * @returns `Ok` with the factorized config on success, or `Err` with a\n * discriminated `MigrateError` on primary-selection failure.\n */\nexport function factorizeEnvironments(\n\tinputs: FactorizeInputs,\n): Result<FactorizeResult, MigrateError> {\n\tconst primaryResult = pickPrimary(inputs.folds, inputs.primaryEnvironment);\n\tif (!primaryResult.success) {\n\t\treturn primaryResult;\n\t}\n\n\tconst primary = primaryResult.data;\n\treturn {\n\t\tdata: {\n\t\t\tconfig: buildConfig({\n\t\t\t\tfolds: inputs.folds,\n\t\t\t\tprimaryFold: primary.fold,\n\t\t\t\tprimaryName: primary.name,\n\t\t\t}),\n\t\t\tprimaryEnvironment: primary.name,\n\t\t\twarnings: collectMissingResourceWarnings({ folds: inputs.folds, primary }),\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction pickPrimary(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n\tprimary: string | undefined,\n): Result<ResolvedPrimary, MigrateError> {\n\tconst available = [...folds.keys()];\n\tconst requested = primary ?? (available.length === 1 ? available[0] : undefined);\n\tif (requested === undefined) {\n\t\treturn { err: { available, kind: \"primaryEnvironmentRequired\" }, success: false };\n\t}\n\n\tconst fold = folds.get(requested);\n\tif (fold === undefined) {\n\t\treturn {\n\t\t\terr: { available, kind: \"primaryEnvironmentNotFound\", primary: requested },\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: { name: requested, fold }, success: true };\n}\n\nfunction buildRootPasses(\n\tprimaryFold: EnvironmentFoldResult,\n): Record<string, GamePassEntry> | undefined {\n\tif (primaryFold.passes.length === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn Object.fromEntries(primaryFold.passes.map(({ key, entry }) => [key, entry]));\n}\n\nfunction buildFullPassOverlay(entry: GamePassEntry): PassOverlayEntry {\n\treturn {\n\t\tname: entry.name,\n\t\tdescription: entry.description,\n\t\ticon: entry.icon,\n\t\t...(entry.price !== undefined && { price: entry.price }),\n\t};\n}\n\nfunction buildPassOverlayEntry(\n\tentry: GamePassEntry,\n\tprimary: GamePassEntry,\n): PassOverlayEntry | undefined {\n\tconst overlay: PassOverlayEntry = {};\n\tif (!Object.is(primary.name, entry.name)) {\n\t\toverlay.name = entry.name;\n\t}\n\n\tif (!Object.is(primary.description, entry.description)) {\n\t\toverlay.description = entry.description;\n\t}\n\n\tif (!Object.is(primary.icon[\"en-us\"], entry.icon[\"en-us\"])) {\n\t\toverlay.icon = entry.icon;\n\t}\n\n\tif (!Object.is(primary.price, entry.price)) {\n\t\toverlay.price = entry.price;\n\t}\n\n\treturn Object.keys(overlay).length === 0 ? undefined : overlay;\n}\n\nfunction buildPassesOverlay(\n\tfold: EnvironmentFoldResult,\n\tprimary: EnvironmentFoldResult | undefined,\n): Record<string, PassOverlayEntry> | undefined {\n\tconst primaryByKey = new Map<string, GamePassEntry>(\n\t\tprimary?.passes.map(({ key, entry }) => [key, entry]),\n\t);\n\tconst overlay: Record<string, PassOverlayEntry> = {};\n\tfor (const { key, entry } of fold.passes) {\n\t\tconst primaryEntry = primaryByKey.get(key);\n\t\tconst passOverlay =\n\t\t\tprimaryEntry === undefined\n\t\t\t\t? buildFullPassOverlay(entry)\n\t\t\t\t: buildPassOverlayEntry(entry, primaryEntry);\n\t\tif (passOverlay !== undefined) {\n\t\t\toverlay[key] = passOverlay;\n\t\t}\n\t}\n\n\treturn Object.keys(overlay).length === 0 ? undefined : overlay;\n}\n\nfunction buildEnvironmentEntry(inputs: EnvironmentEntryInputs): EnvironmentEntry {\n\tconst { context, fold, label } = inputs;\n\tconst passes = buildPassesOverlay(fold, context.primary);\n\tconst places = buildPlacesOverlay({ fold, label, rootPlaces: context.rootPlaces });\n\tconst products = buildProductsOverlay(fold, context.rootProducts);\n\tconst universe = buildUniverseOverlay({\n\t\tfold,\n\t\thasDivergentUniverseIds: context.hasDivergentUniverseIds,\n\t});\n\treturn {\n\t\t...(label !== undefined && { label }),\n\t\t...(passes !== undefined && { passes }),\n\t\t...(places !== undefined && { places }),\n\t\t...(products !== undefined && { products }),\n\t\t...(universe !== undefined && { universe }),\n\t};\n}\n\nfunction buildEnvironmentEntries(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n\tcontext: OverlayContext,\n): Record<string, EnvironmentEntry> {\n\treturn Object.fromEntries(\n\t\t[...folds].map(([name, fold]) => [\n\t\t\tname,\n\t\t\tbuildEnvironmentEntry({ context, fold, label: context.labels.get(name) }),\n\t\t]),\n\t);\n}\n\nfunction buildEnvironmentLabels(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n): ReadonlyMap<string, string | undefined> {\n\treturn new Map([...folds].map(([name, fold]) => [name, computeEnvironmentLabel(fold)]));\n}\n\nfunction stripDisplayNamePrefix(\n\tuniverse: undefined | UniverseEntry,\n\tlabel: string | undefined,\n): undefined | UniverseEntry {\n\tif (universe === undefined || label === undefined || universe.displayName === undefined) {\n\t\treturn universe;\n\t}\n\n\treturn { ...universe, displayName: extractDisplayNamePrefix(universe.displayName).body };\n}\n\nfunction buildConfig(inputs: BuildConfigInputs): Config {\n\tconst { folds, primaryFold, primaryName } = inputs;\n\tconst labels = buildEnvironmentLabels(folds);\n\tconst places = buildRootPlaces({ folds, labels, primaryFold });\n\tconst products = buildRootProducts(folds, primaryFold);\n\tconst shouldOmitRootUniverseId = hasDivergentUniverseIds(folds);\n\tconst environments = buildEnvironmentEntries(folds, {\n\t\thasDivergentUniverseIds: shouldOmitRootUniverseId,\n\t\tlabels,\n\t\tprimary: primaryFold,\n\t\trootPlaces: places,\n\t\trootProducts: products,\n\t});\n\tconst passes = buildRootPasses(primaryFold);\n\tconst universe = stripDisplayNamePrefix(\n\t\tbuildRootUniverse(primaryFold, shouldOmitRootUniverseId),\n\t\tlabels.get(primaryName),\n\t);\n\tconst config: LooseConfigForBuild = {\n\t\tenvironments,\n\t\t...(passes !== undefined && { passes }),\n\t\t...(places !== undefined && { places }),\n\t\t...(products !== undefined && { products }),\n\t\t...(universe !== undefined && { universe }),\n\t};\n\t// Precondition for the cast: `hasDivergentUniverseIds` decides\n\t// `shouldOmitRootUniverseId` and `buildRootUniverse` and\n\t// `buildUniverseOverlay` honour it, so the constructed config always\n\t// lands in one arm of the discriminated `Config` union (root has\n\t// `universeId` and no env carries one, or every env carries one and\n\t// root omits it).\n\treturn config as unknown as Config;\n}\n","import { asResourceKey, asRobloxAssetId, asSha256Hex, isSha256Hex } from \"../../types/ids.ts\";\nimport type { ResourceKey, Sha256Hex } from \"../../types/ids.ts\";\nimport type { GamePassOutputs } from \"../resources.ts\";\nimport type { GamePassEntry } from \"../schema.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst PASS_KIND = \"pass\";\n\n/**\n * Folded representation of one Mantle `pass_<k>` resource.\n *\n * `entry` carries the bedrock `Config.passes[<k>]` shape (omitting\n * `iconFileHashes` because the hashes are recomputed from disk by the shell\n * before they land on state). `outputs` carries the Roblox-assigned\n * identifiers for the pass and its icon. `mantleIconFileHashes` preserves\n * the hashes recorded by Mantle so the shell can fall back to them when the\n * icon file is missing on disk. `mantlePath` roots warnings at the\n * resource so the report is searchable.\n */\nexport interface PassFoldEntry {\n\t/** User-supplied Mantle key, branded as a bedrock `ResourceKey`. */\n\treadonly key: ResourceKey;\n\t/** Bedrock `Config.passes[<k>]` block populated from the pass resource. */\n\treadonly entry: GamePassEntry;\n\t/** Locale-keyed Mantle-recorded icon hashes; retained as a fallback for hash recomputation. */\n\treadonly mantleIconFileHashes: Record<\"en-us\", Sha256Hex>;\n\t/** Resource-rooted Mantle path (`pass_<k>`) used to anchor warnings. */\n\treadonly mantlePath: string;\n\t/** Roblox-assigned identifiers carried into `BedrockState.resources[*].outputs`. */\n\treadonly outputs: GamePassOutputs;\n}\n\n/**\n * Output of folding the `pass_<k>` Mantle resources of one environment\n * into bedrock-shaped game-pass entries.\n *\n * `passes` carries one record per well-formed `pass_<k>` resource;\n * malformed resources are dropped silently in this slice (warning-emitting\n * variants land alongside their consuming rules). `warnings` accumulates\n * per-rule diagnostics; the slice emits an empty list because no\n * interpretive rules have been wired yet.\n */\ninterface PassesFoldResult {\n\t/** One folded entry per well-formed Mantle `pass_<k>` resource. */\n\treadonly passes: ReadonlyArray<PassFoldEntry>;\n\t/** Per-rule diagnostics; empty in this slice, populated as rules land. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface PassInputs {\n\treadonly name: string;\n\treadonly description: string;\n\treadonly iconFileHash: Sha256Hex;\n\treadonly iconFilePath: string;\n\treadonly price: number | undefined;\n}\n\ninterface PassOutputsRaw {\n\treadonly assetId: string;\n\treadonly iconAssetId: string;\n}\n\n/**\n * Fold the `pass_<k>` Mantle resources of one environment into a list\n * of bedrock game-pass entries plus matching outputs.\n *\n * Each well-formed `pass_<k>.inputs.{name, description, iconFilePath,\n * price}` becomes the corresponding `Config.passes[<k>]` entry; the\n * matching `pass_<k>.outputs.{assetId, iconAssetId}` becomes the\n * `BedrockState` resource's `outputs`. The Mantle-recorded\n * `iconFileHash` is preserved on the result so the shell can fall back\n * to it when the icon file is missing on disk.\n *\n * Resources whose payload is malformed (non-object, missing required\n * string field, non-`Sha256Hex` hash, missing output IDs) are dropped\n * silently in this slice; warning-emitting variants land alongside the\n * specific interpretive rules that need them.\n *\n * @param resources - Resource list for one Mantle environment.\n * @returns The folded pass entries plus an aggregated warnings list.\n */\nexport function foldPasses(resources: ReadonlyArray<MantleResource>): PassesFoldResult {\n\tconst passes = resources.flatMap((resource): ReadonlyArray<PassFoldEntry> => {\n\t\tif (resource.kind !== PASS_KIND) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst folded = foldOnePass(resource);\n\t\treturn folded === undefined ? [] : [folded];\n\t});\n\n\treturn { passes, warnings: [] };\n}\n\nfunction buildEntry(inputs: PassInputs): GamePassEntry {\n\tconst base = {\n\t\tname: inputs.name,\n\t\tdescription: inputs.description,\n\t\ticon: { \"en-us\": inputs.iconFilePath },\n\t};\n\n\treturn inputs.price === undefined ? base : { ...base, price: inputs.price };\n}\n\nfunction isObjectPayload(value: unknown): value is Record<string, unknown> {\n\treturn Object.prototype.toString.call(value) === \"[object Object]\";\n}\n\nfunction readString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction readPrice(raw: Record<string, unknown>): number | undefined {\n\tconst candidate = raw[\"price\"];\n\treturn typeof candidate === \"number\" ? candidate : undefined;\n}\n\nfunction readPassInputs(raw: unknown): PassInputs | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst name = readString(raw[\"name\"]);\n\tconst description = readString(raw[\"description\"]);\n\tconst iconFilePath = readString(raw[\"iconFilePath\"]);\n\tconst iconFileHashRaw = readString(raw[\"iconFileHash\"]);\n\tif (\n\t\tname === undefined ||\n\t\tdescription === undefined ||\n\t\ticonFilePath === undefined ||\n\t\ticonFileHashRaw === undefined ||\n\t\t!isSha256Hex(iconFileHashRaw)\n\t) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tname,\n\t\tdescription,\n\t\ticonFileHash: asSha256Hex(iconFileHashRaw),\n\t\ticonFilePath,\n\t\tprice: readPrice(raw),\n\t};\n}\n\nfunction coerceRobloxId(value: unknown): string | undefined {\n\tif (typeof value === \"string\") {\n\t\treturn value;\n\t}\n\n\tif (Number.isInteger(value)) {\n\t\treturn String(value);\n\t}\n\n\treturn undefined;\n}\n\nfunction readPassOutputs(raw: unknown): PassOutputsRaw | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst assetId = coerceRobloxId(raw[\"assetId\"]);\n\tconst iconAssetId = coerceRobloxId(raw[\"iconAssetId\"]);\n\tif (assetId === undefined || iconAssetId === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { assetId, iconAssetId };\n}\n\nfunction foldOnePass(resource: MantleResource): PassFoldEntry | undefined {\n\tconst inputs = readPassInputs(resource.inputs);\n\tif (inputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst outputs = readPassOutputs(resource.outputs);\n\tif (outputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tkey: asResourceKey(resource.key),\n\t\tentry: buildEntry(inputs),\n\t\tmantleIconFileHashes: { \"en-us\": inputs.iconFileHash },\n\t\tmantlePath: `${PASS_KIND}_${resource.key}`,\n\t\toutputs: {\n\t\t\tassetId: asRobloxAssetId(outputs.assetId),\n\t\t\ticonAssetIds: { \"en-us\": asRobloxAssetId(outputs.iconAssetId) },\n\t\t},\n\t};\n}\n","import type { UniverseOutputs } from \"../resources.ts\";\nimport type { UniverseEntry } from \"../schema.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\n\n/**\n * Output of one fold rule contributing to the eventual `UniverseEntry`.\n * The orchestrator (`foldUniverse`) composes fragments by spreading\n * `entryFragment` into the final entry, spreading `outputsFragment` into\n * the matching `BedrockState` resource's `outputs`, and concatenating\n * `warnings`. Most rules contribute only to the entry; rules that surface\n * Roblox-assigned identifiers populate `outputsFragment`. The\n * `mergeFragment` helper, used inside individual fold modules to combine\n * sub-fragments, only merges `entryFragment` and `warnings` because no\n * rule that uses it currently produces outputs.\n */\nexport interface FoldFragment {\n\t/**\n\t * Subset of universe fields this rule populated (empty when the rule\n\t * did not fire). `universeId` is excluded because the orchestrator\n\t * seeds it from the experience resource's outputs; no fold rule\n\t * contributes to it.\n\t */\n\treadonly entryFragment: UniverseEntryFragment;\n\t/** Subset of universe outputs this rule populated; absent when the rule contributes no Roblox-assigned identifiers. */\n\treadonly outputsFragment?: Partial<UniverseOutputs>;\n\t/** Diagnostics this rule emitted. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\n/**\n * Subset of {@link UniverseEntry} that any individual fold rule may\n * populate. `universeId` is excluded because the orchestrator seeds it\n * from the experience resource's outputs; no fold rule contributes to it.\n */\ntype UniverseEntryFragment = {\n\treadonly [Key in Exclude<keyof UniverseEntry, \"universeId\">]?: UniverseEntry[Key];\n};\n\n/** Sentinel value for fold rules that produced neither output nor warnings. */\nexport const EMPTY_FRAGMENT: FoldFragment = { entryFragment: {}, warnings: [] };\n\n/**\n * One row in a static blocked-field table: the Mantle field name and the\n * upstream limitation reason that the migrator surfaces verbatim in the\n * warning's `reason` slot. Used by folds that emit one `blocked`\n * `MigrationWarning` per non-`undefined` legacy-only field.\n */\nexport interface BlockedFieldRule {\n\t/** Mantle input key to test for non-`undefined`. */\n\treadonly field: string;\n\t/** Human-readable reason naming the upstream limitation. */\n\treadonly reason: string;\n}\n\n/**\n * Required fields for an `interpretive` warning, gathered as a single\n * argument so this builder stays under the project's max-params cap.\n */\ninterface InterpretiveWarningSpec {\n\t/** Path the migrator wrote in the bedrock config. */\n\treadonly bedrockPath: string;\n\t/** Resource-rooted Mantle path the rule consumed. */\n\treadonly mantlePath: string;\n\t/** Stable rule identifier audited in the migration report. */\n\treadonly rule: string;\n}\n\n/**\n * Narrow a value to a plain object payload (rejects arrays, null, primitives).\n *\n * @param value - Value to test.\n * @returns `true` when `value` is a plain object record.\n */\nexport function isObjectPayload(value: unknown): value is Record<string, unknown> {\n\treturn Object.prototype.toString.call(value) === \"[object Object]\";\n}\n\n/**\n * Coerce a Mantle-recorded Roblox identifier to a string. YAML can carry\n * IDs as either a quoted string or a bare integer; both are accepted.\n * Anything else (null, float, object) returns `undefined`.\n *\n * @param value - Raw value pulled from a Mantle resource's outputs.\n * @returns The stringified ID, or `undefined` when the value is not a valid wire shape.\n */\nexport function coerceRobloxId(value: unknown): string | undefined {\n\tif (typeof value === \"string\") {\n\t\treturn value;\n\t}\n\n\tif (Number.isInteger(value)) {\n\t\treturn String(value);\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Combine two fragments, with the right-hand side overriding overlapping\n * `entryFragment` keys and its `warnings` appended after the left's.\n * Does not merge `outputsFragment`; outputs composition is handled by\n * `foldUniverse` across the whole fragment list, not by this helper.\n *\n * @param left - Earlier fragment.\n * @param right - Later fragment whose entry-fragment keys win on conflict.\n * @returns The merged fragment.\n */\nexport function mergeFragment(left: FoldFragment, right: FoldFragment): FoldFragment {\n\treturn {\n\t\tentryFragment: { ...left.entryFragment, ...right.entryFragment },\n\t\twarnings: [...left.warnings, ...right.warnings],\n\t};\n}\n\n/**\n * Build an `interpretive` `MigrationWarning`. Used by every fold rule that\n * applies a documented mapping; the report consumer narrows on `kind`.\n *\n * @param spec - The bedrock path, mantle path, and rule identifier.\n * @returns A `MigrationWarning` with `kind: \"interpretive\"`.\n */\nexport function interpretiveWarning(spec: InterpretiveWarningSpec): MigrationWarning {\n\treturn {\n\t\tbedrockPath: spec.bedrockPath,\n\t\tkind: \"interpretive\",\n\t\tmantlePath: spec.mantlePath,\n\t\trule: spec.rule,\n\t};\n}\n\n/**\n * Build a `blocked` `MigrationWarning`. Used when no Open Cloud writable\n * endpoint exists for the field the migrator encountered.\n *\n * @param mantlePath - Resource-rooted Mantle path of the blocked field.\n * @param reason - Human-readable reason describing what is unsupported.\n * @returns A `MigrationWarning` with `kind: \"blocked\"`.\n */\nexport function blockedWarning(mantlePath: string, reason: string): MigrationWarning {\n\treturn { kind: \"blocked\", mantlePath, reason };\n}\n\n/**\n * Build an `ambiguous` `MigrationWarning`. Used when a value is mappable\n * but unsafe to act on without user input; the hint guides the next step.\n *\n * @param mantlePath - Resource-rooted Mantle path of the ambiguous field.\n * @param hint - Human-readable suggestion for resolving the ambiguity.\n * @returns A `MigrationWarning` with `kind: \"ambiguous\"`.\n */\nexport function ambiguousWarning(mantlePath: string, hint: string): MigrationWarning {\n\treturn { hint, kind: \"ambiguous\", mantlePath };\n}\n","import { type BlockedFieldRule, blockedWarning, isObjectPayload } from \"./fold-universe-shared.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst PLACE_CONFIGURATION_KIND = \"placeConfiguration\";\n\n/**\n * `placeConfiguration_<k>` fields with no Open Cloud writable endpoint.\n * `name`, `description`, and `maxPlayerCount` are intentionally omitted:\n * `foldDisplayName` and `foldPlaces` fold them into the bedrock config.\n */\nconst BLOCKED_FIELDS: ReadonlyArray<BlockedFieldRule> = [\n\t{\n\t\tfield: \"allowCopying\",\n\t\treason: \"placeConfiguration.allowCopying has no Open Cloud equivalent\",\n\t},\n\t{\n\t\tfield: \"socialSlotType\",\n\t\treason: \"placeConfiguration social-slot config has no Open Cloud equivalent\",\n\t},\n\t{\n\t\tfield: \"customSocialSlotsCount\",\n\t\treason: \"placeConfiguration social-slot config has no Open Cloud equivalent\",\n\t},\n];\n\n/**\n * Emit one `blocked` `MigrationWarning` per non-`undefined` legacy-only\n * field on every `placeConfiguration_<k>` resource. The Mantle null\n * sentinel (`~`) is normalized to `undefined` by `parseState`, so absent\n * and null fields both skip.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns One warning per blocked field present, ordered by resource\n * list then by the static field table.\n */\nexport function foldBlockedPlaceFields(\n\tresources: ReadonlyArray<MantleResource>,\n): ReadonlyArray<MigrationWarning> {\n\treturn resources.flatMap((resource) => {\n\t\tif (resource.kind !== PLACE_CONFIGURATION_KIND || !isObjectPayload(resource.inputs)) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst { key, inputs } = resource;\n\t\treturn BLOCKED_FIELDS.flatMap((rule) => {\n\t\t\tif (inputs[rule.field] === undefined) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\treturn [\n\t\t\t\tblockedWarning(`${PLACE_CONFIGURATION_KIND}_${key}.${rule.field}`, rule.reason),\n\t\t\t];\n\t\t});\n\t});\n}\n","import { isSha256Hex, type Sha256Hex } from \"../../types/ids.ts\";\nimport type { PlaceOutputs } from \"../resources.ts\";\nimport type { PlaceEntry } from \"../schema.ts\";\nimport { foldBlockedPlaceFields } from \"./fold-blocked-place-fields.ts\";\nimport { interpretiveWarning, isObjectPayload } from \"./fold-universe-shared.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst PLACE_KIND = \"place\";\nconst PLACE_FILE_KIND = \"placeFile\";\nconst PLACE_CONFIGURATION_KIND = \"placeConfiguration\";\n\n/**\n * Folded place data for one Mantle resource key.\n *\n * `entry` populates the bedrock root `Config.places.<key>` block (currently\n * just `filePath`). `placeId` is the Roblox-assigned place ID Mantle stored\n * under `place_<k>.outputs.assetId`; the migrator threads it onto the\n * matching per-environment overlay (`environments[env].places.<key>`).\n * `fileHash` is the SHA-256 digest Mantle recorded under\n * `placeFile_<k>.inputs.fileHash`; the shell consumes it as the fallback\n * value when the migrator cannot recompute the hash from disk. `outputs`\n * carries the auto-incrementing publish version number Mantle stored under\n * `placeFile_<k>.outputs.version`.\n */\nexport interface PlaceFoldEntry {\n\t/** Bedrock root `Config.places.<key>` body (currently `filePath` only). */\n\treadonly entry: PlaceEntry;\n\t/** Mantle-recorded SHA-256 hex digest of the place file (fallback for hash recomputation). */\n\treadonly fileHash: Sha256Hex;\n\t/** Roblox-assigned identifiers carried into `BedrockState.resources[*].outputs`. */\n\treadonly outputs: PlaceOutputs;\n\t/** Roblox-assigned place ID copied from `place_<key>.outputs.assetId`. */\n\treadonly placeId: string;\n}\n\n/**\n * Output of folding the place-related Mantle resources of one environment\n * into bedrock's `places` shape. Each Mantle place key produces one entry\n * in `entries`; orphans (a `place_<k>` without a `placeFile_<k>`, or vice\n * versa) emit one `ambiguous` warning each instead of being projected into\n * `entries`.\n */\ninterface PlaceFoldResult {\n\t/** Folded entries keyed by Mantle's place key (the suffix after the first `_`). */\n\treadonly entries: ReadonlyMap<string, PlaceFoldEntry>;\n\t/** Per-rule diagnostics: orphan resources surface as `ambiguous` warnings. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface PlaceOutputsRaw {\n\treadonly assetId: string;\n}\n\ninterface PlaceFileInputsRaw {\n\treadonly fileHash: Sha256Hex;\n\treadonly filePath: string;\n}\n\ninterface PlaceFileOutputsRaw {\n\treadonly version: number;\n}\n\ninterface PlaceConfigFoldResult {\n\treadonly entry: PlaceFoldEntry;\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface ApplyPlaceConfigFieldsInputs {\n\treadonly key: string;\n\treadonly configResource: MantleResource | undefined;\n\treadonly folded: PlaceFoldEntry;\n\treadonly isStart: boolean;\n}\n\ninterface PlaceConfigFragmentRule {\n\treadonly bedrockField: string;\n\treadonly mantleField: string;\n\treadonly rule: string;\n}\n\ninterface PlaceConfigFragment {\n\treadonly entry: Partial<PlaceEntry>;\n\treadonly warnings: ReadonlyArray<PlaceConfigFragmentRule>;\n}\n\ninterface PlaceConfigRule {\n\treadonly bedrockField: keyof PlaceEntry;\n\treadonly mantleField: string;\n\treadonly read: (value: unknown) => number | string | undefined;\n\treadonly rule: string;\n}\n\ninterface PlaceBuckets {\n\treadonly placeConfigurations: Map<string, MantleResource>;\n\treadonly placeFiles: Map<string, MantleResource>;\n\treadonly places: Map<string, MantleResource>;\n}\n\n/**\n * Fold the place-related Mantle resources of one environment into a map of\n * `PlaceFoldEntry` plus accompanying warnings. Pairs each `place_<k>`\n * with the matching `placeFile_<k>` by key; an unmatched resource on\n * either side emits an `ambiguous` warning carrying the orphan's\n * `mantlePath` and a hint instructing the user to verify their Mantle\n * state.\n *\n * Matched pairs whose payloads fail shape validation (missing or\n * non-numeric `assetId`, missing `filePath`, malformed `fileHash`, etc.)\n * are dropped silently rather than surfacing as warnings, mirroring the\n * malformed-input handling in `foldUniverse`. A `malformed` warning kind\n * spanning the per-kind folds is left to a follow-up slice.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns The folded entries plus orphan warnings.\n */\nexport function foldPlaces(resources: ReadonlyArray<MantleResource>): PlaceFoldResult {\n\tconst buckets = bucketByKind(resources);\n\tconst matched = collectMatchedFolds(buckets);\n\tconst orphans = collectOrphanWarnings(buckets);\n\treturn {\n\t\tentries: matched.entries,\n\t\twarnings: [...matched.warnings, ...orphans, ...foldBlockedPlaceFields(resources)],\n\t};\n}\n\nfunction collectMatchedFolds(buckets: PlaceBuckets): PlaceFoldResult {\n\tconst { placeConfigurations, placeFiles, places } = buckets;\n\tconst entries = new Map<string, PlaceFoldEntry>();\n\tconst warnings: Array<MigrationWarning> = [];\n\tfor (const [key, placeResource] of places) {\n\t\tconst fileResource = placeFiles.get(key);\n\t\tif (fileResource === undefined) {\n\t\t\twarnings.push(orphanWarning(PLACE_KIND, key));\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst folded = mergeMatchedPair(placeResource, fileResource);\n\t\tif (folded === undefined) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst configFold = applyPlaceConfigFields({\n\t\t\tkey,\n\t\t\tconfigResource: placeConfigurations.get(key),\n\t\t\tfolded,\n\t\t\tisStart: isStartPlace(placeResource),\n\t\t});\n\t\tentries.set(key, configFold.entry);\n\t\twarnings.push(...configFold.warnings);\n\t}\n\n\treturn { entries, warnings };\n}\n\nfunction collectOrphanWarnings(buckets: PlaceBuckets): ReadonlyArray<MigrationWarning> {\n\tconst { placeConfigurations, placeFiles, places } = buckets;\n\tconst warnings: Array<MigrationWarning> = [];\n\tfor (const [key] of placeFiles) {\n\t\tif (!places.has(key)) {\n\t\t\twarnings.push(orphanWarning(PLACE_FILE_KIND, key));\n\t\t}\n\t}\n\n\tfor (const [key] of placeConfigurations) {\n\t\tif (!places.has(key) || !placeFiles.has(key)) {\n\t\t\twarnings.push(placeConfigOrphanWarning(key));\n\t\t}\n\t}\n\n\treturn warnings;\n}\n\nfunction bucketByKind(resources: ReadonlyArray<MantleResource>): PlaceBuckets {\n\tconst places = new Map<string, MantleResource>();\n\tconst placeFiles = new Map<string, MantleResource>();\n\tconst placeConfigurations = new Map<string, MantleResource>();\n\tfor (const resource of resources) {\n\t\tswitch (resource.kind) {\n\t\t\tcase PLACE_CONFIGURATION_KIND: {\n\t\t\t\tplaceConfigurations.set(resource.key, resource);\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase PLACE_FILE_KIND: {\n\t\t\t\tplaceFiles.set(resource.key, resource);\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase PLACE_KIND: {\n\t\t\t\tplaces.set(resource.key, resource);\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// No default\n\t\t}\n\t}\n\n\treturn { placeConfigurations, placeFiles, places };\n}\n\nfunction readString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction readPositiveInteger(value: unknown): number | undefined {\n\tif (typeof value !== \"number\" || !Number.isInteger(value) || value <= 0) {\n\t\treturn undefined;\n\t}\n\n\treturn value;\n}\n\nconst ALWAYS_RULES: ReadonlyArray<PlaceConfigRule> = [\n\t{\n\t\tbedrockField: \"description\",\n\t\tmantleField: \"description\",\n\t\tread: readString,\n\t\trule: \"place-description\",\n\t},\n\t{\n\t\tbedrockField: \"serverSize\",\n\t\tmantleField: \"maxPlayerCount\",\n\t\tread: readPositiveInteger,\n\t\trule: \"max-player-count-to-server-size\",\n\t},\n];\n\nconst NON_START_RULES: ReadonlyArray<PlaceConfigRule> = [\n\t{\n\t\tbedrockField: \"displayName\",\n\t\tmantleField: \"name\",\n\t\tread: readString,\n\t\trule: \"place-name-to-display-name\",\n\t},\n];\n\nfunction placeConfigRules(isStart: boolean): ReadonlyArray<PlaceConfigRule> {\n\treturn isStart ? ALWAYS_RULES : [...ALWAYS_RULES, ...NON_START_RULES];\n}\n\nfunction readPlaceConfigFragment(\n\tinputs: Record<string, unknown>,\n\trules: ReadonlyArray<PlaceConfigRule>,\n): PlaceConfigFragment {\n\treturn rules.reduce<{\n\t\tentry: Partial<PlaceEntry>;\n\t\twarnings: Array<PlaceConfigFragmentRule>;\n\t}>(\n\t\t(accumulator, rule) => {\n\t\t\tconst value = rule.read(inputs[rule.mantleField]);\n\t\t\tif (value === undefined) {\n\t\t\t\treturn accumulator;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tentry: { ...accumulator.entry, [rule.bedrockField]: value },\n\t\t\t\twarnings: [\n\t\t\t\t\t...accumulator.warnings,\n\t\t\t\t\t{\n\t\t\t\t\t\tbedrockField: rule.bedrockField,\n\t\t\t\t\t\tmantleField: rule.mantleField,\n\t\t\t\t\t\trule: rule.rule,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t};\n\t\t},\n\t\t{ entry: {}, warnings: [] },\n\t);\n}\n\nfunction applyPlaceConfigFields(inputs: ApplyPlaceConfigFieldsInputs): PlaceConfigFoldResult {\n\tconst { key, configResource, folded, isStart } = inputs;\n\tif (configResource === undefined || !isObjectPayload(configResource.inputs)) {\n\t\treturn { entry: folded, warnings: [] };\n\t}\n\n\tconst fragment = readPlaceConfigFragment(configResource.inputs, placeConfigRules(isStart));\n\tconst entry: PlaceFoldEntry = {\n\t\t...folded,\n\t\tentry: { ...folded.entry, ...fragment.entry },\n\t};\n\n\treturn {\n\t\tentry,\n\t\twarnings: fragment.warnings.map((rule) => {\n\t\t\treturn interpretiveWarning({\n\t\t\t\tbedrockPath: `places.${key}.${rule.bedrockField}`,\n\t\t\t\tmantlePath: `${PLACE_CONFIGURATION_KIND}_${key}.${rule.mantleField}`,\n\t\t\t\trule: rule.rule,\n\t\t\t});\n\t\t}),\n\t};\n}\n\nfunction isStartPlace(resource: MantleResource): boolean {\n\tif (!isObjectPayload(resource.inputs)) {\n\t\treturn false;\n\t}\n\n\treturn resource.inputs[\"isStart\"] === true;\n}\n\nfunction coerceRobloxId(value: unknown): string | undefined {\n\tif (typeof value === \"string\") {\n\t\treturn value;\n\t}\n\n\tif (Number.isInteger(value)) {\n\t\treturn String(value);\n\t}\n\n\treturn undefined;\n}\n\nfunction readPlaceOutputs(resource: MantleResource): PlaceOutputsRaw | undefined {\n\tconst { outputs } = resource;\n\tif (!isObjectPayload(outputs)) {\n\t\treturn undefined;\n\t}\n\n\tconst assetId = coerceRobloxId(outputs[\"assetId\"]);\n\tif (assetId === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { assetId };\n}\n\nfunction readPlaceFileInputs(resource: MantleResource): PlaceFileInputsRaw | undefined {\n\tconst { inputs } = resource;\n\tif (!isObjectPayload(inputs)) {\n\t\treturn undefined;\n\t}\n\n\tconst { fileHash, filePath } = inputs;\n\tif (typeof filePath !== \"string\" || typeof fileHash !== \"string\" || !isSha256Hex(fileHash)) {\n\t\treturn undefined;\n\t}\n\n\treturn { fileHash, filePath };\n}\n\nfunction readPlaceFileOutputs(resource: MantleResource): PlaceFileOutputsRaw | undefined {\n\tconst { outputs } = resource;\n\tif (!isObjectPayload(outputs)) {\n\t\treturn undefined;\n\t}\n\n\tconst { version } = outputs;\n\tif (typeof version !== \"number\") {\n\t\treturn undefined;\n\t}\n\n\treturn { version };\n}\n\nfunction mergeMatchedPair(\n\tplaceResource: MantleResource,\n\tfileResource: MantleResource,\n): PlaceFoldEntry | undefined {\n\tconst placeOutputs = readPlaceOutputs(placeResource);\n\tconst fileInputs = readPlaceFileInputs(fileResource);\n\tconst fileOutputs = readPlaceFileOutputs(fileResource);\n\tif (placeOutputs === undefined || fileInputs === undefined || fileOutputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tentry: { filePath: fileInputs.filePath },\n\t\tfileHash: fileInputs.fileHash,\n\t\toutputs: { versionNumber: fileOutputs.version },\n\t\tplaceId: placeOutputs.assetId,\n\t};\n}\n\nfunction orphanWarning(kind: string, key: string): MigrationWarning {\n\treturn {\n\t\thint: \"Verify your Mantle state file: each place_<k> resource must be paired with a matching placeFile_<k>, and vice versa.\",\n\t\tkind: \"ambiguous\",\n\t\tmantlePath: `${kind}_${key}`,\n\t};\n}\n\nfunction placeConfigOrphanWarning(key: string): MigrationWarning {\n\treturn {\n\t\thint: \"Verify your Mantle state file: each placeConfiguration_<k> requires a matching place_<k>+placeFile_<k> pair to fold its data into the bedrock config.\",\n\t\tkind: \"ambiguous\",\n\t\tmantlePath: `${PLACE_CONFIGURATION_KIND}_${key}`,\n\t};\n}\n","import { asResourceKey, asRobloxAssetId, asSha256Hex, isSha256Hex } from \"../../types/ids.ts\";\nimport type { ResourceKey, Sha256Hex } from \"../../types/ids.ts\";\nimport type { DeveloperProductOutputs } from \"../resources.ts\";\nimport type { DeveloperProductEntry } from \"../schema.ts\";\nimport { coerceRobloxId, isObjectPayload } from \"./fold-universe-shared.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst PRODUCT_KIND = \"product\";\nconst PRODUCT_ICON_KIND = \"productIcon\";\n\n/**\n * Folded representation of one Mantle `product_<k>` resource.\n *\n * `entry` carries the bedrock `Config.products[<k>]` shape; when a matching\n * `productIcon_<k>` resource is present it also carries the locale-keyed icon\n * path. `outputs` carries the Roblox-assigned identifiers for the product and\n * its optional icon. `mantleIconFileHashes` preserves the hash recorded by\n * Mantle so the shell can fall back to it when the icon file is missing on\n * disk; absent when no icon partner is found. `mantlePath` roots warnings at\n * the resource so the report is searchable.\n */\nexport interface ProductFoldEntry {\n\t/** User-supplied Mantle key, branded as a bedrock `ResourceKey`. */\n\treadonly key: ResourceKey;\n\t/** Bedrock `Config.products[<k>]` block populated from the product resource. */\n\treadonly entry: DeveloperProductEntry;\n\t/** Locale-keyed Mantle-recorded icon hashes; absent when no productIcon partner is paired. */\n\treadonly mantleIconFileHashes?: Record<\"en-us\", Sha256Hex>;\n\t/** Resource-rooted Mantle path (`product_<k>`) used to anchor warnings. */\n\treadonly mantlePath: string;\n\t/** Roblox-assigned identifiers carried into `BedrockState.resources[*].outputs`. */\n\treadonly outputs: DeveloperProductOutputs;\n}\n\n/**\n * Output of folding the `product_<k>` and `productIcon_<k>` Mantle resources\n * of one environment into bedrock-shaped developer-product entries.\n *\n * `products` carries one record per well-formed `product_<k>` resource;\n * malformed resources are dropped silently. `warnings` carries one\n * `ambiguous` warning per orphan `productIcon_<k>` (no matching product).\n */\ninterface ProductsFoldResult {\n\t/** One folded entry per well-formed Mantle `product_<k>` resource. */\n\treadonly products: ReadonlyArray<ProductFoldEntry>;\n\t/** Per-rule diagnostics: orphan productIcon resources surface as `ambiguous` warnings. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface ProductInputs {\n\treadonly name: string;\n\treadonly description: string;\n\treadonly price: number | undefined;\n}\n\ninterface ProductOutputsRaw {\n\treadonly productId: string;\n}\n\ninterface ProductIconInputs {\n\treadonly fileHash: Sha256Hex;\n\treadonly filePath: string;\n}\n\ninterface ProductIconOutputsRaw {\n\treadonly assetId: string;\n}\n\ninterface ProductIconParts {\n\treadonly icon: Record<\"en-us\", string>;\n\treadonly iconImageAssetId: string;\n\treadonly mantleIconFileHashes: Record<\"en-us\", Sha256Hex>;\n}\n\n/**\n * Fold the `product_<k>` (and matching `productIcon_<k>`) Mantle resources\n * of one environment into a list of bedrock developer-product entries plus\n * matching outputs. Pairs each `product_<k>` with the optional\n * `productIcon_<k>` resource sharing the same key; when the icon partner is\n * present and well-formed, the locale-keyed icon path lands on the entry\n * and the Roblox-assigned `iconImageAssetId` lands on the outputs.\n *\n * Resources whose payload is malformed (non-object, missing required string\n * field, missing `assetId`, malformed `fileHash`) are dropped silently.\n * Orphan `productIcon_<k>` resources (no matching product) emit one\n * `ambiguous` warning each.\n *\n * @param resources - Resource list for one Mantle environment.\n * @returns The folded product entries plus per-rule diagnostics.\n */\nexport function foldProducts(resources: ReadonlyArray<MantleResource>): ProductsFoldResult {\n\tconst { productIcons, products } = bucketByKind(resources);\n\tconst folded = products.flatMap((resource): ReadonlyArray<ProductFoldEntry> => {\n\t\tconst iconResource = productIcons.get(resource.key);\n\t\tconst entry = foldOneProduct(resource, iconResource);\n\t\treturn entry === undefined ? [] : [entry];\n\t});\n\n\tconst productKeys = new Set(products.map((resource) => resource.key));\n\tconst warnings = [...productIcons.keys()]\n\t\t.filter((key) => !productKeys.has(key))\n\t\t.map((key) => orphanIconWarning(key));\n\n\treturn { products: folded, warnings };\n}\n\nfunction bucketByKind(resources: ReadonlyArray<MantleResource>): {\n\treadonly productIcons: Map<string, MantleResource>;\n\treadonly products: ReadonlyArray<MantleResource>;\n} {\n\tconst products: Array<MantleResource> = [];\n\tconst productIcons = new Map<string, MantleResource>();\n\tfor (const resource of resources) {\n\t\tif (resource.kind === PRODUCT_KIND) {\n\t\t\tproducts.push(resource);\n\t\t} else if (resource.kind === PRODUCT_ICON_KIND) {\n\t\t\tproductIcons.set(resource.key, resource);\n\t\t}\n\t}\n\n\treturn { productIcons, products };\n}\n\nfunction buildEntry(\n\tinputs: ProductInputs,\n\ticon: Record<\"en-us\", string> | undefined,\n): DeveloperProductEntry {\n\tconst base: DeveloperProductEntry = {\n\t\tname: inputs.name,\n\t\tdescription: inputs.description,\n\t};\n\tconst withPrice = inputs.price === undefined ? base : { ...base, price: inputs.price };\n\treturn icon === undefined ? withPrice : { ...withPrice, icon };\n}\n\nfunction readString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction readPrice(raw: Record<string, unknown>): number | undefined {\n\tconst candidate = raw[\"price\"];\n\treturn typeof candidate === \"number\" ? candidate : undefined;\n}\n\nfunction readProductInputs(raw: unknown): ProductInputs | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst name = readString(raw[\"name\"]);\n\tconst description = readString(raw[\"description\"]);\n\tif (name === undefined || description === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { name, description, price: readPrice(raw) };\n}\n\nfunction readProductOutputs(raw: unknown): ProductOutputsRaw | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\t// Mantle's `assetId` is the canonical marketplace product id (the value\n\t// MarketplaceService.PromptProductPurchase and Open Cloud's URL accept).\n\t// Mantle's `productId` is a legacy config id from a different endpoint\n\t// family that Open Cloud v2 does not route.\n\tconst productId = coerceRobloxId(raw[\"assetId\"]);\n\tif (productId === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { productId };\n}\n\nfunction readProductIconInputs(raw: unknown): ProductIconInputs | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst filePath = readString(raw[\"filePath\"]);\n\tconst fileHash = readString(raw[\"fileHash\"]);\n\tif (filePath === undefined || fileHash === undefined || !isSha256Hex(fileHash)) {\n\t\treturn undefined;\n\t}\n\n\treturn { fileHash: asSha256Hex(fileHash), filePath };\n}\n\nfunction readProductIconOutputs(raw: unknown): ProductIconOutputsRaw | undefined {\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst assetId = coerceRobloxId(raw[\"assetId\"]);\n\tif (assetId === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { assetId };\n}\n\nfunction readProductIconParts(resource: MantleResource): ProductIconParts | undefined {\n\tconst inputs = readProductIconInputs(resource.inputs);\n\tconst outputs = readProductIconOutputs(resource.outputs);\n\tif (inputs === undefined || outputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\ticon: { \"en-us\": inputs.filePath },\n\t\ticonImageAssetId: outputs.assetId,\n\t\tmantleIconFileHashes: { \"en-us\": inputs.fileHash },\n\t};\n}\n\nfunction foldOneProduct(\n\tresource: MantleResource,\n\ticonResource: MantleResource | undefined,\n): ProductFoldEntry | undefined {\n\tconst inputs = readProductInputs(resource.inputs);\n\tif (inputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst outputs = readProductOutputs(resource.outputs);\n\tif (outputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst iconParts = iconResource === undefined ? undefined : readProductIconParts(iconResource);\n\tconst productOutputs: DeveloperProductOutputs =\n\t\ticonParts === undefined\n\t\t\t? { iconImageAssetId: undefined, productId: asRobloxAssetId(outputs.productId) }\n\t\t\t: {\n\t\t\t\t\ticonImageAssetId: asRobloxAssetId(iconParts.iconImageAssetId),\n\t\t\t\t\tproductId: asRobloxAssetId(outputs.productId),\n\t\t\t\t};\n\n\tconst base: ProductFoldEntry = {\n\t\tkey: asResourceKey(resource.key),\n\t\tentry: buildEntry(inputs, iconParts?.icon),\n\t\tmantlePath: `${PRODUCT_KIND}_${resource.key}`,\n\t\toutputs: productOutputs,\n\t};\n\n\treturn iconParts === undefined\n\t\t? base\n\t\t: { ...base, mantleIconFileHashes: iconParts.mantleIconFileHashes };\n}\n\nfunction orphanIconWarning(key: string): MigrationWarning {\n\treturn {\n\t\thint: \"Verify your Mantle state file: each productIcon_<k> resource must be paired with a matching product_<k>.\",\n\t\tkind: \"ambiguous\",\n\t\tmantlePath: `${PRODUCT_ICON_KIND}_${key}`,\n\t};\n}\n","import {\n\ttype BlockedFieldRule,\n\tblockedWarning,\n\ttype FoldFragment,\n\tisObjectPayload,\n} from \"./fold-universe-shared.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst EXPERIENCE_KIND = \"experience\";\nconst EXPERIENCE_CONFIGURATION_KIND = \"experienceConfiguration\";\nconst UNIVERSE_AVATAR_PREFIX = \"universeAvatar\";\nconst UNIVERSE_AVATAR_REASON = \"avatar configuration has no Open Cloud equivalent\";\nconst GROUP_ID_REASON =\n\t\"Mantle used `groupId` to set the owning group when creating the experience. Bedrock requires pre-existing universe and place IDs and does not use this field.\";\n\n/**\n * `experienceConfiguration_singleton` fields with no Open Cloud writable\n * endpoint.\n */\nconst BLOCKED_FIELDS: ReadonlyArray<BlockedFieldRule> = [\n\t{ field: \"genre\", reason: \"Roblox does not expose `genre` via Open Cloud\" },\n\t{ field: \"isForSale\", reason: \"paid-access flag has no Open Cloud equivalent\" },\n\t{ field: \"price\", reason: \"paid-access flag has no Open Cloud equivalent\" },\n\t{\n\t\tfield: \"studioAccessToApisAllowed\",\n\t\treason: \"Open Cloud does not currently expose a write endpoint for studio API access\",\n\t},\n\t{\n\t\tfield: \"permissions\",\n\t\treason: \"experienceConfiguration.permissions has no Open Cloud equivalent\",\n\t},\n\t{ field: \"isArchived\", reason: \"isArchived has no Open Cloud equivalent\" },\n\t{ field: \"isFriendsOnly\", reason: \"isFriendsOnly has no Open Cloud equivalent\" },\n];\n\n/**\n * Emit one `blocked` `MigrationWarning` per non-`undefined` legacy-only\n * field on the experience-side Mantle resources: every entry in the\n * static `BLOCKED_FIELDS` table on `experienceConfiguration_singleton`,\n * any key under the `universeAvatar` prefix, and `groupId` on\n * `experience_singleton`. The Mantle null sentinel (`~`) is normalized\n * to `undefined` by `parseState`, so absent and null fields both skip.\n *\n * The fragment's `entryFragment` is always empty; this fold contributes\n * only warnings, and the `warnings` array is empty when none of the\n * watched resources have a populated blocked field.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns A fragment whose warnings list every blocked field present.\n */\nexport function foldBlockedExperienceFields(\n\tresources: ReadonlyArray<MantleResource>,\n): FoldFragment {\n\treturn {\n\t\tentryFragment: {},\n\t\twarnings: [...configurationWarnings(resources), ...groupIdWarnings(resources)],\n\t};\n}\n\nfunction groupIdWarnings(\n\tresources: ReadonlyArray<MantleResource>,\n): ReadonlyArray<MigrationWarning> {\n\tconst experience = resources.find((resource) => resource.kind === EXPERIENCE_KIND);\n\tif (\n\t\texperience === undefined ||\n\t\t!isObjectPayload(experience.inputs) ||\n\t\texperience.inputs[\"groupId\"] === undefined\n\t) {\n\t\treturn [];\n\t}\n\n\treturn [blockedWarning(\"experience_singleton.groupId\", GROUP_ID_REASON)];\n}\n\nfunction staticFieldWarnings(inputs: Record<string, unknown>): ReadonlyArray<MigrationWarning> {\n\treturn BLOCKED_FIELDS.flatMap((rule) => {\n\t\tif (inputs[rule.field] === undefined) {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn [blockedWarning(`experienceConfiguration_singleton.${rule.field}`, rule.reason)];\n\t});\n}\n\nfunction avatarFieldWarnings(inputs: Record<string, unknown>): ReadonlyArray<MigrationWarning> {\n\treturn Object.keys(inputs)\n\t\t.filter((key) => key.startsWith(UNIVERSE_AVATAR_PREFIX) && inputs[key] !== undefined)\n\t\t.map((key) =>\n\t\t\tblockedWarning(`experienceConfiguration_singleton.${key}`, UNIVERSE_AVATAR_REASON),\n\t\t);\n}\n\nfunction configurationWarnings(\n\tresources: ReadonlyArray<MantleResource>,\n): ReadonlyArray<MigrationWarning> {\n\tconst config = resources.find((resource) => resource.kind === EXPERIENCE_CONFIGURATION_KIND);\n\tif (config === undefined || !isObjectPayload(config.inputs)) {\n\t\treturn [];\n\t}\n\n\treturn [...staticFieldWarnings(config.inputs), ...avatarFieldWarnings(config.inputs)];\n}\n","import {\n\tambiguousWarning,\n\tEMPTY_FRAGMENT,\n\ttype FoldFragment,\n\tinterpretiveWarning,\n\tisObjectPayload,\n} from \"./fold-universe-shared.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst PLACE_KIND = \"place\";\nconst PLACE_CONFIGURATION_KIND = \"placeConfiguration\";\n\ninterface NamedPlaceConfig {\n\treadonly key: string;\n\treadonly name: string;\n}\n\nfunction readPlaceConfigName(resource: MantleResource): NamedPlaceConfig | undefined {\n\tif (!isObjectPayload(resource.inputs)) {\n\t\treturn undefined;\n\t}\n\n\tconst { name } = resource.inputs;\n\treturn typeof name === \"string\" ? { key: resource.key, name } : undefined;\n}\n\nfunction isStartPlace(resource: MantleResource): boolean {\n\tif (resource.kind !== PLACE_KIND || !isObjectPayload(resource.inputs)) {\n\t\treturn false;\n\t}\n\n\treturn resource.inputs[\"isStart\"] === true;\n}\n\nfunction startKeys(resources: ReadonlyArray<MantleResource>): ReadonlyArray<string> {\n\treturn resources.filter(isStartPlace).map((resource) => resource.key);\n}\n\nfunction interpretiveFragment(named: NamedPlaceConfig): FoldFragment {\n\treturn {\n\t\tentryFragment: { displayName: named.name },\n\t\twarnings: [\n\t\t\tinterpretiveWarning({\n\t\t\t\tbedrockPath: \"universe.displayName\",\n\t\t\t\tmantlePath: `${PLACE_CONFIGURATION_KIND}_${named.key}.name`,\n\t\t\t\trule: \"start-place-name-to-display-name\",\n\t\t\t}),\n\t\t],\n\t};\n}\n\nconst AMBIGUOUS_MULTIPLE_STARTS: FoldFragment = {\n\tentryFragment: {},\n\twarnings: [\n\t\tambiguousWarning(\n\t\t\t\"place_*.isStart\",\n\t\t\t\"Multiple place_<k> resources have inputs.isStart=true; pick one as the canonical start place.\",\n\t\t),\n\t],\n};\n\n/**\n * Fold the start place's `placeConfiguration_<k>.name` into\n * `universe.displayName`. Non-start places' names are folded onto each\n * place's `displayName` by `foldPlaces`; multiple `isStart: true` places\n * emit one `ambiguous` warning and skip the displayName mapping.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns The folded displayName plus per-rule diagnostics.\n */\nexport function foldDisplayName(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tconst starts = startKeys(resources);\n\tif (starts.length >= 2) {\n\t\treturn AMBIGUOUS_MULTIPLE_STARTS;\n\t}\n\n\tconst [startKey] = starts;\n\tconst startConfigResource = resources.findLast((resource) => {\n\t\treturn resource.kind === PLACE_CONFIGURATION_KIND && resource.key === startKey;\n\t});\n\tif (startConfigResource === undefined) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\tconst named = readPlaceConfigName(startConfigResource);\n\treturn named === undefined ? EMPTY_FRAGMENT : interpretiveFragment(named);\n}\n","import {\n\tblockedWarning,\n\tEMPTY_FRAGMENT,\n\ttype FoldFragment,\n\tisObjectPayload,\n} from \"./fold-universe-shared.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst EXPERIENCE_ICON_KIND = \"experienceIcon\";\n\nconst BLOCKED_REASON =\n\t\"Open Cloud has no route to set a universe's source-language game icon; configure it via the Roblox creator portal.\";\n\n/**\n * Surface every Mantle `experienceIcon_<key>` resource as a `blocked`\n * migration warning. Bedrock has no `UniverseEntry.icon` field today\n * because no Open Cloud endpoint accepts a source-language game icon, so\n * the migrator emits one warning per legacy resource (rather than the\n * first matching entry only) so the operator can audit each affected\n * environment before reconfiguring the icon by hand.\n *\n * Resources whose payload is malformed (non-object inputs/outputs,\n * non-string `filePath`) are skipped silently, matching the\n * malformed-payload behaviour of the other fold rules.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns A fragment whose `warnings` carries one `blocked` entry per\n * legacy experience-icon resource, or {@link EMPTY_FRAGMENT} when none\n * are present.\n */\nexport function foldExperienceIcon(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tconst warnings = resources\n\t\t.filter(\n\t\t\t(resource) => resource.kind === EXPERIENCE_ICON_KIND && hasReadablePayload(resource),\n\t\t)\n\t\t.map((resource) =>\n\t\t\tblockedWarning(`${EXPERIENCE_ICON_KIND}_${resource.key}`, BLOCKED_REASON),\n\t\t);\n\n\tif (warnings.length === 0) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn { entryFragment: {}, warnings };\n}\n\nfunction hasReadablePayload(resource: MantleResource): boolean {\n\tif (!isObjectPayload(resource.inputs)) {\n\t\treturn false;\n\t}\n\n\tconst { filePath } = resource.inputs;\n\treturn typeof filePath === \"string\";\n}\n","import type { SocialLinkField } from \"../resources.ts\";\nimport {\n\tblockedWarning,\n\tEMPTY_FRAGMENT,\n\ttype FoldFragment,\n\tinterpretiveWarning,\n\tisObjectPayload,\n\tmergeFragment,\n} from \"./fold-universe-shared.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst SOCIAL_LINK_KIND = \"socialLink\";\n\nconst SOCIAL_LINK_DOMAIN_TO_FIELD: Readonly<Record<string, SocialLinkField>> = {\n\t\"discord.gg\": \"discordSocialLink\",\n\t\"facebook.com\": \"facebookSocialLink\",\n\t\"guilded.gg\": \"guildedSocialLink\",\n\t\"roblox.com\": \"robloxGroupSocialLink\",\n\t\"twitch.tv\": \"twitchSocialLink\",\n\t\"twitter.com\": \"twitterSocialLink\",\n\t\"www.roblox.com\": \"robloxGroupSocialLink\",\n\t\"youtube.com\": \"youtubeSocialLink\",\n};\n\ninterface KnownSocialLink {\n\treadonly field: SocialLinkField;\n\treadonly mantlePath: string;\n\treadonly title: string;\n\treadonly url: string;\n}\n\n/**\n * Fold every `socialLink_<domain>` Mantle resource into the matching\n * `universe.<field>SocialLink` entry. Unknown domains emit a `blocked`\n * warning and are dropped from the output. Malformed payloads (non-string\n * `title` or `url`) drop silently per the established convention.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns The folded social-link entries plus per-rule diagnostics.\n */\nexport function foldSocialLinks(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\treturn resources\n\t\t.filter((resource) => resource.kind === SOCIAL_LINK_KIND)\n\t\t.reduce<FoldFragment>(\n\t\t\t(accumulator, resource) => mergeFragment(accumulator, mapSocialLink(resource)),\n\t\t\tEMPTY_FRAGMENT,\n\t\t);\n}\n\nfunction knownFragment(known: KnownSocialLink): FoldFragment {\n\treturn {\n\t\tentryFragment: { [known.field]: { title: known.title, uri: known.url } },\n\t\twarnings: [\n\t\t\tinterpretiveWarning({\n\t\t\t\tbedrockPath: `universe.${known.field}`,\n\t\t\t\tmantlePath: known.mantlePath,\n\t\t\t\trule: \"domain-to-field\",\n\t\t\t}),\n\t\t],\n\t};\n}\n\nfunction mapSocialLink(resource: MantleResource): FoldFragment {\n\tconst mantlePath = `${SOCIAL_LINK_KIND}_${resource.key}`;\n\tconst field = SOCIAL_LINK_DOMAIN_TO_FIELD[resource.key];\n\tif (field === undefined) {\n\t\treturn {\n\t\t\tentryFragment: {},\n\t\t\twarnings: [blockedWarning(mantlePath, `Unknown socialLink domain: ${resource.key}`)],\n\t\t};\n\t}\n\n\tif (!isObjectPayload(resource.inputs)) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\tconst { title, url } = resource.inputs;\n\tif (typeof title !== \"string\" || typeof url !== \"string\") {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn knownFragment({ field, mantlePath, title, url });\n}\n","import { asRobloxAssetId } from \"../../types/ids.ts\";\nimport type { UniverseOutputs } from \"../resources.ts\";\nimport type { ResolvedUniverseEntry } from \"../schema.ts\";\nimport { foldBlockedExperienceFields } from \"./fold-blocked-experience-fields.ts\";\nimport { foldDisplayName } from \"./fold-display-name.ts\";\nimport { foldExperienceIcon } from \"./fold-experience-icon.ts\";\nimport { foldSocialLinks } from \"./fold-social-links.ts\";\nimport {\n\tblockedWarning,\n\tEMPTY_FRAGMENT,\n\ttype FoldFragment,\n\tinterpretiveWarning,\n\tisObjectPayload,\n\tmergeFragment,\n} from \"./fold-universe-shared.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\nconst EXPERIENCE_KIND = \"experience\";\nconst EXPERIENCE_ACTIVATION_KIND = \"experienceActivation\";\nconst EXPERIENCE_CONFIGURATION_KIND = \"experienceConfiguration\";\nconst SPATIAL_VOICE_KIND = \"spatialVoice\";\n\nconst PLAYABLE_DEVICE_TO_FLAG: Readonly<\n\tRecord<string, \"consoleEnabled\" | \"desktopEnabled\" | \"mobileEnabled\" | \"tabletEnabled\">\n> = {\n\tComputer: \"desktopEnabled\",\n\tConsole: \"consoleEnabled\",\n\tPhone: \"mobileEnabled\",\n\tTablet: \"tabletEnabled\",\n};\n\n/**\n * Output of folding the experience-related Mantle resources of one\n * environment into Bedrock's `universe` shape.\n *\n * `entry` populates the bedrock `Config.universe` block. `outputs`\n * populates the matching `BedrockState` resource's `outputs` field.\n * `warnings` carries per-rule diagnostics that the migration report\n * presents to the user.\n */\ninterface UniverseFoldResult {\n\t/**\n\t * Bedrock universe entry populated from the experience resource. Carries\n\t * `universeId` because the Mantle state always knows which universe the\n\t * environment targets; downstream consumers rely on the post-merge\n\t * invariant that `universeId` is present.\n\t */\n\treadonly entry: ResolvedUniverseEntry;\n\t/** Roblox-assigned identifiers carried into `BedrockState.resources[*].outputs`. */\n\treadonly outputs: UniverseOutputs;\n\t/** Per-rule diagnostics emitted while folding this environment's resources. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface ExperienceOutputs {\n\treadonly assetId: string;\n\treadonly startPlaceId: string;\n}\n\n/**\n * Fold the universe-contributing Mantle resources of one environment\n * into a `UniverseEntry` plus matching `UniverseOutputs`.\n *\n * Returns `undefined` when no `experience_singleton` resource is present;\n * the caller treats that as \"this environment has no universe to migrate\"\n * and omits the `universe` field from the resulting `Config`.\n *\n * @param resources - Resource list for one Mantle environment.\n * @returns The folded universe data plus warnings, or `undefined` when\n * no `experience_singleton` is present.\n */\nexport function foldUniverse(\n\tresources: ReadonlyArray<MantleResource>,\n): undefined | UniverseFoldResult {\n\tconst experience = resources.find((resource) => resource.kind === EXPERIENCE_KIND);\n\tif (experience === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst outputs = readExperienceOutputs(experience);\n\tif (outputs === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst fragments = collectUniverseFragments(resources);\n\n\tconst entry: ResolvedUniverseEntry = fragments.reduce<ResolvedUniverseEntry>(\n\t\t(accumulator, fragment) => ({ ...accumulator, ...fragment.entryFragment }),\n\t\t{ universeId: outputs.assetId },\n\t);\n\n\tconst universeOutputs: UniverseOutputs = fragments.reduce<UniverseOutputs>(\n\t\t(accumulator, fragment) => ({ ...accumulator, ...fragment.outputsFragment }),\n\t\t{ rootPlaceId: asRobloxAssetId(outputs.startPlaceId) },\n\t);\n\n\tconst warnings = fragments.flatMap((fragment) => fragment.warnings);\n\n\treturn {\n\t\tentry,\n\t\toutputs: universeOutputs,\n\t\twarnings,\n\t};\n}\n\nfunction collectUniverseFragments(\n\tresources: ReadonlyArray<MantleResource>,\n): ReadonlyArray<FoldFragment> {\n\treturn [\n\t\tfoldPlayableDevices(resources),\n\t\tfoldPrivateServers(resources),\n\t\tfoldVoiceChat(resources),\n\t\tfoldExperienceActivation(resources),\n\t\tfoldSocialLinks(resources),\n\t\tfoldDisplayName(resources),\n\t\tfoldExperienceIcon(resources),\n\t\tfoldBlockedExperienceFields(resources),\n\t];\n}\n\nfunction coerceRobloxId(value: unknown): string | undefined {\n\tif (typeof value === \"string\") {\n\t\treturn value;\n\t}\n\n\tif (Number.isInteger(value)) {\n\t\treturn String(value);\n\t}\n\n\treturn undefined;\n}\n\nfunction readExperienceOutputs(resource: MantleResource): ExperienceOutputs | undefined {\n\tconst raw = resource.outputs;\n\tif (!isObjectPayload(raw)) {\n\t\treturn undefined;\n\t}\n\n\tconst assetId = coerceRobloxId(raw[\"assetId\"]);\n\tconst startPlaceId = coerceRobloxId(raw[\"startPlaceId\"]);\n\tif (assetId === undefined || startPlaceId === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { assetId, startPlaceId };\n}\n\nconst PLAYABLE_DEVICES_PATH = \"experienceConfiguration_singleton.playableDevices\";\n\nfunction mapPlayableDevice(raw: unknown): FoldFragment {\n\tconst flag = typeof raw === \"string\" ? PLAYABLE_DEVICE_TO_FLAG[raw] : undefined;\n\tif (flag === undefined) {\n\t\treturn {\n\t\t\tentryFragment: {},\n\t\t\twarnings: [\n\t\t\t\tblockedWarning(\n\t\t\t\t\tPLAYABLE_DEVICES_PATH,\n\t\t\t\t\t`Unknown playableDevices value: ${String(raw)}`,\n\t\t\t\t),\n\t\t\t],\n\t\t};\n\t}\n\n\treturn {\n\t\tentryFragment: { [flag]: true },\n\t\twarnings: [\n\t\t\tinterpretiveWarning({\n\t\t\t\tbedrockPath: `universe.${flag}`,\n\t\t\t\tmantlePath: PLAYABLE_DEVICES_PATH,\n\t\t\t\trule: \"list-to-flag\",\n\t\t\t}),\n\t\t],\n\t};\n}\n\nfunction foldPlayableDevices(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tconst config = resources.find((resource) => resource.kind === EXPERIENCE_CONFIGURATION_KIND);\n\tif (config === undefined || !isObjectPayload(config.inputs)) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\tconst { playableDevices } = config.inputs;\n\tif (!Array.isArray(playableDevices)) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn playableDevices.reduce<FoldFragment>(\n\t\t(accumulator, raw) => mergeFragment(accumulator, mapPlayableDevice(raw)),\n\t\tEMPTY_FRAGMENT,\n\t);\n}\n\nconst PRIVATE_SERVERS_DISABLED: FoldFragment = {\n\tentryFragment: {},\n\twarnings: [\n\t\tinterpretiveWarning({\n\t\t\tbedrockPath: \"universe.privateServerPriceRobux\",\n\t\t\tmantlePath: \"experienceConfiguration_singleton.allowPrivateServers\",\n\t\t\trule: \"private-servers-disabled-omitted\",\n\t\t}),\n\t],\n};\n\nfunction pricedPrivateServersFragment(price: number): FoldFragment {\n\treturn {\n\t\tentryFragment: { privateServerPriceRobux: price },\n\t\twarnings: [\n\t\t\tinterpretiveWarning({\n\t\t\t\tbedrockPath: \"universe.privateServerPriceRobux\",\n\t\t\t\tmantlePath: \"experienceConfiguration_singleton.privateServerPrice\",\n\t\t\t\trule: \"private-servers-priced\",\n\t\t\t}),\n\t\t],\n\t};\n}\n\nconst EXPERIENCE_ACTIVATION_BLOCKED: FoldFragment = {\n\tentryFragment: {},\n\twarnings: [\n\t\tblockedWarning(\n\t\t\t\"experienceActivation_singleton.isActive\",\n\t\t\t\"isActive has no Open Cloud equivalent\",\n\t\t),\n\t],\n};\n\nfunction readIsActive(resources: ReadonlyArray<MantleResource>): boolean | undefined {\n\tconst activation = resources.find((resource) => resource.kind === EXPERIENCE_ACTIVATION_KIND);\n\tif (activation === undefined || !isObjectPayload(activation.inputs)) {\n\t\treturn undefined;\n\t}\n\n\tconst { isActive } = activation.inputs;\n\treturn typeof isActive === \"boolean\" ? isActive : undefined;\n}\n\nfunction foldExperienceActivation(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tif (readIsActive(resources) === undefined) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn EXPERIENCE_ACTIVATION_BLOCKED;\n}\n\nfunction foldVoiceChat(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tconst voice = resources.find((resource) => resource.kind === SPATIAL_VOICE_KIND);\n\tif (voice === undefined || !isObjectPayload(voice.inputs)) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\tconst { enabled } = voice.inputs;\n\tif (typeof enabled !== \"boolean\") {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn {\n\t\tentryFragment: { voiceChatEnabled: enabled },\n\t\twarnings: [\n\t\t\tinterpretiveWarning({\n\t\t\t\tbedrockPath: \"universe.voiceChatEnabled\",\n\t\t\t\tmantlePath: \"spatialVoice_singleton.enabled\",\n\t\t\t\trule: \"voice-chat-enabled\",\n\t\t\t}),\n\t\t],\n\t};\n}\n\nfunction foldPrivateServers(resources: ReadonlyArray<MantleResource>): FoldFragment {\n\tconst config = resources.find((resource) => resource.kind === EXPERIENCE_CONFIGURATION_KIND);\n\tif (config === undefined || !isObjectPayload(config.inputs)) {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\tconst { allowPrivateServers, privateServerPrice } = config.inputs;\n\tif (allowPrivateServers === false) {\n\t\treturn PRIVATE_SERVERS_DISABLED;\n\t}\n\n\tif (allowPrivateServers !== true || typeof privateServerPrice !== \"number\") {\n\t\treturn EMPTY_FRAGMENT;\n\t}\n\n\treturn pricedPrivateServersFragment(privateServerPrice);\n}\n","import type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\n/**\n * Mantle resource kinds bedrock plans to support but does not yet model.\n * Maps the wire-form discriminator to the human-readable noun used in\n * each emitted `deferred` warning's `reason`. The reason string is\n * shared across all resources of the same kind.\n */\nconst DEFERRED_KIND_REASONS: Readonly<Record<string, string>> = {\n\tassetAlias: \"asset aliases\",\n\taudioAsset: \"audio assets\",\n\tbadge: \"badges\",\n\tbadgeIcon: \"badge icons\",\n\texperienceThumbnail: \"experience thumbnails\",\n\texperienceThumbnailOrder: \"experience thumbnail ordering\",\n\timageAsset: \"image assets\",\n\tnotification: \"experience notifications\",\n};\n\n/**\n * Emit one `deferred` `MigrationWarning` per Mantle resource whose kind\n * bedrock has reserved for a future slice. The warning's `mantlePath` is\n * resource-rooted (`<kind>_<key>`). Resources whose kind is not in the\n * deferred set are passed over silently.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns One warning per deferred-kind resource, in the input order.\n */\nexport function foldUnsupported(\n\tresources: ReadonlyArray<MantleResource>,\n): ReadonlyArray<MigrationWarning> {\n\treturn resources.flatMap((resource): ReadonlyArray<MigrationWarning> => {\n\t\tconst humanName = DEFERRED_KIND_REASONS[resource.kind];\n\t\tif (humanName === undefined) {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: \"deferred\",\n\t\t\t\tmantlePath: `${resource.kind}_${resource.key}`,\n\t\t\t\treason: `${humanName}: not yet modeled in bedrock; will surface once the relevant kind ships`,\n\t\t\t},\n\t\t];\n\t});\n}\n","import type { UniverseOutputs } from \"../resources.ts\";\nimport type { ResolvedUniverseEntry } from \"../schema.ts\";\nimport { foldPasses, type PassFoldEntry } from \"./fold-passes.ts\";\nimport { foldPlaces, type PlaceFoldEntry } from \"./fold-places.ts\";\nimport { foldProducts, type ProductFoldEntry } from \"./fold-products.ts\";\nimport { foldUniverse } from \"./fold-universe.ts\";\nimport { foldUnsupported } from \"./fold-unsupported.ts\";\nimport type { MigrationWarning } from \"./migration-report.ts\";\nimport type { MantleResource } from \"./types.ts\";\n\n/**\n * Result of folding one Mantle environment's resource list into bedrock\n * shapes. A Mantle environment without an experience resource yields\n * `universe: undefined`, signalling that the bedrock `Config` for this\n * environment carries no `universe` block. `places`, `passes`, and\n * `products` are always present (potentially empty) so the orchestrator\n * does not have to special-case \"no <kind>\" against \"<kind> not yet\n * wired\"; orphan place resources surface as `ambiguous` warnings, and\n * resources of kinds bedrock plans to support but does not yet model\n * surface as `deferred` warnings.\n */\nexport interface EnvironmentFoldResult {\n\t/** Folded pass entries for this environment, in declaration order. */\n\treadonly passes: ReadonlyArray<PassFoldEntry>;\n\t/** Folded place entries for this environment, keyed by Mantle's place key. */\n\treadonly places: ReadonlyMap<string, PlaceFoldEntry>;\n\t/** Folded developer-product entries for this environment, in declaration order. */\n\treadonly products: ReadonlyArray<ProductFoldEntry>;\n\t/** Folded universe data for this environment, or `undefined` when no experience is present. */\n\treadonly universe:\n\t\t| undefined\n\t\t| {\n\t\t\t\treadonly entry: ResolvedUniverseEntry;\n\t\t\t\treadonly outputs: UniverseOutputs;\n\t\t };\n\t/** Per-rule diagnostics aggregated across all per-kind folds. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\n/**\n * Orchestrate the per-kind folds for one Mantle environment, gathering\n * the results and warnings into a single `EnvironmentFoldResult`. Pure;\n * delegates to `foldUniverse`, `foldPlaces`, `foldPasses`, `foldProducts`,\n * and `foldUnsupported`. Emitted warning paths are resource-rooted.\n *\n * @param resources - Mantle resource list for one environment.\n * @returns The folded per-kind data plus aggregated warnings.\n */\nexport function foldEnvironment(resources: ReadonlyArray<MantleResource>): EnvironmentFoldResult {\n\tconst universeResult = foldUniverse(resources);\n\tconst universe =\n\t\tuniverseResult === undefined\n\t\t\t? undefined\n\t\t\t: { entry: universeResult.entry, outputs: universeResult.outputs };\n\n\tconst placesResult = foldPlaces(resources);\n\tconst passesResult = foldPasses(resources);\n\tconst productsResult = foldProducts(resources);\n\n\tconst warnings = [\n\t\t...(universeResult?.warnings ?? []),\n\t\t...placesResult.warnings,\n\t\t...passesResult.warnings,\n\t\t...productsResult.warnings,\n\t\t...foldUnsupported(resources),\n\t];\n\n\treturn {\n\t\tpasses: passesResult.passes,\n\t\tplaces: placesResult.entries,\n\t\tproducts: productsResult.products,\n\t\tuniverse,\n\t\twarnings,\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { parseYAML } from \"confbox\";\n\nimport type { MigrateError } from \"./migration-report.ts\";\nimport type { MantleResource, MantleStateV6 } from \"./types.ts\";\n\nconst SUPPORTED_VERSIONS = [\"6\"] as const;\n\n/**\n * Parse the contents of a `.mantle-state.yml` file into a typed\n * `MantleStateV6` envelope. Pure: takes a string, returns a `Result`.\n *\n * The parser also normalizes JSON `null` values to `undefined` recursively\n * across each resource's `inputs` and `outputs` payloads (per the project\n * type convention) and splits each resource's `id` field into the\n * `(kind, key)` pair carried on `MantleResource`.\n *\n * Surfaces three discriminated `MigrateError` kinds:\n * - `stateParseFailed` when the YAML parser refuses the file or its\n * structural shape (missing `environments`, malformed resource entry,\n * non-string `id`, etc.) does not match the v6 envelope.\n * - `unsupportedMantleStateVersion` when the `version` field decodes but\n * is not one of the values in `SUPPORTED_VERSIONS`.\n *\n * @param raw - Raw YAML text decoded from the state file.\n * @param sourceFile - Path or identifier of the source file, threaded into\n * any returned `MigrateError` for diagnostics.\n * @returns `Ok` with the parsed envelope, or `Err` with a discriminated\n * `MigrateError` describing why the file was rejected.\n */\nexport function parseState(raw: string, sourceFile: string): Result<MantleStateV6, MigrateError> {\n\tlet envelope: Record<string, unknown>;\n\ttry {\n\t\tconst parsed = parseYAML(raw);\n\t\tenvelope = expectObject(parsed);\n\t} catch (err) {\n\t\treturn parseFailedFrom(err, sourceFile);\n\t}\n\n\tconst versionResult = parseVersion(envelope[\"version\"]);\n\tif (!versionResult.success) {\n\t\treturn versionResult;\n\t}\n\n\tlet environments: Readonly<Record<string, ReadonlyArray<MantleResource>>>;\n\ttry {\n\t\tenvironments = parseEnvironments(envelope[\"environments\"]);\n\t} catch (err) {\n\t\treturn parseFailedFrom(err, sourceFile);\n\t}\n\n\treturn {\n\t\tdata: { environments, version: versionResult.data },\n\t\tsuccess: true,\n\t};\n}\n\nfunction parseVersion(raw: unknown): Result<\"6\", MigrateError> {\n\tconst found = typeof raw === \"string\" ? raw : String(raw);\n\tif (found !== \"6\") {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tfound,\n\t\t\t\tkind: \"unsupportedMantleStateVersion\",\n\t\t\t\tsupported: SUPPORTED_VERSIONS,\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: \"6\", success: true };\n}\n\nfunction isObjectPayload(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction describe(value: unknown): string {\n\tif (value === null) {\n\t\treturn \"null\";\n\t}\n\n\tif (Array.isArray(value)) {\n\t\treturn \"array\";\n\t}\n\n\treturn typeof value;\n}\n\nfunction expectObject(value: unknown): Record<string, unknown> {\n\tif (!isObjectPayload(value)) {\n\t\tthrow new TypeError(`expected object, got ${describe(value)}`);\n\t}\n\n\treturn value;\n}\n\nfunction expectArray(value: unknown): ReadonlyArray<unknown> {\n\tif (!Array.isArray(value)) {\n\t\tthrow new TypeError(`expected array, got ${describe(value)}`);\n\t}\n\n\treturn value;\n}\n\nfunction expectString(value: unknown, label: string): string {\n\tif (typeof value !== \"string\") {\n\t\tthrow new TypeError(`expected ${label} to be a string, got ${describe(value)}`);\n\t}\n\n\treturn value;\n}\n\nfunction stripNulls(value: unknown): unknown {\n\tif (value === null) {\n\t\treturn undefined;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\treturn value.map(stripNulls);\n\t}\n\n\tif (isObjectPayload(value)) {\n\t\treturn Object.fromEntries(\n\t\t\tObject.entries(value).map(([key, child]) => [key, stripNulls(child)]),\n\t\t);\n\t}\n\n\treturn value;\n}\n\nfunction parseResource(value: unknown): MantleResource {\n\tconst resource = expectObject(value);\n\tconst id = expectString(resource[\"id\"], \"id\");\n\tconst underscoreIndex = id.indexOf(\"_\");\n\tif (underscoreIndex <= 0 || underscoreIndex === id.length - 1) {\n\t\tthrow new Error(`expected id of form <kind>_<key>, got \"${id}\"`);\n\t}\n\n\tconst kind = id.slice(0, underscoreIndex);\n\tconst key = id.slice(underscoreIndex + 1);\n\n\tconst inputsObject = expectObject(resource[\"inputs\"]);\n\tconst inputs = stripNulls(inputsObject[kind]);\n\n\tconst outputsRaw = resource[\"outputs\"];\n\tconst outputs = isObjectPayload(outputsRaw) ? stripNulls(outputsRaw[kind]) : undefined;\n\n\tconst dependencies = expectArray(resource[\"dependencies\"]).map((dep) => {\n\t\treturn expectString(dep, \"dependency\");\n\t});\n\n\treturn { key, dependencies, inputs, kind, outputs };\n}\n\nfunction parseEnvironments(raw: unknown): Readonly<Record<string, ReadonlyArray<MantleResource>>> {\n\tconst environmentsObject = expectObject(raw);\n\treturn Object.fromEntries(\n\t\tObject.entries(environmentsObject).map(([name, value]) => [\n\t\t\tname,\n\t\t\texpectArray(value).map(parseResource),\n\t\t]),\n\t);\n}\n\nfunction parseFailedFrom(err: unknown, sourceFile: string): Result<MantleStateV6, MigrateError> {\n\tconst reason = err instanceof Error ? err.message : String(err);\n\treturn {\n\t\terr: { kind: \"stateParseFailed\", path: sourceFile, reason },\n\t\tsuccess: false,\n\t};\n}\n","import { stringifyYAML } from \"confbox\";\n\nimport type { Config } from \"../schema.ts\";\n\nconst IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\n/**\n * Inputs for {@link serializeConfig}. Format dispatch lives on\n * `configFormat` so a single function shape covers both TypeScript and\n * YAML output.\n */\nexport interface SerializeConfigOptions {\n\t/** Validated bedrock config to render. */\n\treadonly config: Config;\n\t/** Output format. TypeScript emits `defineConfig({...})`; YAML emits a `bedrock.config.yaml` body. */\n\treadonly configFormat: \"typescript\" | \"yaml\";\n}\n\n/**\n * Render a bedrock `Config` as the source text of a `bedrock.config.{ts,yaml}`\n * file the user can write straight to disk.\n *\n * The TypeScript branch emits a hand-authored object literal: tab-indented,\n * with double-quoted string values and unquoted keys for valid identifier\n * names (quoted otherwise). The YAML branch delegates to `stringifyYAML`,\n * which already drops `undefined`-valued properties so the output never\n * surfaces `null` or `~` for absent managed fields. Both shapes round-trip\n * through `loadConfig` cleanly.\n *\n * @param options - Render inputs.\n * @returns A UTF-8 source string ending with a trailing newline.\n */\nexport function serializeConfig(options: SerializeConfigOptions): string {\n\tif (options.configFormat === \"yaml\") {\n\t\treturn `${stringifyYAML(options.config)}\\n`;\n\t}\n\n\tconst body = renderObjectLiteral(options.config, 0);\n\treturn [\n\t\t'import { defineConfig } from \"@bedrock-rbx/core/config\";',\n\t\t\"\",\n\t\t`export default defineConfig(${body});`,\n\t\t\"\",\n\t].join(\"\\n\");\n}\n\nfunction renderValue(value: unknown, depth: number): string {\n\tif (Array.isArray(value)) {\n\t\treturn renderArrayLiteral(value, depth);\n\t}\n\n\tif (value !== null && typeof value === \"object\") {\n\t\treturn renderObjectLiteral(value, depth);\n\t}\n\n\tif (typeof value === \"string\") {\n\t\treturn JSON.stringify(value);\n\t}\n\n\treturn String(value);\n}\n\nfunction renderObjectLiteral(value: object, depth: number): string {\n\tconst entries = Object.entries(value).filter(([, fieldValue]) => fieldValue !== undefined);\n\tif (entries.length === 0) {\n\t\treturn \"{}\";\n\t}\n\n\tconst innerIndent = \"\\t\".repeat(depth + 1);\n\tconst closeIndent = \"\\t\".repeat(depth);\n\tconst lines = entries.map(([key, fieldValue]) => {\n\t\tconst renderedKey = IDENTIFIER_PATTERN.test(key) ? key : JSON.stringify(key);\n\t\treturn `${innerIndent}${renderedKey}: ${renderValue(fieldValue, depth + 1)}`;\n\t});\n\treturn `{\\n${lines.join(\",\\n\")}\\n${closeIndent}}`;\n}\n\nfunction renderArrayLiteral(value: ReadonlyArray<unknown>, depth: number): string {\n\tif (value.length === 0) {\n\t\treturn \"[]\";\n\t}\n\n\tconst innerIndent = \"\\t\".repeat(depth + 1);\n\tconst closeIndent = \"\\t\".repeat(depth);\n\tconst lines = value.map((item) => `${innerIndent}${renderValue(item, depth + 1)}`);\n\treturn `[\\n${lines.join(\",\\n\")}\\n${closeIndent}]`;\n}\n","import type { MigrationSummary, MigrationWarning } from \"./migration-report.ts\";\n\nconst ZERO_SUMMARY: MigrationSummary = {\n\tambiguousCount: 0,\n\tblockedCount: 0,\n\tdeferredCount: 0,\n\tinterpretiveCount: 0,\n};\n\n/**\n * Fold a `MigrationWarning` array into a `MigrationSummary` so the\n * report's aggregate counts are derived from the warning list rather\n * than maintained in parallel. Future warning-emitting slices thread\n * through this helper instead of having to remember to update the\n * summary at every emission site.\n *\n * @param warnings - Warnings to aggregate; the empty array yields a\n * zeroed summary.\n * @returns Per-kind counts.\n */\nexport function summarizeWarnings(warnings: ReadonlyArray<MigrationWarning>): MigrationSummary {\n\treturn warnings.reduce<MigrationSummary>((accumulator, warning) => {\n\t\treturn {\n\t\t\t...accumulator,\n\t\t\t[`${warning.kind}Count`]: accumulator[`${warning.kind}Count`] + 1,\n\t\t};\n\t}, ZERO_SUMMARY);\n}\n","import { join } from \"node:path\";\n\nimport { sha256Hex } from \"../core/kinds/hash.ts\";\nimport type { EnvironmentFoldResult } from \"../core/migrate/fold-environment.ts\";\nimport type { PassFoldEntry } from \"../core/migrate/fold-passes.ts\";\nimport type { ProductFoldEntry } from \"../core/migrate/fold-products.ts\";\nimport type { MigrationWarning } from \"../core/migrate/migration-report.ts\";\nimport { asSha256Hex, type ResourceKey, type Sha256Hex } from \"../types/ids.ts\";\n\n/**\n * Result of walking each environment's pass and product entries and\n * recomputing the SHA-256 digest of every locale-keyed icon path from\n * disk. `passHashesByEnvironment` and `productHashesByEnvironment` carry\n * the recomputed digests keyed by environment then by resource key; the\n * inner record mirrors `GamePassDesiredState.iconFileHashes` /\n * `DeveloperProductDesiredState.iconFileHashes`. `warnings` carries one\n * `kind: \"ambiguous\"` warning per resource whose icon could not be read so\n * the caller can fall back to the Mantle-recorded hashes without losing\n * the diagnostic.\n */\nexport interface IconHashRecomputation {\n\t/** Recomputed pass-icon digests keyed by environment then by pass key. */\n\treadonly passHashesByEnvironment: ReadonlyMap<\n\t\tstring,\n\t\tReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>\n\t>;\n\t/** Recomputed product-icon digests keyed by environment then by product key. */\n\treadonly productHashesByEnvironment: ReadonlyMap<\n\t\tstring,\n\t\tReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>\n\t>;\n\t/** One ambiguous warning per resource whose icon could not be read. */\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\n/** Inputs for {@link recomputeIconHashes}. */\ninterface RecomputeIconHashesInputs {\n\t/** Per-environment fold results carrying pass and product entries to walk. */\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\t/** Reads file bytes; same shape as `MigrateMantleStateDeps.readFile`. */\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\t/** Directory the state file lives in; relative icon paths resolve against it. */\n\treadonly stateFileDirectory: string;\n}\n\ninterface IconWalkEntry {\n\treadonly key: ResourceKey;\n\treadonly iconPath: string;\n\treadonly mantlePath: string;\n}\n\ninterface IconWalkInputs {\n\treadonly entries: ReadonlyArray<IconWalkEntry>;\n\treadonly environmentName: string;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\treadonly stateFileDirectory: string;\n}\n\ninterface IconWalkResult {\n\treadonly perKey: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>;\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\ninterface AmbiguousIconWarningInputs {\n\treadonly environmentName: string;\n\treadonly mantlePath: string;\n\treadonly resolvedPath: string;\n}\n\ninterface WalkEnvironmentInputs {\n\treadonly environmentName: string;\n\treadonly folded: EnvironmentFoldResult;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\treadonly stateFileDirectory: string;\n}\n\ninterface WalkEnvironmentResult {\n\treadonly passHashes: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>;\n\treadonly productHashes: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>;\n\treadonly warnings: ReadonlyArray<MigrationWarning>;\n}\n\n/**\n * Walk each environment's folded pass and product entries, resolve the\n * locale-keyed icon paths against `stateFileDirectory`, read the bytes via\n * the injected `readFile`, and compute the SHA-256 hex digest. Files that\n * cannot be read surface as `ambiguous` `MigrationWarning`s with the\n * environment-prefixed `mantlePath` and a hint pointing at the resolved\n * path; the caller carries the Mantle-recorded hashes forward as a\n * fallback. Products without an icon partner are silently skipped.\n *\n * @param inputs - Per-environment fold results plus I/O dependencies.\n * @returns Per-environment recomputed pass and product hashes plus\n * accumulated ambiguous warnings.\n */\nexport async function recomputeIconHashes(\n\tinputs: RecomputeIconHashesInputs,\n): Promise<IconHashRecomputation> {\n\tconst walked = await Promise.all(\n\t\t[...inputs.folds.entries()].map(async ([environment, folded]) => {\n\t\t\tconst result = await walkEnvironment({\n\t\t\t\tenvironmentName: environment,\n\t\t\t\tfolded,\n\t\t\t\treadFile: inputs.readFile,\n\t\t\t\tstateFileDirectory: inputs.stateFileDirectory,\n\t\t\t});\n\t\t\treturn [environment, result] as const;\n\t\t}),\n\t);\n\n\treturn collectRecomputation(walked);\n}\n\nfunction collectRecomputation(\n\twalked: ReadonlyArray<readonly [string, WalkEnvironmentResult]>,\n): IconHashRecomputation {\n\treturn {\n\t\tpassHashesByEnvironment: new Map(\n\t\t\twalked.map(([environment, walk]) => [environment, walk.passHashes]),\n\t\t),\n\t\tproductHashesByEnvironment: new Map(\n\t\t\twalked.map(([environment, walk]) => [environment, walk.productHashes]),\n\t\t),\n\t\twarnings: walked.flatMap(([, walk]) => walk.warnings),\n\t};\n}\n\nfunction passWalkEntry(entry: PassFoldEntry): IconWalkEntry {\n\treturn {\n\t\tkey: entry.key,\n\t\ticonPath: entry.entry.icon[\"en-us\"],\n\t\tmantlePath: entry.mantlePath,\n\t};\n}\n\nfunction productWalkEntries(entry: ProductFoldEntry): ReadonlyArray<IconWalkEntry> {\n\tif (entry.entry.icon === undefined) {\n\t\treturn [];\n\t}\n\n\treturn [\n\t\t{\n\t\t\tkey: entry.key,\n\t\t\ticonPath: entry.entry.icon[\"en-us\"],\n\t\t\tmantlePath: entry.mantlePath,\n\t\t},\n\t];\n}\n\nasync function tryRecomputeHash(\n\treadFile: (path: string) => Promise<Uint8Array>,\n\tpath: string,\n): Promise<Sha256Hex | undefined> {\n\ttry {\n\t\tconst bytes = await readFile(path);\n\t\treturn asSha256Hex(await sha256Hex(bytes));\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction buildAmbiguousIconWarning(inputs: AmbiguousIconWarningInputs): MigrationWarning {\n\treturn {\n\t\thint: `Could not read icon file at ${inputs.resolvedPath}; verify the file's location relative to the state file or correct the icon entry before re-running.`,\n\t\tkind: \"ambiguous\",\n\t\tmantlePath: `${inputs.environmentName}.${inputs.mantlePath}`,\n\t};\n}\n\nasync function walkIconEntries(inputs: IconWalkInputs): Promise<IconWalkResult> {\n\tconst perKey = new Map<ResourceKey, Record<\"en-us\", Sha256Hex>>();\n\tconst warnings: Array<MigrationWarning> = [];\n\tfor (const entry of inputs.entries) {\n\t\tconst resolved = join(inputs.stateFileDirectory, entry.iconPath);\n\t\tconst recomputed = await tryRecomputeHash(inputs.readFile, resolved);\n\t\tif (recomputed === undefined) {\n\t\t\twarnings.push(\n\t\t\t\tbuildAmbiguousIconWarning({\n\t\t\t\t\tenvironmentName: inputs.environmentName,\n\t\t\t\t\tmantlePath: entry.mantlePath,\n\t\t\t\t\tresolvedPath: resolved,\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tperKey.set(entry.key, { \"en-us\": recomputed });\n\t\t}\n\t}\n\n\treturn { perKey, warnings };\n}\n\nasync function walkEnvironment(inputs: WalkEnvironmentInputs): Promise<WalkEnvironmentResult> {\n\tconst passWalk = await walkIconEntries({\n\t\tentries: inputs.folded.passes.map(passWalkEntry),\n\t\tenvironmentName: inputs.environmentName,\n\t\treadFile: inputs.readFile,\n\t\tstateFileDirectory: inputs.stateFileDirectory,\n\t});\n\tconst productWalk = await walkIconEntries({\n\t\tentries: inputs.folded.products.flatMap(productWalkEntries),\n\t\tenvironmentName: inputs.environmentName,\n\t\treadFile: inputs.readFile,\n\t\tstateFileDirectory: inputs.stateFileDirectory,\n\t});\n\treturn {\n\t\tpassHashes: passWalk.perKey,\n\t\tproductHashes: productWalk.perKey,\n\t\twarnings: [...passWalk.warnings, ...productWalk.warnings],\n\t};\n}\n","import type { Result } from \"@bedrock-rbx/ocale\";\n\nimport { readFile as nodeReadFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nimport { buildState } from \"../core/migrate/build-state.ts\";\nimport { factorizeEnvironments } from \"../core/migrate/factorize-environments.ts\";\nimport { type EnvironmentFoldResult, foldEnvironment } from \"../core/migrate/fold-environment.ts\";\nimport type {\n\tMigrateError,\n\tMigrationReport,\n\tMigrationWarning,\n} from \"../core/migrate/migration-report.ts\";\nimport { parseState } from \"../core/migrate/parse-state.ts\";\nimport { serializeConfig } from \"../core/migrate/serialize-config.ts\";\nimport { summarizeWarnings } from \"../core/migrate/summarize-warnings.ts\";\nimport type { MantleStateV6 } from \"../core/migrate/types.ts\";\nimport { type Config, validateConfig } from \"../core/schema.ts\";\nimport type { BedrockState } from \"../core/state.ts\";\nimport type { ResourceKey, Sha256Hex } from \"../types/ids.ts\";\nimport { type IconHashRecomputation, recomputeIconHashes } from \"./recompute-icon-hashes.ts\";\n\ntype ConfigFormat = \"typescript\" | \"yaml\";\n\nconst FILE_MISSING_CODES = new Set([\"ENOENT\"]);\n\n/**\n * Inputs for `migrateMantleState`. The state file is read via\n * `readFile` (defaults to `node:fs/promises.readFile`) so callers can\n * inject in-memory fixtures from tests and the JSDoc `@example` block\n * stays self-contained.\n *\n * `configFormat` selects the output shape: `\"typescript\"` emits a\n * `bedrock.config.ts` with `defineConfig({...})`; `\"yaml\"` emits a\n * `bedrock.config.yaml` body. Both shapes round-trip through\n * `loadConfig` cleanly.\n */\nexport interface MigrateMantleStateDeps {\n\t/**\n\t * Output format for the emitted bedrock config file. `\"typescript\"`\n\t * produces a `defineConfig({...})` module; `\"yaml\"` produces a YAML\n\t * body whose keys match the `Config` schema.\n\t */\n\treadonly configFormat: ConfigFormat;\n\t/**\n\t * Environment in the input state file whose resolved values seed\n\t * the root config. Required when the state file declares more than\n\t * one environment; ignored when only one environment is present.\n\t */\n\treadonly primaryEnvironment?: string;\n\t/**\n\t * Reads file bytes; defaults to `node:fs/promises.readFile`. Kept\n\t * `Uint8Array`-typed to match `deploy`, `buildDesired`, and\n\t * `buildDefaultRegistry`. UTF-8 decoding happens inside the migrator\n\t * before YAML parsing.\n\t */\n\treadonly readFile?: (path: string) => Promise<Uint8Array>;\n\t/** Absolute path to the `.mantle-state.yml` file to migrate. */\n\treadonly stateFilePath: string;\n}\n\ninterface FinalizeReportInputs {\n\treadonly config: Config;\n\treadonly configFormat: ConfigFormat;\n\treadonly factorizeWarnings: ReadonlyArray<MigrationWarning>;\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\treadonly iconRecomputation: IconHashRecomputation;\n}\n\ninterface AssembleReportInputs {\n\treadonly configFormat: ConfigFormat;\n\treadonly primaryEnvironment: string | undefined;\n\treadonly readFile: (path: string) => Promise<Uint8Array>;\n\treadonly state: MantleStateV6;\n\treadonly stateFilePath: string;\n}\n\n/**\n * Read a Mantle state file and produce a `MigrationReport` containing a\n * bedrock config, per-environment `BedrockState`s, and a structured list\n * of fields that did not migrate verbatim.\n *\n * Skeleton: handles single-environment or multi-environment states with\n * universe, place, and game-pass resources. The primary environment\n * auto-picks when only one environment is present; multi-environment\n * inputs without an explicit `primaryEnvironment` return\n * `Err({ kind: \"primaryEnvironmentRequired\", available })` so the\n * migrator never silently picks a winner. Future slices add social\n * links and the deferred / blocked warning categories.\n *\n * @param deps - Inputs for the migration.\n * @returns `Ok` with a `MigrationReport` on success, or `Err` with a\n * discriminated `MigrateError` on failure.\n * @rejects Re-thrown `readFile` failure when the underlying error code is\n * not in the recognized \"missing file\" set; surfaced so callers see\n * permission or filesystem outages instead of having them coerced to\n * `stateFileNotFound`.\n * @example\n *\n * ```ts\n * import { migrateMantleState } from \"@bedrock-rbx/core\";\n *\n * const yaml = [\n * 'version: \"6\"',\n * \"environments:\",\n * \" production:\",\n * \" - id: experience_singleton\",\n * \" inputs:\",\n * \" experience:\",\n * \" groupId: ~\",\n * \" outputs:\",\n * \" experience:\",\n * \" assetId: 6031475575\",\n * \" startPlaceId: 17613681043\",\n * \" dependencies: []\",\n * \"\",\n * ].join(\"\\n\");\n *\n * async function readFile(): Promise<Uint8Array> {\n * return new TextEncoder().encode(yaml);\n * }\n *\n * return migrateMantleState({\n * configFormat: \"typescript\",\n * readFile,\n * stateFilePath: \".mantle-state.yml\",\n * }).then((result) => {\n * expect(result.success).toBeTrue();\n * if (result.success) {\n * expect(result.data.config.universe?.universeId).toBe(\"6031475575\");\n * }\n * });\n * ```\n */\nexport async function migrateMantleState(\n\tdeps: MigrateMantleStateDeps,\n): Promise<Result<MigrationReport, MigrateError>> {\n\tconst readFile = deps.readFile ?? nodeReadFile;\n\n\tlet bytes: Uint8Array;\n\ttry {\n\t\tbytes = await readFile(deps.stateFilePath);\n\t} catch (err) {\n\t\tif (isFileMissing(err)) {\n\t\t\treturn {\n\t\t\t\terr: { kind: \"stateFileNotFound\", path: deps.stateFilePath },\n\t\t\t\tsuccess: false,\n\t\t\t};\n\t\t}\n\n\t\tthrow err;\n\t}\n\n\tconst raw = new TextDecoder(\"utf-8\").decode(bytes);\n\tconst parsed = parseState(raw, deps.stateFilePath);\n\tif (!parsed.success) {\n\t\treturn parsed;\n\t}\n\n\treturn assembleReport({\n\t\tconfigFormat: deps.configFormat,\n\t\tprimaryEnvironment: deps.primaryEnvironment,\n\t\treadFile,\n\t\tstate: parsed.data,\n\t\tstateFilePath: deps.stateFilePath,\n\t});\n}\n\nconst EMPTY_HASHES: ReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>> = new Map();\n\ninterface BuildStatesByEnvironmentInputs {\n\treadonly folds: ReadonlyMap<string, EnvironmentFoldResult>;\n\treadonly passHashesByEnvironment: ReadonlyMap<\n\t\tstring,\n\t\tReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>\n\t>;\n\treadonly productHashesByEnvironment: ReadonlyMap<\n\t\tstring,\n\t\tReadonlyMap<ResourceKey, Record<\"en-us\", Sha256Hex>>\n\t>;\n}\n\nfunction buildStatesByEnvironment(\n\tinputs: BuildStatesByEnvironmentInputs,\n): Readonly<Record<string, BedrockState>> {\n\treturn Object.fromEntries(\n\t\t[...inputs.folds.entries()].map(([name, folded]): [string, BedrockState] => {\n\t\t\treturn [\n\t\t\t\tname,\n\t\t\t\tbuildState({\n\t\t\t\t\tenvironment: name,\n\t\t\t\t\tfolded,\n\t\t\t\t\tpassIconHashesByKey: inputs.passHashesByEnvironment.get(name) ?? EMPTY_HASHES,\n\t\t\t\t\tproductIconHashesByKey:\n\t\t\t\t\t\tinputs.productHashesByEnvironment.get(name) ?? EMPTY_HASHES,\n\t\t\t\t}),\n\t\t\t];\n\t\t}),\n\t);\n}\n\nfunction prefixMantlePath(warning: MigrationWarning, environmentName: string): MigrationWarning {\n\treturn { ...warning, mantlePath: `${environmentName}.${warning.mantlePath}` };\n}\n\nfunction collectFoldWarnings(\n\tfolds: ReadonlyMap<string, EnvironmentFoldResult>,\n): ReadonlyArray<MigrationWarning> {\n\treturn [...folds.entries()].flatMap(([name, fold]) => {\n\t\treturn fold.warnings.map((warning) => prefixMantlePath(warning, name));\n\t});\n}\n\nfunction buildReport(inputs: FinalizeReportInputs, validated: Config): MigrationReport {\n\tconst {\n\t\tpassHashesByEnvironment,\n\t\tproductHashesByEnvironment,\n\t\twarnings: iconWarnings,\n\t} = inputs.iconRecomputation;\n\tconst warnings = [\n\t\t...collectFoldWarnings(inputs.folds),\n\t\t...inputs.factorizeWarnings,\n\t\t...iconWarnings,\n\t];\n\treturn {\n\t\tconfig: validated,\n\t\tconfigFileContent: serializeConfig({\n\t\t\tconfig: validated,\n\t\t\tconfigFormat: inputs.configFormat,\n\t\t}),\n\t\tstatesByEnvironment: buildStatesByEnvironment({\n\t\t\tfolds: inputs.folds,\n\t\t\tpassHashesByEnvironment,\n\t\t\tproductHashesByEnvironment,\n\t\t}),\n\t\tsummary: summarizeWarnings(warnings),\n\t\twarnings,\n\t};\n}\n\nfunction finalizeReport(inputs: FinalizeReportInputs): Result<MigrationReport, MigrateError> {\n\tconst validated = validateConfig(inputs.config, \"<migrate-mantle-state>\");\n\tif (!validated.success) {\n\t\treturn {\n\t\t\terr: {\n\t\t\t\tcause: validated.err,\n\t\t\t\tkind: \"internalError\",\n\t\t\t\treason: \"migrator emitted a config that failed validateConfig\",\n\t\t\t},\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: buildReport(inputs, validated.data), success: true };\n}\n\nasync function assembleReport(\n\tinputs: AssembleReportInputs,\n): Promise<Result<MigrationReport, MigrateError>> {\n\tconst available = Object.keys(inputs.state.environments);\n\tconst folds: ReadonlyMap<string, EnvironmentFoldResult> = new Map(\n\t\tavailable.map((name) => [name, foldEnvironment(inputs.state.environments[name] ?? [])]),\n\t);\n\tconst factorized = factorizeEnvironments({\n\t\tfolds,\n\t\tprimaryEnvironment: inputs.primaryEnvironment,\n\t});\n\tif (!factorized.success) {\n\t\treturn factorized;\n\t}\n\n\tconst iconRecomputation = await recomputeIconHashes({\n\t\tfolds,\n\t\treadFile: inputs.readFile,\n\t\tstateFileDirectory: dirname(inputs.stateFilePath),\n\t});\n\treturn finalizeReport({\n\t\tconfig: factorized.data.config,\n\t\tconfigFormat: inputs.configFormat,\n\t\tfactorizeWarnings: factorized.data.warnings,\n\t\tfolds,\n\t\ticonRecomputation,\n\t});\n}\n\nfunction isFileMissing(err: unknown): boolean {\n\treturn (\n\t\ttypeof err === \"object\" &&\n\t\terr !== null &&\n\t\t\"code\" in err &&\n\t\ttypeof err.code === \"string\" &&\n\t\tFILE_MISSING_CODES.has(err.code)\n\t);\n}\n"],"mappings":"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"}