@objectstack/core 17.2.0 → 17.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1450 -0
- package/dist/index.cjs +862 -302
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1195 -115
- package/dist/index.d.ts +1195 -115
- package/dist/index.js +822 -291
- package/dist/index.js.map +1 -1
- package/package.json +8 -4
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/plugin-order.ts","../src/hook-dispatch.ts","../src/kernel-base.ts","../src/logger.ts","../src/kernel.ts","../src/security/plugin-config-validator.ts","../src/security/plugin-artifact-signature.ts","../src/plugin-loader.ts","../src/utils/env.ts","../src/fallbacks/memory-cache.ts","../src/fallbacks/memory-queue.ts","../src/fallbacks/memory-i18n.ts","../src/metadata-service-contract.ts","../src/fallbacks/memory-metadata.ts","../src/fallbacks/memory-job.ts","../src/fallbacks/authored-translation-sync.ts","../src/fallbacks/index.ts","../src/plugin-registration.ts","../src/timeout-guard.ts","../src/lite-kernel.ts","../src/qa/index.ts","../src/qa/runner.ts","../src/qa/http-adapter.ts","../src/security/plugin-signature-verifier.ts","../src/security/plugin-permission-enforcer.ts","../src/security/permission-manager.ts","../src/security/sandbox-runtime.ts","../src/security/security-scanner.ts","../src/security/api-key.ts","../src/security/resolve-authz-context.ts","../src/security/grant-validity.ts","../src/security/posture-ladder.ts","../src/security/row-active.ts","../src/security/assemble-execution-context.ts","../src/security/auth-gate.ts","../src/security/anonymous-deny.ts","../src/security/admin-standing-surface.ts","../src/security/audience-binding-suggestion-status.ts","../src/security/operation-private-keys.ts","../src/utils/datetime.ts","../src/utils/bulk-write.ts","../src/utils/internal-write-response.ts","../src/utils/migration-journal.ts","../src/utils/filter-tokens.ts","../src/utils/temporal-comparand.ts","../src/utils/record-not-found.ts","../src/health-monitor.ts","../src/hot-reload.ts","../src/dependency-resolver.ts","../src/namespace-resolver.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * @objectstack/core\n * \n * Core runtime for ObjectStack microkernel architecture.\n * Provides plugin system, dependency injection, and lifecycle management.\n */\n\nexport * from './kernel-base.js';\nexport * from './kernel.js';\nexport * from './plugin-order.js';\nexport * from './lite-kernel.js';\nexport * from './types.js';\nexport * from './logger.js';\nexport * from './plugin-loader.js';\n// `./api-registry.js` + `./api-registry-plugin.js` were RETIRED in #4939\n// (ADR-0049 enforce-or-remove). `createApiRegistryPlugin()` registered an\n// `api-registry` service that only `packages/core/examples/` ever composed —\n// no runtime, CLI or example app mounted it, and a real 47-plugin boot had no\n// such service — so the ~500-line `ApiRegistry` and the whole\n// `ApiEndpointRegistration` schema family it served were zero-execution.\n// Declarative endpoints have ONE shape now: `ApiEndpointSchema`\n// (`@objectstack/spec/api`), whose executor is tracked by #5040.\nexport * as QA from './qa/index.js';\n\n// Export security utilities\nexport * from './security/index.js';\n\n// Export environment utilities\nexport * from './utils/env.js';\n\n// Export timezone-aware calendar utilities (ADR-0053 Phase 2)\nexport * from './utils/datetime.js';\n\n// Export the shared batched-write helper (framework#2678)\nexport * from './utils/bulk-write.js';\n\n// Export the shared write-response `internal: true` strip (#7823, #8497) — the\n// ONE helper every write mouth that answers an external caller runs its\n// records through. It lives here, on the floor all three transports share,\n// because the mouths are not all on the protocol class: `rest` and `mcp` both\n// reach the engine directly and neither depends on `@objectstack/metadata-\n// protocol` (which re-exports these two names unchanged).\nexport * from './utils/internal-write-response.js';\n\n// Export the migration-journal runner (ADR-0119 D2, #4617) — chunk-atomic\n// migrations with durable recovery, plus the shared `engineCanRollBack` gate\n// that `@objectstack/metadata-protocol`'s atomic `batchData` also uses.\nexport * from './utils/migration-journal.js';\n\n// Export the runtime filter-placeholder resolver (framework#3582)\nexport * from './utils/filter-tokens.js';\n\n// [#8690] Can a temporal column's storage rule read this comparand? The VALUE\n// half of the field-typed judgement behind the engine's temporal-comparand door\n// and the analytics raw-SQL decline — one rule, two packages that do not depend\n// on each other.\nexport * from './utils/temporal-comparand.js';\n\n// Export the shared single-record 404 (#4435/#5138, moved down here in #7867) —\n// the one `RECORD_NOT_FOUND` envelope `protocol.updateData`/`deleteData`,\n// `callData`'s ObjectQL fallback and the engine's own by-id write gate answer\n// with. `@objectstack/metadata-protocol` re-exports it from its original home.\nexport * from './utils/record-not-found.js';\n\n// Export in-memory fallbacks for core-criticality services\nexport * from './fallbacks/index.js';\n\n// [#7378] The IMetadataService register/read argument contract (three-cell\n// maintainer ruling, 2026-08-12) — shared by every shipped implementation so\n// the refusals and the canonical type fold have one home instead of three.\nexport * from './metadata-service-contract.js';\n\n// Export Phase 2 components - Advanced lifecycle management\nexport * from './health-monitor.js';\nexport * from './hot-reload.js';\nexport * from './dependency-resolver.js';\n\n// Export Phase 3 components - Package lifecycle management\nexport * from './namespace-resolver.js';\n\n// Re-export contracts from @objectstack/spec for backward compatibility\nexport type { \n Logger,\n IHttpServer,\n IHttpRequest,\n IHttpResponse,\n RouteHandler,\n Middleware,\n HttpResponseObservation,\n HttpResponseObserver,\n IDataEngine,\n IObjectQLEngine,\n EngineSchemaRegistryView,\n EngineTransactionOptions,\n EngineTransactionInfo,\n IDataDriver,\n} from '@objectstack/spec/contracts';\n// The reserved route label for unrouted requests on the `afterResponse`\n// observation seam (#9835) — a VALUE, so it rides beside the type block above.\nexport { UNMATCHED_ROUTE_PATTERN } from '@objectstack/spec/contracts';\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Plugin ordering + init-service contract (ADR-0116, #4131).\n *\n * The kernel resolves BOTH init and start order from the plugin dependency\n * graph, so `kernel.use()` registration order proves nothing. Twice a plugin\n * relied on list position anyway and shipped a boot that dies inside init —\n * the first cut of DefaultDatasourcePlugin (started after boot schema-sync;\n * server with no tables) and AppPlugin (#4085: `manifest` grabbed in init\n * before ObjectQLPlugin registered it). Both times the fix existed only as a\n * convention: put the plugin in the right slot, write a comment. This module\n * is the enforced form of that contract, shared by ObjectKernel and\n * LiteKernel so there is exactly one ordering semantic:\n *\n * - `dependencies` — hard: hoisted ahead, missing ⇒ boot error (unchanged).\n * - `optionalDependencies` — order-if-present: hoisted ahead when composed,\n * silently skipped when absent. For plugins that DEGRADE without the\n * dependency but must never init before it (AppPlugin on an engine-less\n * metadata-only kernel).\n * - `requiresServices` — services a plugin resolves SYNCHRONOUSLY during\n * `init()`. Validated before Phase 1 (provable misordering ⇒ named error\n * instead of a crash inside init) and again immediately before each init\n * (authoritative: the service is either registered by now or init dies).\n * - `providesServices` — services a plugin's `init()` UNCONDITIONALLY\n * registers. Powers the pre-Phase-1 check and the named diagnostics.\n * Declare only unconditional registrations: a conditional service (e.g.\n * one gated behind an option) would indict this plugin for orderings it\n * cannot actually satisfy.\n *\n * Declaring is NOT voluntary (#4471). Everything above can only enforce what\n * a plugin declares — a plugin that resolves `getService('X')` during init()\n * and declares nothing was invisible to all of it, failing only under\n * unlucky composition orders (#4085, and #4420 at data-consistency cost).\n * `scripts/check-init-service-contract.mjs` (CI: `check:init-service-contract`)\n * closes that gap: it walks every plugin's init() call graph and errors on\n * any init-reachable getService of a workspace-provided service that no\n * declaration covers. Best-effort tolerance is declared IN the plugin via\n * `optionalDependencies`, never exempted in the checker.\n */\n\n/**\n * The ordering-relevant surface of a kernel plugin. Structural on purpose:\n * ObjectKernel sorts `PluginMetadata`, LiteKernel sorts `Plugin`, and both\n * satisfy this shape.\n */\nexport interface OrderablePlugin {\n name: string;\n /** Hard dependencies — hoisted ahead; missing ⇒ boot error. */\n dependencies?: string[];\n /** Soft dependencies — hoisted ahead when composed, skipped when absent. */\n optionalDependencies?: string[];\n /** Services resolved synchronously during init(). */\n requiresServices?: string[];\n /** Services init() unconditionally registers. */\n providesServices?: string[];\n}\n\n/**\n * Topologically order plugins: every plugin's `dependencies` (throw when\n * missing) and `optionalDependencies` (skip when missing) init before it.\n * Insertion order is preserved for plugins with no edges between them.\n * Cycles through either edge kind throw — an optional dependency is a real\n * edge whenever both sides are composed.\n */\nexport function resolvePluginOrder<P extends OrderablePlugin>(plugins: Map<string, P>): P[] {\n const resolved: P[] = [];\n const visited = new Set<string>();\n const visiting = new Set<string>();\n\n const visit = (pluginName: string) => {\n if (visited.has(pluginName)) return;\n\n if (visiting.has(pluginName)) {\n throw new Error(`[Kernel] Circular dependency detected: ${pluginName}`);\n }\n\n const plugin = plugins.get(pluginName);\n if (!plugin) {\n throw new Error(`[Kernel] Plugin '${pluginName}' not found`);\n }\n\n visiting.add(pluginName);\n\n for (const dep of plugin.dependencies ?? []) {\n if (!plugins.has(dep)) {\n throw new Error(\n `[Kernel] Dependency '${dep}' not found for plugin '${pluginName}'`\n );\n }\n visit(dep);\n }\n for (const dep of plugin.optionalDependencies ?? []) {\n if (plugins.has(dep)) visit(dep);\n }\n\n visiting.delete(pluginName);\n visited.add(pluginName);\n resolved.push(plugin);\n };\n\n for (const pluginName of plugins.keys()) {\n visit(pluginName);\n }\n\n return resolved;\n}\n\n/**\n * Pre-Phase-1 check: walk the resolved order and prove no plugin requires a\n * service whose only declared provider initializes AFTER it. A violation is\n * the exact #4085 class — misplaced composition — reported as a named,\n * structural boot error BEFORE any init side effects, instead of a bare\n * \"Service not found\" thrown from inside the victim's init.\n *\n * Deliberately does NOT fail when a required service has no declared provider\n * and is not yet registered: an earlier plugin may register it without\n * declaring `providesServices`. That case is settled authoritatively by\n * {@link assertInitServiceRequirements} immediately before the requiring\n * plugin's init runs.\n */\nexport function validateInitServiceContract<P extends OrderablePlugin>(\n ordered: P[],\n isServiceRegistered: (name: string) => boolean,\n): void {\n const providerSlot = new Map<string, { plugin: string; slot: number }>();\n ordered.forEach((plugin, slot) => {\n for (const service of plugin.providesServices ?? []) {\n if (!providerSlot.has(service)) {\n providerSlot.set(service, { plugin: plugin.name, slot });\n }\n }\n });\n\n const violations: string[] = [];\n ordered.forEach((plugin, slot) => {\n for (const service of plugin.requiresServices ?? []) {\n if (isServiceRegistered(service)) continue;\n const provider = providerSlot.get(service);\n if (provider && provider.slot > slot) {\n violations.push(\n `'${plugin.name}' requires service '${service}' during init, but '${service}' is ` +\n `provided by '${provider.plugin}', which initializes later (slot ${provider.slot} vs ${slot}). ` +\n `Registration order is not a contract — declare '${provider.plugin}' in ` +\n `'${plugin.name}'.dependencies (hard) or .optionalDependencies (order-if-present) ` +\n `so the kernel hoists it.`\n );\n }\n }\n });\n\n if (violations.length > 0) {\n throw new Error(\n `[Kernel] Plugin ordering contract violated (#4131):\\n - ${violations.join('\\n - ')}`\n );\n }\n}\n\n/**\n * Diagnosis suffix for a getService miss that happens WHILE a plugin's\n * init() is running: names the initializing plugin and — when a composed\n * plugin declares the service — the provider and the directive to declare\n * the ordering. Returns '' when no plugin is initializing, so non-boot\n * error messages stay byte-identical. Shared by both kernels.\n */\nexport function describeInitOrderFault(\n currentlyInitializing: string | undefined,\n plugins: Iterable<OrderablePlugin>,\n serviceName: string,\n): string {\n if (!currentlyInitializing) return '';\n let providerHint = '';\n for (const plugin of plugins) {\n if (plugin.providesServices?.includes(serviceName)) {\n providerHint = ` '${serviceName}' is provided by composed plugin '${plugin.name}', which has ` +\n `not initialized yet — declare it in the requiring plugin's dependencies/optionalDependencies.`;\n break;\n }\n }\n return ` (while plugin '${currentlyInitializing}' was initializing — a composition/` +\n `ordering fault, #4131.${providerHint})`;\n}\n\n/**\n * Just-before-init check: every service in `requiresServices` must be\n * registered at the moment the plugin's init() is about to run. At this\n * point the verdict is authoritative — Phase 1 runs sequentially, so a\n * service absent now is absent for this init, and the init would die on a\n * bare \"Service not found\" anyway. This turns that crash into a named\n * composition error.\n */\nexport function assertInitServiceRequirements(\n plugin: OrderablePlugin,\n isServiceRegistered: (name: string) => boolean,\n): void {\n for (const service of plugin.requiresServices ?? []) {\n if (isServiceRegistered(service)) continue;\n throw new Error(\n `[Kernel] Plugin '${plugin.name}' requires service '${service}' at init, but no such ` +\n `service is registered at this point of the boot. No composed plugin that initializes ` +\n `earlier provides it — compose a provider (and, if it initializes later without declaring ` +\n `'${service}' in providesServices, order it ahead via this plugin's dependencies/` +\n `optionalDependencies) (#4131).`\n );\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Shared lifecycle-hook dispatchers (#5282).\n *\n * ## Why this module exists\n *\n * `ObjectKernel` does not extend `ObjectKernelBase` — it is a standalone\n * production kernel with its own `plugins`/`services`/`hooks`/`state`/`logger`\n * fields, and only `LiteKernel` extends the base. Making it inherit was\n * considered and **rejected** (#5282, option A: an inheritance refactor of a\n * 800-line production kernel, risk out of proportion to the benefit). What was\n * ruled instead is this module (option B): the two *dispatch flavours* become\n * module-level functions both kernels call, so a change to how a hook is\n * dispatched is written once and lands on both kernels.\n *\n * Before this, dispatch existed twice with no shared code path — the base's\n * `triggerHook` / `triggerHookOrThrow` / `context.trigger` on one side, and\n * `ObjectKernel`'s private `triggerShutdownHookIsolating` / `context.trigger`\n * on the other. The two isolating loops printed the same log line **because\n * someone typed it twice**. That hand-mirroring is the structural seam three\n * consecutive bugs grew on, every one of them the same shape — one hook name\n * meaning opposite things on the two kernels:\n *\n * - #5170 — `kernel:ready`: LiteKernel swallowed a throwing handler while\n * ObjectKernel failed the boot.\n * - #5257 — `kernel:bootstrapped` / `kernel:listening`: same, and the ugliest\n * specimen — `HonoServerPlugin` awaits `server.listen(port)` inside\n * `kernel:listening`, so on LiteKernel a failed listen was swallowed and\n * the process printed \"✅ Bootstrap complete\" with nothing listening.\n * - #5274 — `kernel:shutdown`, in the opposite direction: ObjectKernel's\n * propagating dispatch let one bad handler skip every `destroy()` and\n * `process.exit(1)`.\n *\n * The **storage** is deliberately still two maps (out of the ruling's scope);\n * only the dispatch is shared. The paired-pin gate\n * (`scripts/check-kernel-hook-pairs.mjs`, option C of the same ruling) covers\n * the residue: every `kernel:*` hook dispatched in `packages/core/src` must\n * carry a named pin in BOTH `kernel.test.ts` and `lite-kernel.test.ts`, so a\n * fifth lifecycle hook cannot arrive paired on one kernel only.\n *\n * ## Choosing a flavour\n *\n * The choice is per hook, not per kernel, and it is a judgement recorded at\n * each dispatch site:\n *\n * - **Boot path** (`kernel:ready`, `kernel:bootstrapped`, `kernel:listening`)\n * ⇒ {@link dispatchHookPropagating}. Everything dispatched before\n * \"✅ Bootstrap complete\" is a precondition of that claim; swallowing a\n * throw there does not rescue the boot, it only hides the failure behind a\n * process reporting success.\n * - **Teardown path** (`kernel:shutdown`) ⇒ {@link dispatchHookIsolating}.\n * There is no \"refuse to proceed\" left to buy: what is queued behind a\n * failing handler is the rest of the cleanup — the other subscribers, then\n * each plugin's `destroy()` — which is what flushes buffers, closes\n * connections and releases locks.\n *\n * ⛔ Internal module: not exported from `packages/core/src/index.ts`. These are\n * kernel internals, not a public dispatch API.\n */\n\n/** A registered lifecycle-hook handler, exactly as `PluginContext.hook` stores it. */\nexport type HookHandler = (...args: any[]) => void | Promise<void>;\n\n/**\n * The slice of the logger contract a dispatcher uses. Structural on purpose:\n * both `ObjectLogger` (what `ObjectKernel` holds) and the spec `Logger`\n * contract (what `ObjectKernelBase` holds) satisfy it without either kernel\n * importing the other's logger type.\n */\nexport interface HookDispatchLogger {\n debug(message: string, meta?: Record<string, any>): void;\n error(message: string, error?: Error, meta?: Record<string, any>): void;\n}\n\n/**\n * The trace line every *logged* dispatch emits before running handlers.\n *\n * Kept in one place because it is asserted verbatim on both kernels. Note the\n * handler count is read here, before the loop — the same instant both\n * hand-written copies read it.\n */\nfunction traceDispatch(name: string, handlers: readonly HookHandler[], logger: HookDispatchLogger): void {\n logger.debug(`Triggering hook: ${name}`, {\n hook: name,\n handlerCount: handlers.length,\n });\n}\n\n/**\n * Dispatch a hook ISOLATING failures: a handler that throws is logged as\n * `Hook handler failed: <name>` and the remaining handlers still run.\n *\n * Used by `ObjectKernelBase.triggerHook` and by `ObjectKernel`'s own\n * `kernel:shutdown` dispatch — the two sites that until #5282 were separate\n * hand-written loops printing the same line.\n *\n * ⛔ Wrong dispatcher for anything on the BOOT path — see the module docs and\n * {@link dispatchHookPropagating}.\n *\n * `handlers` is taken by reference, never copied: a handler that subscribes\n * another handler to the same hook mid-dispatch is picked up by this loop, and\n * that has always been true of both originals.\n *\n * @param name - Hook name\n * @param handlers - The hook's registered handlers, in registration order\n * @param logger - Receives the dispatch trace and one error line per failure\n * @param args - Arguments to pass to each handler\n */\nexport async function dispatchHookIsolating(\n name: string,\n handlers: readonly HookHandler[],\n logger: HookDispatchLogger,\n args: readonly any[] = [],\n): Promise<void> {\n traceDispatch(name, handlers, logger);\n\n for (const handler of handlers) {\n try {\n await handler(...args);\n } catch (error) {\n logger.error(`Hook handler failed: ${name}`, error as Error);\n // Continue with other handlers even if one fails\n }\n }\n}\n\n/**\n * Dispatch a hook PROPAGATING the first failure: the remaining handlers do not\n * run and the original error reaches the caller unwrapped.\n *\n * Used by `ObjectKernelBase.triggerHookOrThrow` (the boot-path hooks on\n * `LiteKernel`) and by BOTH kernels' `PluginContext.trigger`.\n *\n * `logger` is optional, and its absence is the one behavioural difference\n * between those callers: `context.trigger` has never emitted the\n * `Triggering hook: <name>` trace, so it passes no logger. Handing it one would\n * add a debug line to a path that has never had one — a behaviour change, and\n * #5282 preserves every call path's semantics exactly.\n *\n * @param name - Hook name\n * @param handlers - The hook's registered handlers, in registration order\n * @param logger - When given, receives the dispatch trace; omit to stay silent\n * @param args - Arguments to pass to each handler\n */\nexport async function dispatchHookPropagating(\n name: string,\n handlers: readonly HookHandler[],\n logger: HookDispatchLogger | undefined,\n args: readonly any[] = [],\n): Promise<void> {\n if (logger) traceDispatch(name, handlers, logger);\n\n for (const handler of handlers) {\n await handler(...args);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from './types.js';\nimport type { Logger } from '@objectstack/spec/contracts';\nimport type { IServiceRegistry } from '@objectstack/spec/contracts';\nimport {\n resolvePluginOrder,\n validateInitServiceContract,\n assertInitServiceRequirements,\n describeInitOrderFault,\n} from './plugin-order.js';\nimport { dispatchHookIsolating, dispatchHookPropagating } from './hook-dispatch.js';\n\n/**\n * Kernel state machine\n */\nexport type KernelState = 'idle' | 'initializing' | 'running' | 'stopping' | 'stopped';\n\n/**\n * ObjectKernelBase - Abstract Base Class for Microkernel\n * \n * Provides common functionality for ObjectKernel and LiteKernel:\n * - Plugin management (Map storage)\n * - Dependency resolution (topological sort)\n * - Hook/Event system\n * - Context creation\n * - State validation\n * \n * This eliminates code duplication between the implementations.\n */\nexport abstract class ObjectKernelBase {\n protected plugins: Map<string, Plugin> = new Map();\n protected services: IServiceRegistry | Map<string, any> = new Map();\n protected hooks: Map<string, Array<(...args: any[]) => void | Promise<void>>> = new Map();\n protected state: KernelState = 'idle';\n protected logger: Logger;\n protected context!: PluginContext;\n /**\n * Name of the plugin whose init() is currently executing (Phase 1 runs\n * sequentially, so there is at most one). Lets a getService miss during\n * init name the structural fault (#4131) instead of only the symptom.\n */\n protected currentlyInitializing?: string;\n\n constructor(logger: Logger) {\n this.logger = logger;\n }\n\n /**\n * Validate kernel state\n * @param requiredState - Required state for the operation\n * @throws Error if current state doesn't match\n */\n protected validateState(requiredState: KernelState): void {\n if (this.state !== requiredState) {\n throw new Error(\n `[Kernel] Invalid state: expected '${requiredState}', got '${this.state}'`\n );\n }\n }\n\n /**\n * Validate kernel is in idle state (for plugin registration)\n */\n protected validateIdle(): void {\n if (this.state !== 'idle') {\n throw new Error('[Kernel] Cannot register plugins after bootstrap has started');\n }\n }\n\n /**\n * Create the plugin context\n * Subclasses can override to customize context creation\n */\n protected createContext(): PluginContext {\n return {\n registerService: (name, service) => {\n if (this.services instanceof Map) {\n if (this.services.has(name)) {\n throw new Error(`[Kernel] Service '${name}' already registered`);\n }\n this.services.set(name, service);\n } else {\n // IServiceRegistry implementation\n this.services.register(name, service);\n }\n this.logger.info(`Service '${name}' registered`, { service: name });\n },\n getService: <T>(name: string): T => {\n if (this.services instanceof Map) {\n const service = this.services.get(name);\n if (!service) {\n throw new Error(\n `[Kernel] Service '${name}' not found${this.describeInitOrderFault(name)}`\n );\n }\n return service as T;\n } else {\n // IServiceRegistry implementation\n return this.services.get<T>(name);\n }\n },\n replaceService: <T>(name: string, implementation: T): void => {\n if (this.services instanceof Map) {\n if (!this.services.has(name)) {\n throw new Error(`[Kernel] Service '${name}' not found. Use registerService() to add new services.`);\n }\n this.services.set(name, implementation);\n } else {\n // IServiceRegistry implementation\n if (!this.services.has(name)) {\n throw new Error(`[Kernel] Service '${name}' not found. Use registerService() to add new services.`);\n }\n this.services.register(name, implementation);\n }\n this.logger.info(`Service '${name}' replaced`, { service: name });\n },\n hook: (name, handler) => {\n if (!this.hooks.has(name)) {\n this.hooks.set(name, []);\n }\n this.hooks.get(name)!.push(handler);\n },\n // PROPAGATING dispatch, and deliberately WITHOUT the trace line the\n // kernel's own dispatch sites emit — `context.trigger` has never\n // logged one, so no logger is handed over (#5282).\n trigger: async (name, ...args) => {\n await dispatchHookPropagating(name, this.hooks.get(name) || [], undefined, args);\n },\n getServices: () => {\n if (this.services instanceof Map) {\n return new Map(this.services);\n } else {\n // For IServiceRegistry, we need to return the underlying Map\n // This is a compatibility method\n return new Map();\n }\n },\n logger: this.logger,\n getKernel: () => this as any,\n registerServiceFactory: (_name, _factory, _lifecycle, _dependencies) => {\n throw new Error('[KernelBase] registerServiceFactory not supported — use ObjectKernel');\n },\n getServiceScoped: async <T>(_name: string, _scopeId: string): Promise<T> => {\n throw new Error('[KernelBase] getServiceScoped not supported — use ObjectKernel');\n },\n };\n }\n\n /**\n * Resolve plugin dependencies using topological sort — `dependencies`\n * hard, `optionalDependencies` order-if-present (ADR-0116, #4131). One\n * implementation shared with ObjectKernel via `plugin-order.ts`.\n * @returns Ordered list of plugins (dependencies first)\n */\n protected resolveDependencies(): Plugin[] {\n return resolvePluginOrder(this.plugins);\n }\n\n /**\n * Whether a service is registered on this kernel right now. Backs the\n * init-service contract checks (#4131).\n */\n protected hasRegisteredService(name: string): boolean {\n // Both the plain Map and IServiceRegistry expose `has`.\n return this.services.has(name);\n }\n\n /**\n * Pre-Phase-1 ordering validation (ADR-0116, #4131): a plugin whose\n * `requiresServices` names a service provided only by a LATER plugin is\n * a named boot error before any init side effects.\n */\n protected validateInitServices(ordered: Plugin[]): void {\n validateInitServiceContract(ordered, (name) => this.hasRegisteredService(name));\n }\n\n /**\n * When a getService miss happens while a plugin's init() is running,\n * append the structural diagnosis (#4131): which plugin was initializing,\n * and — when a composed plugin declares the service — who provides it.\n * Empty string outside Phase 1, so non-boot messages stay unchanged.\n */\n protected describeInitOrderFault(serviceName: string): string {\n return describeInitOrderFault(this.currentlyInitializing, this.plugins.values(), serviceName);\n }\n\n /**\n * Run plugin init phase\n * @param plugin - Plugin to initialize\n */\n protected async runPluginInit(plugin: Plugin): Promise<void> {\n const pluginName = plugin.name;\n this.logger.info(`Initializing plugin: ${pluginName}`);\n\n // Authoritative init-service check (#4131): Phase 1 is sequential,\n // so a required service absent NOW is absent for this init.\n assertInitServiceRequirements(plugin, (name) => this.hasRegisteredService(name));\n\n this.currentlyInitializing = pluginName;\n try {\n await plugin.init(this.context);\n this.logger.info(`Plugin initialized: ${pluginName}`);\n } catch (error) {\n this.logger.error(`Plugin init failed: ${pluginName}`, error as Error);\n throw error;\n } finally {\n this.currentlyInitializing = undefined;\n }\n }\n\n /**\n * Run plugin start phase\n * @param plugin - Plugin to start\n */\n protected async runPluginStart(plugin: Plugin): Promise<void> {\n if (!plugin.start) return;\n \n const pluginName = plugin.name;\n this.logger.info(`Starting plugin: ${pluginName}`);\n \n try {\n await plugin.start(this.context);\n this.logger.info(`Plugin started: ${pluginName}`);\n } catch (error) {\n this.logger.error(`Plugin start failed: ${pluginName}`, error as Error);\n throw error;\n }\n }\n\n /**\n * Run plugin destroy phase\n * @param plugin - Plugin to destroy\n */\n protected async runPluginDestroy(plugin: Plugin): Promise<void> {\n if (!plugin.destroy) return;\n \n const pluginName = plugin.name;\n this.logger.info(`Destroying plugin: ${pluginName}`);\n \n try {\n await plugin.destroy();\n this.logger.info(`Plugin destroyed: ${pluginName}`);\n } catch (error) {\n this.logger.error(`Plugin destroy failed: ${pluginName}`, error as Error);\n throw error;\n }\n }\n\n /**\n * Trigger a hook with all registered handlers, ISOLATING failures: a\n * handler that throws is logged and the remaining handlers still run.\n *\n * Use this for hooks where one subscriber's failure must not deny the\n * others their turn — notification-style hooks, and `kernel:shutdown`,\n * where the handlers still queued behind the failing one are the cleanup\n * that flushes buffers and releases resources (#5257).\n *\n * It is the WRONG dispatcher for anything on the BOOT path. Every hook\n * dispatched before \"✅ Bootstrap complete\" is a precondition of that\n * claim, so swallowing a throw there does not rescue the boot — it only\n * hides the failure behind a process that reports success. Those hooks\n * (`kernel:ready`, `kernel:bootstrapped`, `kernel:listening`) use\n * {@link triggerHookOrThrow} (#5170, #5257).\n *\n * The loop itself lives in {@link dispatchHookIsolating} — one\n * implementation shared with `ObjectKernel`'s own `kernel:shutdown`\n * dispatch, which cannot inherit this method (`ObjectKernel` does not\n * extend this class) and used to hand-mirror it (#5282).\n *\n * @param name - Hook name\n * @param args - Arguments to pass to handlers\n */\n protected async triggerHook(name: string, ...args: any[]): Promise<void> {\n await dispatchHookIsolating(name, this.hooks.get(name) || [], this.logger, args);\n }\n\n /**\n * Trigger a hook with all registered handlers, PROPAGATING the first\n * failure: the remaining handlers do not run and the original error\n * reaches the caller unwrapped.\n *\n * This is the dispatch semantics `ObjectKernel` has always had for every\n * lifecycle hook (its `context.trigger` is a bare awaited loop that never\n * catches). `LiteKernel` used the isolating {@link triggerHook} for all of\n * them, so one hook name meant two opposite things depending on which\n * kernel booted the same plugin code (#5170).\n *\n * `LiteKernel` now uses this dispatcher for all three BOOT-path hooks:\n *\n * - `kernel:ready` (#5170) — the only correct moment for a plugin to\n * assert that the preconditions it declared were actually met (the\n * registries are still filling during `init()`), so \"declared but not\n * deliverable ⇒ refuse to boot\" gates live there. On LiteKernel, which\n * is what vitest/serverless/edge run, they were downgraded to an error\n * log while the process carried on serving traffic without the\n * guarantee it claimed.\n * - `kernel:bootstrapped` and `kernel:listening` (#5257) — the same\n * argument one hook later. `kernel:listening` is where HTTP server\n * plugins open their socket, so a swallowed failure there produced the\n * worst shape available: a live process printing \"✅ Bootstrap complete\"\n * with nothing listening. `kernel:bootstrapped` carries reconcile and\n * audit passes whose silent failure is a quieter version of the same\n * lie.\n *\n * Deliberately NOT applied to `kernel:shutdown`, which keeps\n * {@link triggerHook}: on the teardown path a failing handler must not\n * block the cleanup queued behind it. That is a per-hook judgement\n * recorded at the dispatch site in `lite-kernel.ts`, not an inherited\n * default — and it is the reason this dispatcher is chosen per hook rather\n * than swapped in wholesale.\n *\n * The loop itself lives in {@link dispatchHookPropagating} — the same\n * function `PluginContext.trigger` runs on both kernels, so \"propagating\"\n * means one thing repo-wide (#5282).\n *\n * @param name - Hook name\n * @param args - Arguments to pass to handlers\n */\n protected async triggerHookOrThrow(name: string, ...args: any[]): Promise<void> {\n await dispatchHookPropagating(name, this.hooks.get(name) || [], this.logger, args);\n }\n\n /**\n * Get current kernel state\n */\n getState(): KernelState {\n return this.state;\n }\n\n /**\n * Get all registered plugins\n */\n getPlugins(): Map<string, Plugin> {\n return new Map(this.plugins);\n }\n\n /**\n * Abstract methods to be implemented by subclasses\n */\n abstract use(plugin: Plugin): this | Promise<this>;\n abstract bootstrap(): Promise<void>;\n abstract destroy(): Promise<void>;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { LoggerConfig, LogLevel } from '@objectstack/spec/system';\nimport type { Logger } from '@objectstack/spec/contracts';\n\n// Re-export the contract type so consumers can do\n// `import type { Logger } from '@objectstack/core/logger'` without also\n// pulling `@objectstack/spec` into their bundle graph manually.\nexport type { Logger };\n\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n fatal: 4,\n silent: 5,\n};\n\nconst LEVEL_COLORS: Record<LogLevel, string> = {\n debug: '\\x1b[36m',\n info: '\\x1b[32m',\n warn: '\\x1b[33m',\n error: '\\x1b[31m',\n fatal: '\\x1b[35m',\n silent: '',\n};\n\nconst RESET = '\\x1b[0m';\n\n/**\n * Split a field name into lowercase words on camelCase, `snake_case`,\n * `kebab-case`, dot and letter/digit boundaries.\n *\n * `apiKey` / `api_key` / `API_KEY` / `x-api-key` all tokenize to\n * `['api','key']`, while `monkey`, `keyword` and `tokenizer` stay a single\n * word. That difference is the whole point: it is what makes the redactor a\n * **word-boundary** matcher instead of the substring matcher it used to be\n * (#5573) — a plain `keys` field no longer reads as a secret.\n */\nfunction tokenizeFieldName(name: string): string[] {\n return name\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2') // apiKey -> api Key\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2') // APIKey -> API Key\n .replace(/([a-zA-Z])([0-9])/g, '$1 $2') // key2 -> key 2\n .split(/[^A-Za-z0-9]+/) // _ - . / space\n .filter(Boolean)\n .map((word) => word.toLowerCase());\n}\n\n/**\n * Singular form of the plural spellings the redact vocabulary actually meets\n * (`keys`, `tokens`, `secrets`, `passwords`, `passes`). Deliberately not a\n * general inflector — it only has to be right for words that end up next to a\n * redact word, and it must never turn `address`/`status` into a new word.\n */\nfunction singularizeWord(word: string): string {\n if (/(?:ss|us|is)$/.test(word)) return word; // address / status / axis\n if (/(?:ch|sh|s|x|z)es$/.test(word)) return word.slice(0, -2); // passes / boxes\n if (/[a-z0-9]s$/.test(word)) return word.slice(0, -1); // keys / tokens\n return word;\n}\n\n/**\n * Words that mark the *secret* sense of a redact word when they are glued to\n * it with no boundary to split on: `apikey`, `accesstoken`, `clientsecret`.\n *\n * Word-boundary matching covers every field name spelled the way this repo\n * spells names (camelCase config keys / snake_case machine names — Prime\n * Directive #3), but an all-lowercase concatenation has no boundary at all, so\n * `apikey` would tokenize to one word and stop being redacted. A bare\n * \"ends with `key`\" rule cannot be used to rescue it, because `monkey`,\n * `turkey` and `whiskey` end with `key` too — the exact false positives #5573\n * exists to remove. So the rescue is scoped to this explicit qualifier list:\n * `<qualifier><redact word>` is a secret, anything else glued to a redact word\n * is not.\n *\n * Consequences, on purpose:\n * - Only a **suffix** concatenation counts. `secretary` and `keyword` start\n * with a redact word and stay clear.\n * - An unlisted qualifier (`foobarkey`) is not redacted. The fix is to spell\n * the field `fooBarKey` / `foo_bar_key`, which matches generically — or to\n * add the word here.\n */\nconst CONCATENATED_SECRET_QUALIFIERS = new Set([\n 'access',\n 'account',\n 'admin',\n 'api',\n 'app',\n 'auth',\n 'bearer',\n 'client',\n 'csrf',\n 'db',\n 'database',\n 'encryption',\n 'id',\n 'jwt',\n 'master',\n 'oauth',\n 'private',\n 'public',\n 'refresh',\n 'root',\n 'secret',\n 'service',\n 'session',\n 'shared',\n 'sign',\n 'signing',\n 'ssh',\n 'token',\n 'user',\n 'webhook',\n 'xsrf',\n]);\n\n/** `apikey`/`apikeys` vs `key` — see {@link CONCATENATED_SECRET_QUALIFIERS}. */\nfunction isQualifiedConcatenation(word: string, redactWord: string): boolean {\n for (const base of [word, singularizeWord(word)]) {\n if (base.length <= redactWord.length || !base.endsWith(redactWord)) continue;\n if (CONCATENATED_SECRET_QUALIFIERS.has(base.slice(0, base.length - redactWord.length))) return true;\n }\n return false;\n}\n\n/** Does `words` contain `run` as a consecutive sub-sequence? */\nfunction containsWordRun(words: string[], run: string[]): boolean {\n for (let i = 0; i + run.length <= words.length; i++) {\n if (run.every((word, offset) => words[i + offset] === word)) return true;\n }\n return false;\n}\n\n/**\n * Word-boundary match of one configured redact pattern against one field name,\n * both already tokenized by {@link tokenizeFieldName}.\n *\n * The plural rule is the one subtlety, and it is the maintainer's ruling on\n * #5573 made consistent with itself: a **bare** plural names a collection or a\n * count, not a secret (`keys` on a Zod `unrecognized_keys` issue, `tokens` on\n * an LLM usage record), so it is left alone; a plural **inside a compound**\n * still names the secret (`apiKeys: ['sk-…']`, `refresh_tokens`) and is\n * redacted. Singular words match everywhere, compound or not.\n */\nfunction fieldWordsMatchPattern(nameWords: string[], patternWords: string[]): boolean {\n if (patternWords.length === 0 || nameWords.length === 0) return false;\n\n // A multi-word pattern (`apiKey`, `api_key`) matches a consecutive run of\n // the same words, or those words written as one concatenated token.\n if (patternWords.length > 1) {\n const glued = patternWords.join('');\n return (\n containsWordRun(nameWords, patternWords) ||\n nameWords.some((word) => word === glued || singularizeWord(word) === glued)\n );\n }\n\n const redactWord = patternWords[0];\n const isCompound = nameWords.filter((word) => /[a-z]/.test(word)).length > 1;\n return nameWords.some(\n (word) =>\n word === redactWord ||\n (isCompound && singularizeWord(word) === redactWord) ||\n isQualifiedConcatenation(word, redactWord),\n );\n}\n\n/**\n * Whether ANSI color may be written to the given stream.\n *\n * Follows the https://no-color.org convention: a non-empty `NO_COLOR` env var\n * disables color regardless of TTY, and non-TTY destinations (pipes, CI logs,\n * redirected output) always get plain text so plain-text log scanners see\n * uncolored level tags. Browser bundles have no `process`/TTY → plain text.\n */\nfunction colorEnabled(stream: { isTTY?: boolean } | undefined): boolean {\n if (typeof process !== 'undefined') {\n const noColor = (process as any).env?.NO_COLOR;\n if (noColor !== undefined && noColor !== '') return false;\n }\n return Boolean(stream?.isTTY);\n}\n\n/**\n * Resolve a Node builtin without putting it in this module's import graph.\n *\n * This entry is deliberately browser-safe — `@objectstack/client` bundles it —\n * so `fs`/`path` must never be imported statically. A lazy `require()` used to\n * meet that bar, but esbuild rewrites it to the `__require` shim in the ESM\n * output, which throws `Dynamic require of \"fs\" is not supported`. Every Node\n * ESM consumer (`os serve`, `os dev`) therefore lost file logging (#3110).\n * `process.getBuiltinModule` is a plain method call — opaque to bundlers — and\n * works in both module systems.\n */\nfunction loadNodeBuiltin<T>(id: string): T | undefined {\n if (typeof process === 'undefined') return undefined;\n\n const getBuiltinModule = (process as { getBuiltinModule?: (id: string) => unknown }).getBuiltinModule;\n if (typeof getBuiltinModule === 'function') {\n try {\n return getBuiltinModule.call(process, `node:${id}`) as T;\n } catch {\n return undefined;\n }\n }\n\n // Node < 20.16 / < 22.3 predates `getBuiltinModule`. Real `require` still\n // resolves in the CJS build; in the ESM build this is the shim that throws,\n // which the caller now reports rather than swallows.\n try {\n return require(id) as T;\n } catch {\n return undefined;\n }\n}\n\nexport class ObjectLogger implements Logger {\n private config: Required<Omit<LoggerConfig, 'file' | 'rotation' | 'name'>> & {\n file?: string;\n rotation?: { maxSize: string; maxFiles: number };\n name?: string;\n };\n private bindings: Record<string, any>;\n /** `config.redact`, tokenized once — see {@link fieldWordsMatchPattern}. */\n private redactPatterns: string[][];\n private fileStream?: any;\n /** Only the logger that opened the stream may close it — children share it. */\n private ownsFileStream = false;\n private fileLoggingDisabled = false;\n\n constructor(config: Partial<LoggerConfig> = {}, bindings: Record<string, any> = {}) {\n this.config = {\n name: config.name,\n level: config.level ?? 'info',\n format: config.format ?? 'pretty',\n redact: config.redact ?? ['password', 'token', 'secret', 'key'],\n sourceLocation: config.sourceLocation ?? false,\n file: config.file,\n // Per-key, because `LoggerConfig` is the AUTHOR state (ADR-0122): the\n // schema defaults `maxSize`/`maxFiles` *inside* `rotation`, so a caller\n // may legitimately write `{ rotation: { maxSize: '5m' } }` and this\n // constructor — which does not parse — has to fill the other half the\n // same way `LoggerConfigSchema.parse` would.\n rotation: {\n maxSize: config.rotation?.maxSize ?? '10m',\n maxFiles: config.rotation?.maxFiles ?? 5,\n },\n };\n this.bindings = bindings;\n this.redactPatterns = this.config.redact.map(tokenizeFieldName).filter((words) => words.length > 0);\n\n if (this.config.file && typeof process !== 'undefined') {\n this.openFileStream(this.config.file);\n }\n }\n\n private openFileStream(path: string) {\n const fs = loadNodeBuiltin<typeof import('node:fs')>('fs');\n const nodePath = loadNodeBuiltin<typeof import('node:path')>('path');\n if (!fs || !nodePath) {\n this.disableFileLogging(path, 'no filesystem access in this runtime');\n return;\n }\n\n try {\n fs.mkdirSync(nodePath.dirname(path), { recursive: true });\n const stream = fs.createWriteStream(path, { flags: 'a' });\n // `createWriteStream` reports open failures (EACCES, EISDIR, …)\n // asynchronously. An 'error' event with no listener is fatal to the\n // process, so file logging must degrade here rather than take the\n // host down.\n stream.on('error', (err: Error) => this.disableFileLogging(path, err.message));\n this.fileStream = stream;\n this.ownsFileStream = true;\n } catch (err) {\n this.disableFileLogging(path, (err as Error).message);\n }\n }\n\n /**\n * Report — once — that an explicitly configured `file` destination is not\n * being written, and stop trying.\n *\n * Deliberately not routed through `write()`: this says the logger cannot\n * honour its own config, so `level` must not filter it. The bare `catch {}`\n * this replaces is exactly how #3110 stayed hidden.\n */\n private disableFileLogging(path: string, reason: string) {\n this.fileStream = undefined;\n this.ownsFileStream = false;\n if (this.fileLoggingDisabled) return;\n this.fileLoggingDisabled = true;\n\n const label = this.config.name ? `[${this.config.name}] ` : '';\n const notice = `${label}logger: file logging disabled — cannot write to ${path}: ${reason}`;\n if (typeof process !== 'undefined' && (process as any).stderr) {\n (process as any).stderr.write(notice + '\\n');\n } else if (typeof console !== 'undefined') {\n console.warn(notice);\n }\n }\n\n private isEnabled(level: LogLevel): boolean {\n return LEVEL_ORDER[level] >= LEVEL_ORDER[this.config.level];\n }\n\n /**\n * Whether a meta field name names one of the configured secrets.\n *\n * Until #5573 this was `lower.includes(pattern)`, which redacted every\n * field whose name merely *contained* a redact word — `keys`, `keyword`,\n * `tokens`, `monkey`, `secretary` — and replaced its value with\n * `***REDACTED***`, so the reader lost the fact AND was told a secret had\n * been withheld. Matching is now on word boundaries: `key` matches\n * `apiKey` / `api_key`, not `keys` / `monkey` / `keyword`.\n */\n private isRedactedFieldName(key: string): boolean {\n const nameWords = tokenizeFieldName(key);\n return this.redactPatterns.some((pattern) => fieldWordsMatchPattern(nameWords, pattern));\n }\n\n private redactSensitive(obj: any): any {\n if (!obj || typeof obj !== 'object') return obj;\n const redacted = Array.isArray(obj) ? [...obj] : { ...obj };\n for (const key in redacted) {\n if (this.isRedactedFieldName(key)) {\n redacted[key] = '***REDACTED***';\n } else if (typeof redacted[key] === 'object' && redacted[key] !== null) {\n redacted[key] = this.redactSensitive(redacted[key]);\n }\n }\n return redacted;\n }\n\n private write(level: LogLevel, message: string, meta?: Record<string, any>, error?: Error) {\n if (!this.isEnabled(level)) return;\n\n const context = this.redactSensitive({\n ...this.bindings,\n ...meta,\n ...(error ? { error: { message: error.message, stack: error.stack } } : {}),\n });\n\n const hasContext = Object.keys(context).length > 0;\n const ts = new Date().toISOString();\n\n const isErrorLevel = level === 'error' || level === 'fatal';\n const proc = typeof process !== 'undefined' ? (process as any) : undefined;\n const stream = proc ? (isErrorLevel ? proc.stderr : proc.stdout) : undefined;\n\n let line: string; // console output — may carry ANSI color\n let plainLine: string; // file output — never colored\n\n if (this.config.format === 'json') {\n line = plainLine = JSON.stringify({\n time: ts,\n level,\n ...(this.config.name ? { name: this.config.name } : {}),\n msg: message,\n ...context,\n });\n } else if (this.config.format === 'text') {\n const parts = [ts, level.toUpperCase(), message];\n if (hasContext) parts.push(JSON.stringify(context));\n line = plainLine = parts.join(' | ');\n } else {\n // pretty\n const label = this.config.name ? `[${this.config.name}] ` : '';\n const head = `${ts} ${level.toUpperCase()}`;\n let tail = ` ${label}${message}`;\n if (hasContext) tail += ` ${JSON.stringify(context)}`;\n plainLine = head + tail;\n const color = LEVEL_COLORS[level] || '';\n line = color && colorEnabled(stream) ? `${color}${head}${RESET}${tail}` : plainLine;\n }\n\n // Browser-safe output: prefer process streams when available, otherwise\n // fall back to console. `process` may be missing entirely (browsers) or\n // present without stdio streams (bundler shims) — both fall through to\n // console. The previous unguarded `process.stderr?.write` threw\n // `ReferenceError: process is not defined` in browsers because\n // `process` itself is the missing global, not just its `stderr` field.\n if (stream) {\n stream.write(line + '\\n');\n } else if (typeof console !== 'undefined') {\n const fn =\n level === 'error' || level === 'fatal' ? console.error\n : level === 'warn' ? console.warn\n : level === 'debug' ? console.debug\n : console.log;\n fn(line);\n }\n\n if (this.fileStream) {\n this.fileStream.write(plainLine + '\\n');\n }\n }\n\n debug(message: string, meta?: Record<string, any>): void {\n this.write('debug', message, meta);\n }\n\n info(message: string, meta?: Record<string, any>): void {\n this.write('info', message, meta);\n }\n\n warn(message: string, meta?: Record<string, any>): void {\n this.write('warn', message, meta);\n }\n\n error(message: string, errorOrMeta?: Error | Record<string, any>, meta?: Record<string, any>): void {\n this.writeErrorLike('error', message, errorOrMeta, meta);\n }\n\n fatal(message: string, errorOrMeta?: Error | Record<string, any>, meta?: Record<string, any>): void {\n this.writeErrorLike('fatal', message, errorOrMeta, meta);\n }\n\n /**\n * `error`/`fatal` dispatch — the two levels whose contract has an `Error`\n * slot in front of `meta`.\n *\n * The `Logger` contract declares `error(message, error?: Error, meta?)`, and\n * `ObjectLogger` additionally tolerates a **meta object** in the `error`\n * slot because many in-repo call sites write `logger.error(msg, { … })`.\n * That tolerance is fine; dropping a parameter the contract *declares* is\n * not, and that is what the previous dispatch did:\n *\n * if (errorOrMeta instanceof Error) this.write(level, message, meta, errorOrMeta);\n * else this.write(level, message, errorOrMeta);\n *\n * With `error === undefined` the `else` branch passed `undefined` as the\n * meta and **never read the third argument**, so every contract-shaped\n * `logger.error(msg, undefined, { … })` call rendered a bare message with\n * its diagnostics silently gone — ~15 such call sites across `metadata`,\n * `metadata-protocol`, `client` and `core/security`, plus the connector\n * reconcile seam that found this (#5575). The contract's two sibling\n * implementations (`ConsoleLogger`/`JsonLogger` in `@objectstack/observability`)\n * both honour the slot, so the contract was right and this class was the\n * outlier — declared ≠ enforced, Prime Directive #10.\n *\n * All three shapes are now honoured. When both slots carry meta, `meta`\n * (the later, more specific argument) wins on a key collision.\n */\n private writeErrorLike(\n level: 'error' | 'fatal',\n message: string,\n errorOrMeta?: Error | Record<string, any>,\n meta?: Record<string, any>,\n ): void {\n if (errorOrMeta instanceof Error) {\n this.write(level, message, meta, errorOrMeta);\n return;\n }\n const merged = errorOrMeta && meta ? { ...errorOrMeta, ...meta } : (errorOrMeta ?? meta);\n this.write(level, message, merged);\n }\n\n log(message: string, ...args: any[]): void {\n this.info(message, args.length > 0 ? { args } : undefined);\n }\n\n child(context: Record<string, any>): ObjectLogger {\n // Construct without `file`, then share the parent's stream: the\n // constructor opens eagerly, so passing `file` through would open a\n // second stream per child and immediately orphan it. That leak was\n // unreachable while #3110 kept the ESM open path dead.\n const child = new ObjectLogger({ ...this.config, file: undefined }, { ...this.bindings, ...context });\n child.config.file = this.config.file;\n child.fileStream = this.fileStream;\n return child;\n }\n\n withTrace(traceId: string, spanId?: string): ObjectLogger {\n return this.child({ traceId, spanId });\n }\n\n async destroy(): Promise<void> {\n const stream = this.fileStream;\n this.fileStream = undefined;\n // Children share the opener's stream; if they closed it too, one child's\n // teardown would end file logging for the parent and every sibling,\n // whose writes then land on a closed stream and only trip the 'error'\n // handler above.\n if (!stream || !this.ownsFileStream) return;\n this.ownsFileStream = false;\n await new Promise<void>((resolve) => stream.end(resolve));\n }\n}\n\nexport function createLogger(config?: Partial<LoggerConfig>): ObjectLogger {\n return new ObjectLogger(config);\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Plugin, PluginContext } from './types.js';\nimport { createLogger, ObjectLogger } from './logger.js';\nimport type { LoggerConfig } from '@objectstack/spec/system';\nimport { ServiceRequirementDef } from '@objectstack/spec/system';\nimport { PluginLoader, PluginMetadata, ServiceLifecycle, ServiceFactory, PluginStartupResult } from './plugin-loader.js';\nimport { isNode, safeExit } from './utils/env.js';\nimport { CORE_FALLBACK_FACTORIES } from './fallbacks/index.js';\nimport {\n resolvePluginOrder,\n validateInitServiceContract,\n assertInitServiceRequirements,\n describeInitOrderFault,\n} from './plugin-order.js';\nimport { dispatchHookIsolating, dispatchHookPropagating } from './hook-dispatch.js';\nimport { registerPluginByName } from './plugin-registration.js';\nimport { raceWithTimeout } from './timeout-guard.js';\n\n/**\n * Enhanced Kernel Configuration\n */\nexport interface ObjectKernelConfig {\n logger?: Partial<LoggerConfig>;\n \n /** Default plugin startup timeout in milliseconds */\n defaultStartupTimeout?: number;\n \n /** Whether to enable graceful shutdown */\n gracefulShutdown?: boolean;\n \n /** Graceful shutdown timeout in milliseconds */\n shutdownTimeout?: number;\n \n /** Whether to rollback on startup failure */\n rollbackOnFailure?: boolean;\n \n /** Whether to skip strict system requirement validation (Critical for testing) */\n skipSystemValidation?: boolean;\n}\n\n/**\n * Enhanced ObjectKernel with Advanced Plugin Management\n * \n * Extends the basic ObjectKernel with:\n * - Async plugin loading with validation\n * - Version compatibility checking\n * - Plugin signature verification\n * - Configuration validation (Zod)\n * - Factory-based dependency injection\n * - Service lifecycle management (singleton/transient/scoped)\n * - Circular dependency detection\n * - Lazy loading services\n * - Graceful shutdown\n * - Plugin startup timeout control\n * - Startup failure rollback\n * - Plugin health checks\n */\nexport class ObjectKernel {\n private plugins: Map<string, PluginMetadata> = new Map();\n private services: Map<string, any> = new Map();\n private hooks: Map<string, Array<(...args: any[]) => void | Promise<void>>> = new Map();\n private state: 'idle' | 'initializing' | 'running' | 'stopping' | 'stopped' = 'idle';\n private logger: ObjectLogger;\n private context: PluginContext;\n private pluginLoader: PluginLoader;\n private config: ObjectKernelConfig;\n private startedPlugins: Set<string> = new Set();\n private pluginStartTimes: Map<string, number> = new Map();\n private shutdownHandlers: Array<() => Promise<void>> = [];\n /**\n * Name of the plugin whose init() is currently executing (Phase 1 is\n * sequential, so at most one). Lets a getService miss during init name\n * the structural fault (#4131) instead of only the symptom.\n */\n private currentlyInitializing?: string;\n\n constructor(config: ObjectKernelConfig = {}) {\n this.config = {\n defaultStartupTimeout: 30000, // 30 seconds\n gracefulShutdown: true,\n shutdownTimeout: 60000, // 60 seconds\n rollbackOnFailure: true,\n ...config,\n };\n\n this.logger = createLogger(config.logger);\n this.pluginLoader = new PluginLoader(this.logger);\n \n // Initialize context\n this.context = {\n registerService: (name, service) => {\n this.registerService(name, service);\n },\n registerServiceFactory: (name, factory, lifecycle, dependencies) => {\n this.registerServiceFactory(name, factory, lifecycle, dependencies);\n },\n getService: <T>(name: string) => {\n // 1. Try direct service map first (synchronous cache)\n const service = this.services.get(name);\n if (service) {\n return service as T;\n }\n\n // 2. Try to get from plugin loader cache (Sync access to factories)\n const loaderService = this.pluginLoader.getServiceInstance<T>(name);\n if (loaderService) {\n // Cache it locally for faster next access\n this.services.set(name, loaderService);\n return loaderService;\n }\n\n // 3. Neither sync map has it. Two very different faults share\n // this branch and MUST NOT share one message (#4085):\n // (a) nothing ever registered `name` — a composition /\n // ordering fault at the CALLER (e.g. a plugin reaching\n // for `manifest` in init() before the engine plugin\n // registered it);\n // (b) `name` IS registered, as a factory that has not been\n // instantiated yet — the caller merely used the wrong\n // accessor and needs `getServiceAsync`.\n // `pluginLoader.getService` is an `async` method, so its\n // return value is ALWAYS a Promise and its internal\n // \"not found\" rejection can never surface synchronously.\n // Reading (a) off that Promise therefore reported every\n // missing service as \"is async - use await\" — the wrong fix,\n // pointing at the wrong layer. Decide from the registry\n // instead, which is synchronous and authoritative.\n if (!this.pluginLoader.hasService(name)) {\n throw new Error(\n `[Kernel] Service '${name}' not found${this.describeInitOrderFault(name)}`\n );\n }\n\n // Registered but not instantiated ⇒ factory-backed. Message\n // kept verbatim: callers that tolerate an async-only service\n // (console static assets, the HTTP dispatcher) match on\n // `is async`.\n throw new Error(`Service '${name}' is async - use await`);\n },\n replaceService: <T>(name: string, implementation: T): void => {\n const hasService = this.services.has(name) || this.pluginLoader.hasService(name);\n if (!hasService) {\n throw new Error(`[Kernel] Service '${name}' not found. Use registerService() to add new services.`);\n }\n this.services.set(name, implementation);\n this.pluginLoader.replaceService(name, implementation);\n this.logger.info(`Service '${name}' replaced`, { service: name });\n },\n hook: (name, handler) => {\n if (!this.hooks.has(name)) {\n this.hooks.set(name, []);\n }\n this.hooks.get(name)!.push(handler);\n },\n // PROPAGATING dispatch — the same shared loop `LiteKernel`'s\n // context.trigger runs, and deliberately WITHOUT a trace line:\n // `context.trigger` has never emitted one on either kernel, so no\n // logger is handed over (#5282).\n trigger: async (name, ...args) => {\n await dispatchHookPropagating(name, this.hooks.get(name) || [], undefined, args);\n },\n getServices: () => {\n return new Map(this.services);\n },\n getServiceScoped: <T>(name: string, scopeId: string): Promise<T> => {\n return this.pluginLoader.getService<T>(name, scopeId);\n },\n logger: this.logger,\n getKernel: () => this as any, // Type compatibility\n };\n\n this.pluginLoader.setContext(this.context);\n\n // Register shutdown handler\n if (this.config.gracefulShutdown) {\n this.registerShutdownSignals();\n }\n }\n\n /**\n * Register a plugin with enhanced validation\n *\n * Duplicate names OVERWRITE, with one `warn` naming both versions — the\n * declared contract in `plugin-registration.ts`, applied identically by\n * `LiteKernel.use()` (#9864, maintainer ruling 2026-08-19). The overwrite\n * itself is unchanged: it is what lets an app config's `plugins` entry\n * supersede a plugin the CLI auto-registered earlier in the same boot\n * (#9863). What changes is that it is no longer silent, and no longer\n * disagrees with the other kernel.\n */\n async use(plugin: Plugin): Promise<this> {\n if (this.state !== 'idle') {\n throw new Error('[Kernel] Cannot register plugins after bootstrap has started');\n }\n\n // Load plugin through enhanced loader\n const result = await this.pluginLoader.loadPlugin(plugin);\n\n if (!result.success || !result.plugin) {\n throw new Error(`Failed to load plugin: ${plugin.name} - ${result.error?.message}`);\n }\n\n const pluginMeta = result.plugin;\n const superseded = registerPluginByName(this.plugins, pluginMeta, this.logger);\n\n // [#9864] Suppressed for a superseding registration, deliberately. The\n // defect the ruling names is that this line printed TWICE for one\n // surviving plugin and so read as two plugins running; the `warn`\n // `registerPluginByName` just emitted says everything this line would\n // and says which instance survived. Suppressing it here makes the\n // count of `Plugin registered:` lines in a boot log equal the number\n // of plugins that will actually boot.\n if (superseded === undefined) {\n this.logger.info(`Plugin registered: ${pluginMeta.name}@${pluginMeta.version}`, {\n plugin: pluginMeta.name,\n version: pluginMeta.version,\n });\n }\n\n return this;\n }\n\n /**\n * Register a service instance directly\n */\n registerService<T>(name: string, service: T): this {\n if (this.services.has(name)) {\n throw new Error(`[Kernel] Service '${name}' already registered`);\n }\n this.services.set(name, service);\n this.pluginLoader.registerService(name, service);\n this.logger.info(`Service '${name}' registered`, { service: name });\n return this;\n }\n\n /**\n * Register a service factory with lifecycle management\n */\n registerServiceFactory<T>(\n name: string,\n factory: ServiceFactory<T>,\n lifecycle: ServiceLifecycle = ServiceLifecycle.SINGLETON,\n dependencies?: string[]\n ): this {\n this.pluginLoader.registerServiceFactory({\n name,\n factory,\n lifecycle,\n dependencies,\n });\n return this;\n }\n\n /**\n * Pre-inject in-memory fallbacks for 'core' services that were not registered\n * by plugins during Phase 1. Called before Phase 2 so that all core services\n * (e.g. 'metadata', 'cache', 'queue') are resolvable via ctx.getService()\n * when plugin start() methods execute.\n */\n private preInjectCoreFallbacks() {\n if (this.config.skipSystemValidation) return;\n for (const [serviceName, criticality] of Object.entries(ServiceRequirementDef)) {\n if (criticality !== 'core') continue;\n const hasService = this.services.has(serviceName) || this.pluginLoader.hasService(serviceName);\n if (!hasService) {\n const factory = CORE_FALLBACK_FACTORIES[serviceName];\n if (factory) {\n const fallback = factory();\n this.registerService(serviceName, fallback);\n this.logger.debug(`[Kernel] Pre-injected in-memory fallback for '${serviceName}' before Phase 2`);\n }\n }\n }\n }\n\n /**\n * Validate Critical System Requirements\n */\n private validateSystemRequirements() {\n if (this.config.skipSystemValidation) {\n this.logger.debug('System requirement validation skipped');\n return;\n }\n\n this.logger.debug('Validating system service requirements...');\n const missingServices: string[] = [];\n const missingCoreServices: string[] = [];\n \n // Iterate through all defined requirements\n for (const [serviceName, criticality] of Object.entries(ServiceRequirementDef)) {\n const hasService = this.services.has(serviceName) || this.pluginLoader.hasService(serviceName);\n \n if (!hasService) {\n if (criticality === 'required') {\n this.logger.error(`CRITICAL: Required service missing: ${serviceName}`);\n missingServices.push(serviceName);\n } else if (criticality === 'core') {\n // Auto-inject in-memory fallback if available\n const factory = CORE_FALLBACK_FACTORIES[serviceName];\n if (factory) {\n const fallback = factory();\n this.registerService(serviceName, fallback);\n this.logger.warn(`Service '${serviceName}' not provided — using in-memory fallback`);\n } else {\n this.logger.warn(`CORE: Core service missing, functionality may be degraded: ${serviceName}`);\n missingCoreServices.push(serviceName);\n }\n } else {\n this.logger.info(`Info: Optional service not present: ${serviceName}`);\n }\n }\n }\n\n if (missingServices.length > 0) {\n const errorMsg = `System failed to start. Missing critical services: ${missingServices.join(', ')}`;\n this.logger.error(errorMsg);\n throw new Error(errorMsg);\n }\n\n if (missingCoreServices.length > 0) {\n this.logger.warn(`System started with degraded capabilities. Missing core services: ${missingCoreServices.join(', ')}`);\n }\n \n this.logger.info('System requirement check passed');\n }\n\n /**\n * Bootstrap the kernel with enhanced features\n */\n async bootstrap(): Promise<void> {\n if (this.state !== 'idle') {\n throw new Error('[Kernel] Kernel already bootstrapped');\n }\n\n this.state = 'initializing';\n this.logger.info('Bootstrap started');\n\n try {\n // Check for circular dependencies\n const cycles = this.pluginLoader.detectCircularDependencies();\n if (cycles.length > 0) {\n this.logger.warn('Circular service dependencies detected:', { cycles });\n }\n\n // Resolve plugin dependencies\n const orderedPlugins = this.resolveDependencies();\n\n // Pre-Phase-1 ordering contract (ADR-0116, #4131): a plugin that\n // requires a service provided only by a later plugin fails HERE,\n // named, before any init side effects.\n validateInitServiceContract(orderedPlugins, (name) => this.hasAnyService(name));\n\n // Phase 1: Init - Plugins register services\n this.logger.info('Phase 1: Init plugins');\n for (const plugin of orderedPlugins) {\n await this.initPluginWithTimeout(plugin);\n }\n\n // Pre-inject in-memory fallbacks for 'core' services that were not\n // registered by any plugin during Phase 1. This ensures services like\n // 'metadata', 'cache', 'queue', etc. are always available when plugins\n // call ctx.getService() during their start() methods.\n this.preInjectCoreFallbacks();\n\n // Phase 2: Start - Plugins execute business logic\n this.logger.info('Phase 2: Start plugins');\n this.state = 'running';\n \n for (const plugin of orderedPlugins) {\n const result = await this.startPluginWithTimeout(plugin);\n \n if (!result.success) {\n this.logger.error(`Plugin startup failed: ${plugin.name}`, result.error);\n const origMsg = result.error instanceof Error ? result.error.message : String(result.error);\n const origStack = result.error instanceof Error ? result.error.stack : '';\n console.error(`[Kernel] Plugin startup failed: ${plugin.name}`, origMsg, origStack);\n\n if (this.config.rollbackOnFailure) {\n this.logger.warn('Rolling back started plugins...');\n await this.rollbackStartedPlugins();\n // Propagate the original cause through the thrown error\n // so callers (e.g. cloud auth-proxy) can surface the\n // real failure instead of an opaque \"rollback complete\"\n // string. Without this, every kernel-boot failure looks\n // identical from the outside.\n const err: any = new Error(\n `Plugin ${plugin.name} failed to start - rollback complete: ${origMsg}`,\n );\n if (result.error instanceof Error) {\n err.cause = result.error;\n err.originalStack = origStack;\n }\n throw err;\n }\n }\n }\n\n // Phase 3: Trigger kernel:ready hook\n this.validateSystemRequirements(); // Final check before ready\n this.logger.debug('Triggering kernel:ready hook');\n await this.context.trigger('kernel:ready');\n\n // Phase 3.5: Trigger kernel:bootstrapped AFTER every kernel:ready\n // handler has settled — the \"all synchronous bootstrap has settled\"\n // anchor. Reconcile/backfill work that consumes data produced by a\n // later-starting plugin's kernel:ready handler belongs here, not in\n // kernel:ready (where handler order would race the data). NOTE: this\n // does NOT guarantee background app seed data has settled (an inline\n // seed that overruns OS_INLINE_SEED_BUDGET_MS finishes later) —\n // subscribe `app:seeded` for that. See\n // packages/spec/src/contracts/plugin-lifecycle-events.ts.\n this.logger.debug('Triggering kernel:bootstrapped hook');\n await this.context.trigger('kernel:bootstrapped');\n\n // Phase 4: Trigger kernel:listening hook AFTER all kernel:ready\n // handlers have completed. This is the cue for HTTP server\n // plugins to actually open the listening socket — by now every\n // other plugin has finished registering routes/middleware.\n // See `kernel:listening` docs in\n // packages/spec/src/contracts/plugin-lifecycle-events.ts\n // for the race-condition rationale.\n this.logger.debug('Triggering kernel:listening hook');\n await this.context.trigger('kernel:listening');\n\n this.logger.info('✅ Bootstrap complete');\n } catch (error) {\n this.state = 'stopped';\n throw error;\n }\n }\n\n /**\n * Graceful shutdown with timeout\n */\n async shutdown(): Promise<void> {\n if (this.state === 'stopped' || this.state === 'stopping') {\n this.logger.warn('Kernel already stopped or stopping');\n return;\n }\n\n if (this.state !== 'running') {\n throw new Error('[Kernel] Kernel not running');\n }\n\n this.state = 'stopping';\n this.logger.info('Graceful shutdown started');\n\n // The ONE rejection that means \"teardown hung\". Created here so the\n // catch below can discriminate by IDENTITY (#5274): only this\n // `setTimeout` can produce this exact object, so no message match, no\n // `instanceof`, and nothing a plugin throws can ever impersonate it —\n // not even a handler throwing `new Error('Shutdown timeout exceeded')`.\n // That discrimination is the whole point: the catch used to be reached\n // by BOTH the timer and any exception escaping `performShutdown()`, and\n // it treated them identically — `process.exit(1)` under a log line\n // reading \"Shutdown timed out\" when nothing had timed out.\n const shutdownTimeoutError = new Error('Shutdown timeout exceeded');\n\n try {\n // Same guard as the startup races (#10604). It used to be hand-rolled\n // here, and the two copies had already drifted into doing opposite\n // halves of the same job: this one `unref()`d its timer and never\n // cleared it, so a won race left it armed to fire against a kernel\n // that was already 'stopped', while the startup site cleared and\n // never unref'd. `raceWithTimeout` does both halves, once.\n //\n // Dropping the `unref()` is the point, not a casualty of the merge:\n // an unref'd guard stops being a guard (#4813). If `performShutdown()`\n // hangs and nothing else keeps the loop alive, an unref'd timer lets\n // Node exit *silently* — no 'Shutdown timed out', no `exit(1)`, the\n // one branch that hard-exit was ever right for never reached.\n // Clearing on settle keeps it ref'd exactly while the race is\n // undecided, which is the property this timeout needs.\n await raceWithTimeout(\n this.performShutdown(),\n this.config.shutdownTimeout!,\n () => shutdownTimeoutError,\n );\n\n this.state = 'stopped';\n this.logger.info('✅ Graceful shutdown complete');\n } catch (error) {\n this.state = 'stopped';\n\n if (error === shutdownTimeoutError) {\n // GENUINE timeout: `performShutdown()` is still running and has\n // stopped making progress, so the process would otherwise hang\n // holding whatever it failed to release. Hard-exit stays — it\n // is the only branch it was ever right for.\n this.logger.error('Shutdown timed out — forcing exit', error as Error);\n // Flush logger then hard-exit; the process would otherwise hang\n await this.logger.destroy();\n process.exit(1);\n } else {\n // NOT a timeout. `performShutdown()` isolates every teardown\n // step it owns (hook dispatch, each destroy(), each shutdown\n // handler), so reaching here means something outside those\n // loops failed — the teardown is over either way, and there is\n // nothing hung to escape from. Killing the host process here\n // would take away the embedding host's (cloud auth-proxy, CLI,\n // a test runner) chance to do its own cleanup, over a fault\n // that did not require it. Log and return down the normal\n // path; `shutdown()` still never rejects.\n this.logger.error(\n 'Shutdown finished with an unexpected teardown error — the kernel is stopped and the process is NOT being exited; some cleanup may not have run',\n error as Error,\n );\n }\n } finally {\n await this.logger.destroy();\n }\n }\n\n /**\n * Check health of a specific plugin\n */\n async checkPluginHealth(pluginName: string): Promise<any> {\n return await this.pluginLoader.checkPluginHealth(pluginName);\n }\n\n /**\n * Check health of all plugins\n */\n async checkAllPluginsHealth(): Promise<Map<string, any>> {\n const results = new Map();\n \n for (const pluginName of this.plugins.keys()) {\n const health = await this.checkPluginHealth(pluginName);\n results.set(pluginName, health);\n }\n \n return results;\n }\n\n /**\n * Get plugin startup metrics\n */\n getPluginMetrics(): Map<string, number> {\n return new Map(this.pluginStartTimes);\n }\n\n /**\n * Whether a plugin with the given name has been registered on this kernel.\n *\n * Registration happens synchronously in `use()` before any plugin's\n * `start()` runs, so a plugin may use this during its own start() to make\n * composition-dependent decisions deterministically — e.g. the dispatcher\n * bridge cedes `${prefix}/discovery` to `com.objectstack.rest.api` when\n * both are mounted (ADR-0076 D11: single owner per route, not\n * first-registration-wins).\n */\n hasPlugin(name: string): boolean {\n return this.plugins.has(name);\n }\n\n /**\n * Get a service (sync helper)\n */\n getService<T>(name: string): T {\n return this.context.getService<T>(name);\n }\n\n /**\n * Get a service asynchronously (supports factories)\n */\n async getServiceAsync<T>(name: string, scopeId?: string): Promise<T> {\n return await this.pluginLoader.getService<T>(name, scopeId);\n }\n\n /**\n * Clear all scoped service instances for a given scope (e.g., environmentId).\n * Releases driver connections and metadata caches for idle projects.\n */\n clearScope(scopeId: string): void {\n this.pluginLoader.clearScope(scopeId);\n }\n\n /**\n * Check if kernel is running\n */\n isRunning(): boolean {\n return this.state === 'running';\n }\n\n /**\n * Get kernel state\n */\n getState(): string {\n return this.state;\n }\n\n // Private methods\n\n private async initPluginWithTimeout(plugin: PluginMetadata): Promise<void> {\n const timeout = plugin.startupTimeout || this.config.defaultStartupTimeout!;\n\n this.logger.debug(`Init: ${plugin.name}`, { plugin: plugin.name });\n\n // Authoritative init-service check (#4131): Phase 1 is sequential,\n // so a required service absent NOW is absent for this init.\n assertInitServiceRequirements(plugin, (name) => this.hasAnyService(name));\n\n this.currentlyInitializing = plugin.name;\n try {\n await this.raceStartupTimeout(\n plugin.init(this.context),\n timeout,\n `Plugin ${plugin.name} init timeout after ${timeout}ms`\n );\n } finally {\n this.currentlyInitializing = undefined;\n }\n }\n\n /**\n * Race a plugin lifecycle hook against its startup-timeout guard, and\n * reclaim the guard the moment the race settles (#4813).\n *\n * The guard used to be armed and then abandoned: when the plugin won the\n * race, its `setTimeout` stayed ref'd in the event loop for the full\n * `startupTimeout`, so every process idled that long after its work was\n * done. One `os migrate` finished in 3s and then sat for 120s\n * (`ObjectQLPlugin.startupTimeout`), held open by 8 orphaned guards — one\n * per init plus one per start.\n *\n * Clearing on settle rather than `unref()`-ing at arm time is deliberate.\n * An unref'd guard also stops pinning the loop, but it stops being a guard\n * as well: if the hook never settles and nothing else keeps the loop alive,\n * Node exits before the timer can fire and the timeout is never reported.\n * The guard has to stay ref'd exactly as long as the race is undecided,\n * which is what clearing on settle expresses.\n *\n * Clearing the timer was only half of it, though (#10604): the promise the\n * race still holds a reaction on has to SETTLE, or it and that reaction are\n * retained past the end of the run — two leaking promises per boot, which\n * is what `vitest --detectAsyncLeaks` names here. Both halves now live in\n * `TimeoutGuard.reclaim()`, shared with `shutdown()`, so the two sites\n * cannot drift into doing one half each again.\n */\n private async raceStartupTimeout<T>(\n operation: T | PromiseLike<T>,\n timeout: number,\n message: string\n ): Promise<T> {\n return raceWithTimeout(operation, timeout, () => new Error(message));\n }\n\n /**\n * Whether a service is resolvable on this kernel right now — direct\n * registration or a loader-registered factory. Backs the init-service\n * contract checks (#4131).\n */\n private hasAnyService(name: string): boolean {\n return this.services.has(name) || this.pluginLoader.hasService(name);\n }\n\n /**\n * When a getService miss happens while a plugin's init() is running,\n * append the structural diagnosis (#4131): which plugin was initializing,\n * and — when a composed plugin declares the service — who provides it.\n * Empty string outside Phase 1, so non-boot messages stay unchanged.\n */\n private describeInitOrderFault(serviceName: string): string {\n return describeInitOrderFault(this.currentlyInitializing, this.plugins.values(), serviceName);\n }\n\n private async startPluginWithTimeout(plugin: PluginMetadata): Promise<PluginStartupResult> {\n if (!plugin.start) {\n return { success: true, pluginName: plugin.name };\n }\n\n const timeout = plugin.startupTimeout || this.config.defaultStartupTimeout!;\n const startTime = Date.now();\n \n this.logger.debug(`Start: ${plugin.name}`, { plugin: plugin.name });\n \n try {\n await this.raceStartupTimeout(\n plugin.start(this.context),\n timeout,\n `Plugin ${plugin.name} start timeout after ${timeout}ms`\n );\n\n const duration = Date.now() - startTime;\n this.startedPlugins.add(plugin.name);\n this.pluginStartTimes.set(plugin.name, duration);\n \n this.logger.debug(`Plugin started: ${plugin.name} (${duration}ms)`);\n \n return {\n success: true,\n pluginName: plugin.name,\n startTime: duration,\n };\n } catch (error) {\n const duration = Date.now() - startTime;\n const isTimeout = (error as Error).message.includes('timeout');\n \n return {\n success: false,\n pluginName: plugin.name,\n error: error as Error,\n startTime: duration,\n timedOut: isTimeout,\n };\n }\n }\n\n private async rollbackStartedPlugins(): Promise<void> {\n const pluginsToRollback = Array.from(this.startedPlugins).reverse();\n \n for (const pluginName of pluginsToRollback) {\n const plugin = this.plugins.get(pluginName);\n if (plugin?.destroy) {\n try {\n this.logger.debug(`Rollback: ${pluginName}`);\n await plugin.destroy();\n } catch (error) {\n this.logger.error(`Rollback failed for ${pluginName}`, error as Error);\n }\n }\n }\n \n this.startedPlugins.clear();\n }\n\n /**\n * Dispatch `kernel:shutdown`, ISOLATING failures: a handler that throws is\n * logged and the remaining handlers still run (#5274).\n *\n * This is a per-hook judgement, deliberately NOT the bare awaited loop\n * `context.trigger` runs for every other hook — the boot-path hooks\n * (`kernel:ready`, `kernel:bootstrapped`, `kernel:listening`) keep\n * propagating, because everything dispatched before \"✅ Bootstrap complete\"\n * is a precondition of that claim and swallowing a throw there only hides\n * the failure behind a process reporting success (#5170, #5257).\n *\n * On the teardown path there is no \"refuse to proceed\" left to buy. What is\n * queued behind a failing shutdown handler is the rest of the cleanup —\n * every other subscriber, then each plugin's `destroy()` in reverse order —\n * which is what flushes buffers, closes connections and releases locks. So\n * one bad handler must not amplify into leaked resources and unflushed\n * writes. Same reasoning, same wording, same `Hook handler failed:\n * kernel:shutdown` log line as `LiteKernel`'s dispatch site, which reaches\n * the isolating dispatcher through `ObjectKernelBase.triggerHook` (#5257).\n *\n * Until #5282 \"same wording\" was literally that — the loop was typed out a\n * second time here, because `ObjectKernel` does not extend\n * `ObjectKernelBase` (only `LiteKernel` does) and owns its own `hooks` map,\n * so the base's `protected triggerHook` is out of reach. The loop now lives\n * in {@link dispatchHookIsolating}, which BOTH sides call: the storage is\n * still two maps (deliberately — unifying it was out of #5282's scope), but\n * \"isolating\" is one implementation, so it can no longer drift on one\n * kernel while the other keeps the old shape. That drift is exactly the bug\n * #5170 / #5257 / #5274 each closed one hook at a time, and the paired-pin\n * gate (`scripts/check-kernel-hook-pairs.mjs`) covers the residue.\n */\n private async triggerShutdownHookIsolating(): Promise<void> {\n await dispatchHookIsolating('kernel:shutdown', this.hooks.get('kernel:shutdown') || [], this.logger);\n }\n\n private async performShutdown(): Promise<void> {\n // Trigger shutdown hook — ISOLATING dispatch, see the method's own\n // rationale. The two loops below already isolate per plugin and per\n // handler; before #5274 this line was the one teardown step that did\n // not, so a single throwing subscriber skipped BOTH of them.\n await this.triggerShutdownHookIsolating();\n\n // Destroy plugins in reverse order\n const orderedPlugins = Array.from(this.plugins.values()).reverse();\n for (const plugin of orderedPlugins) {\n if (plugin.destroy) {\n this.logger.debug(`Destroy: ${plugin.name}`, { plugin: plugin.name });\n try {\n await plugin.destroy();\n } catch (error) {\n this.logger.error(`Error destroying plugin ${plugin.name}`, error as Error);\n }\n }\n }\n\n // Execute custom shutdown handlers\n for (const handler of this.shutdownHandlers) {\n try {\n await handler();\n } catch (error) {\n this.logger.error('Shutdown handler error', error as Error);\n }\n }\n }\n\n /**\n * Topological order over `dependencies` (hard) + `optionalDependencies`\n * (order-if-present) — ADR-0116, #4131. One implementation shared with\n * LiteKernel via `plugin-order.ts`.\n */\n private resolveDependencies(): PluginMetadata[] {\n return resolvePluginOrder(this.plugins);\n }\n\n private registerShutdownSignals(): void {\n const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGQUIT'];\n let shutdownInProgress = false;\n \n const handleShutdown = async (signal: string) => {\n if (shutdownInProgress) {\n this.logger.warn(`Shutdown already in progress, ignoring ${signal}`);\n return;\n }\n \n shutdownInProgress = true;\n this.logger.info(`Received ${signal} - initiating graceful shutdown`);\n \n try {\n await this.shutdown();\n safeExit(0);\n } catch (error) {\n this.logger.error('Shutdown failed', error as Error);\n safeExit(1);\n }\n };\n \n if (isNode) {\n for (const signal of signals) {\n process.on(signal, () => handleShutdown(signal));\n }\n }\n }\n\n /**\n * Register a custom shutdown handler\n */\n onShutdown(handler: () => Promise<void>): void {\n this.shutdownHandlers.push(handler);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { z } from 'zod';\nimport type { Logger } from '@objectstack/spec/contracts';\nimport type { PluginMetadata } from '../plugin-loader.js';\n\n/**\n * Plugin Configuration Validator\n * \n * Validates plugin configurations against Zod schemas to ensure:\n * 1. Type safety - all config values have correct types\n * 2. Business rules - values meet constraints (min/max, regex, etc.)\n * 3. Required fields - all mandatory configuration is provided\n * 4. Default values - missing optional fields get defaults\n * \n * Architecture:\n * - Uses Zod for runtime validation\n * - Provides detailed error messages with field paths\n * - Supports nested configuration objects\n * - Allows partial validation for incremental updates\n * \n * Usage:\n * ```typescript\n * const validator = new PluginConfigValidator(logger);\n * const validConfig = validator.validatePluginConfig(plugin, userConfig);\n * ```\n */\nexport class PluginConfigValidator {\n private logger: Logger;\n \n constructor(logger: Logger) {\n this.logger = logger;\n }\n \n /**\n * Validate plugin configuration against its Zod schema\n * \n * @param plugin - Plugin metadata with configSchema\n * @param config - User-provided configuration\n * @returns Validated and typed configuration\n * @throws Error with detailed validation errors\n */\n validatePluginConfig<T = any>(plugin: PluginMetadata, config: any): T {\n if (!plugin.configSchema) {\n this.logger.debug(`Plugin ${plugin.name} has no config schema - skipping validation`);\n return config as T;\n }\n \n try {\n // Use Zod to parse and validate\n const validatedConfig = plugin.configSchema.parse(config);\n \n this.logger.debug(`✅ Plugin config validated: ${plugin.name}`, {\n plugin: plugin.name,\n configKeys: Object.keys(config || {}).length,\n });\n \n return validatedConfig as T;\n } catch (error) {\n if (error instanceof z.ZodError) {\n const formattedErrors = this.formatZodErrors(error);\n const errorMessage = [\n `Plugin ${plugin.name} configuration validation failed:`,\n ...formattedErrors.map(e => ` - ${e.path}: ${e.message}`),\n ].join('\\n');\n \n this.logger.error(errorMessage, undefined, {\n plugin: plugin.name,\n errors: formattedErrors,\n });\n \n throw new Error(errorMessage);\n }\n \n // Re-throw other errors\n throw error;\n }\n }\n \n /**\n * Validate partial configuration (for incremental updates)\n * \n * @param plugin - Plugin metadata\n * @param partialConfig - Partial configuration to validate\n * @returns Validated partial configuration\n */\n validatePartialConfig<T = any>(plugin: PluginMetadata, partialConfig: any): Partial<T> {\n if (!plugin.configSchema) {\n return partialConfig as Partial<T>;\n }\n \n try {\n // Use Zod's partial() method for partial validation\n // Cast to ZodObject to access partial() method\n const partialSchema = (plugin.configSchema as any).partial();\n const validatedConfig = partialSchema.parse(partialConfig);\n \n this.logger.debug(`✅ Partial config validated: ${plugin.name}`);\n return validatedConfig as Partial<T>;\n } catch (error) {\n if (error instanceof z.ZodError) {\n const formattedErrors = this.formatZodErrors(error);\n const errorMessage = [\n `Plugin ${plugin.name} partial configuration validation failed:`,\n ...formattedErrors.map(e => ` - ${e.path}: ${e.message}`),\n ].join('\\n');\n \n throw new Error(errorMessage);\n }\n \n throw error;\n }\n }\n \n /**\n * Get default configuration from schema\n * \n * @param plugin - Plugin metadata\n * @returns Default configuration object\n */\n getDefaultConfig<T = any>(plugin: PluginMetadata): T | undefined {\n if (!plugin.configSchema) {\n return undefined;\n }\n \n try {\n // Parse empty object to get defaults\n const defaults = plugin.configSchema.parse({});\n this.logger.debug(`Default config extracted: ${plugin.name}`);\n return defaults as T;\n } catch (error) {\n // Schema may require some fields - return undefined\n this.logger.debug(`No default config available: ${plugin.name}`);\n return undefined;\n }\n }\n \n /**\n * Check if configuration is valid without throwing\n * \n * @param plugin - Plugin metadata\n * @param config - Configuration to check\n * @returns True if valid, false otherwise\n */\n isConfigValid(plugin: PluginMetadata, config: any): boolean {\n if (!plugin.configSchema) {\n return true;\n }\n \n const result = plugin.configSchema.safeParse(config);\n return result.success;\n }\n \n /**\n * Get configuration errors without throwing\n * \n * @param plugin - Plugin metadata\n * @param config - Configuration to check\n * @returns Array of validation errors, or empty array if valid\n */\n getConfigErrors(plugin: PluginMetadata, config: any): Array<{path: string; message: string}> {\n if (!plugin.configSchema) {\n return [];\n }\n \n const result = plugin.configSchema.safeParse(config);\n \n if (result.success) {\n return [];\n }\n \n return this.formatZodErrors(result.error);\n }\n \n // Private methods\n \n private formatZodErrors(error: z.ZodError<any>): Array<{path: string; message: string}> {\n return error.issues.map((e: z.ZodIssue) => ({\n path: e.path.join('.') || 'root',\n message: e.message,\n }));\n }\n}\n\n/**\n * Create a plugin config validator\n * \n * @param logger - Logger instance\n * @returns Plugin config validator\n */\nexport function createPluginConfigValidator(logger: Logger): PluginConfigValidator {\n return new PluginConfigValidator(logger);\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Plugin artifact signing & verification (ADR-0025 §3.4–§3.7, framework F3).\n *\n * This is the CANONICAL Ed25519 detached-signature contract shared by the\n * whole plugin distribution pipeline. It is intentionally byte-for-byte\n * compatible with the cloud control plane's `package-signing.ts` so the\n * two never drift:\n *\n * - signature string format: `ed25519:<keyId>:<base64url(signature)>`\n * - publisher signature: Ed25519 over the raw `.osplugin` artifact bytes,\n * produced by `os plugin sign`, verified by cloud at publish time and by\n * the runtime when it materializes the artifact.\n * - platform counter-signature: Ed25519 over {@link counterSignPayload}\n * (the version identity), produced by cloud at approval, verified by the\n * runtime at load time as the marketplace's \"reviewed + approved\" attest.\n *\n * Algorithm: Ed25519 via node:crypto (`sign(null, …)` / `verify(null, …)`):\n * short, deterministic, no padding ambiguity. The `keyId` is an opaque\n * rotation handle used to resolve the verifying public key.\n *\n * The two trust chains the runtime checks before loading a third-party\n * plugin are combined in {@link verifyPluginArtifact}.\n */\n\nimport {\n sign as cryptoSign,\n verify as cryptoVerify,\n createPublicKey,\n createPrivateKey,\n generateKeyPairSync,\n type KeyObject,\n} from 'node:crypto';\n\nexport const SIGNATURE_ALG = 'ed25519';\nconst SIG_PREFIX = 'ed25519:';\n\nexport type KeyInput = string | KeyObject;\n\nfunction toPrivateKey(key: KeyInput): KeyObject {\n return typeof key === 'string' ? createPrivateKey(key) : key;\n}\nfunction toPublicKey(key: KeyInput): KeyObject {\n return typeof key === 'string' ? createPublicKey(key) : key;\n}\nfunction toBytes(payload: string | Uint8Array): Uint8Array {\n return typeof payload === 'string' ? new TextEncoder().encode(payload) : payload;\n}\n\n/** Generate an Ed25519 keypair as PEM strings (publisher bootstrap / tests). */\nexport function generateEd25519KeyPair(): { publicKeyPem: string; privateKeyPem: string } {\n const { publicKey, privateKey } = generateKeyPairSync('ed25519');\n return {\n publicKeyPem: publicKey.export({ type: 'spki', format: 'pem' }).toString(),\n privateKeyPem: privateKey.export({ type: 'pkcs8', format: 'pem' }).toString(),\n };\n}\n\n/**\n * Sign `payload` with an Ed25519 private key, returning the formatted\n * signature string `ed25519:<keyId>:<base64url(sig)>`.\n */\nexport function signPayload(\n payload: string | Uint8Array,\n privateKey: KeyInput,\n keyId = 'default',\n): string {\n if (keyId.includes(':')) throw new Error('keyId must not contain \":\"');\n const sig = cryptoSign(null, toBytes(payload), toPrivateKey(privateKey));\n return `${SIG_PREFIX}${keyId}:${sig.toString('base64url')}`;\n}\n\nexport interface ParsedSignature {\n alg: 'ed25519';\n keyId: string;\n signature: Uint8Array;\n}\n\n/** Parse an `ed25519:<keyId>:<base64url>` signature string. Returns null if malformed. */\nexport function parseSignature(s: string | undefined | null): ParsedSignature | null {\n if (typeof s !== 'string' || !s.startsWith(SIG_PREFIX)) return null;\n const rest = s.slice(SIG_PREFIX.length);\n const idx = rest.indexOf(':');\n if (idx <= 0) return null;\n const keyId = rest.slice(0, idx);\n const b64 = rest.slice(idx + 1);\n if (!keyId || !b64) return null;\n try {\n return { alg: 'ed25519', keyId, signature: new Uint8Array(Buffer.from(b64, 'base64url')) };\n } catch {\n return null;\n }\n}\n\n/** Verify a formatted signature string over `payload` with the given public key. */\nexport function verifyPayload(\n payload: string | Uint8Array,\n signature: string,\n publicKey: KeyInput,\n): boolean {\n const parsed = parseSignature(signature);\n if (!parsed) return false;\n try {\n return cryptoVerify(null, toBytes(payload), toPublicKey(publicKey), parsed.signature);\n } catch {\n return false;\n }\n}\n\n/**\n * Canonical payload the platform counter-signs at approval. Binds the\n * attestation to the version identity + artifact location + the publisher\n * signature (which itself binds the artifact bytes). MUST match the cloud\n * control plane's `counterSignPayload` exactly.\n */\nexport function counterSignPayload(version: {\n package_id: string;\n version: string;\n blob_key?: string | null;\n signature?: string | null;\n}): string {\n return [\n version.package_id,\n version.version,\n version.blob_key ?? '',\n version.signature ?? '',\n ].join('\\n');\n}\n\nexport interface PublisherVerifyResult {\n /** Whether loading may proceed on signature grounds. */\n ok: boolean;\n /** True when a signature was present AND cryptographically verified. */\n verified: boolean;\n reason?: string;\n}\n\n/**\n * Verify a publisher signature over the raw artifact bytes. `getPublicKey`\n * resolves the verifying key from the signature's embedded keyId.\n *\n * Mirrors cloud's publish-time policy:\n * - no signature → ok, verified=false (caller decides via trust tier).\n * - malformed / fails verification → NOT ok.\n * - unknown keyId → NOT ok (never silently trust).\n */\nexport async function verifyPublisherSignature(\n args: { artifact: Uint8Array; signature?: string | null },\n getPublicKey?: (keyId: string) => Promise<KeyInput | null> | KeyInput | null,\n): Promise<PublisherVerifyResult> {\n const sig = args.signature;\n if (!sig) return { ok: true, verified: false, reason: 'no signature supplied' };\n\n const parsed = parseSignature(sig);\n if (!parsed) return { ok: false, verified: false, reason: 'signature is malformed' };\n\n if (!getPublicKey) {\n return { ok: true, verified: false, reason: 'no publisher key registry configured' };\n }\n\n const pub = await getPublicKey(parsed.keyId);\n if (!pub) return { ok: false, verified: false, reason: `unknown publisher key '${parsed.keyId}'` };\n\n return verifyPayload(args.artifact, sig, pub)\n ? { ok: true, verified: true }\n : { ok: false, verified: false, reason: 'publisher signature does not match artifact' };\n}\n\n/** Verify a platform counter-signature against the version identity + platform public key. */\nexport function verifyPlatformSignature(\n version: {\n package_id: string;\n version: string;\n blob_key?: string | null;\n signature?: string | null;\n platform_signature?: string | null;\n },\n platformPublicKey: KeyInput,\n): boolean {\n if (!version.platform_signature) return false;\n return verifyPayload(counterSignPayload(version), version.platform_signature, platformPublicKey);\n}\n\nexport interface PluginArtifactVerifyResult {\n /** Overall verdict: both required chains satisfied under the given policy. */\n ok: boolean;\n publisherVerified: boolean;\n platformVerified: boolean;\n reason?: string;\n}\n\n/**\n * Verify both trust chains for a downloaded plugin artifact at load time\n * (ADR-0025 §3.7). The platform counter-signature is the authoritative\n * marketplace attestation; the publisher signature additionally binds the\n * exact bytes. `requirePlatform` (default true) rejects artifacts that lack\n * a valid platform counter-sign — set false for first-party / local builds.\n */\nexport async function verifyPluginArtifact(\n input: {\n artifact: Uint8Array;\n version: {\n package_id: string;\n version: string;\n blob_key?: string | null;\n signature?: string | null;\n platform_signature?: string | null;\n };\n },\n keys: {\n platformPublicKey?: KeyInput;\n getPublisherPublicKey?: (keyId: string) => Promise<KeyInput | null> | KeyInput | null;\n requirePlatform?: boolean;\n },\n): Promise<PluginArtifactVerifyResult> {\n const requirePlatform = keys.requirePlatform ?? true;\n\n const publisher = await verifyPublisherSignature(\n { artifact: input.artifact, signature: input.version.signature },\n keys.getPublisherPublicKey,\n );\n if (!publisher.ok) {\n return { ok: false, publisherVerified: false, platformVerified: false, reason: publisher.reason };\n }\n\n let platformVerified = false;\n if (keys.platformPublicKey) {\n platformVerified = verifyPlatformSignature(input.version, keys.platformPublicKey);\n }\n if (requirePlatform && !platformVerified) {\n return {\n ok: false,\n publisherVerified: publisher.verified,\n platformVerified,\n reason: keys.platformPublicKey\n ? 'platform counter-signature missing or invalid'\n : 'no platform public key configured',\n };\n }\n\n return { ok: true, publisherVerified: publisher.verified, platformVerified };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Plugin, PluginContext } from './types.js';\nimport type { Logger } from '@objectstack/spec/contracts';\nimport { z } from 'zod';\nimport { PluginConfigValidator } from './security/plugin-config-validator.js';\nimport { parseSignature } from './security/plugin-artifact-signature.js';\n\n/**\n * Service Lifecycle Types\n * Defines how services are instantiated and managed\n */\nexport enum ServiceLifecycle {\n /** Single instance shared across all requests */\n SINGLETON = 'singleton',\n /** New instance created for each request */\n TRANSIENT = 'transient',\n /** New instance per scope (e.g., per HTTP request) */\n SCOPED = 'scoped',\n}\n\n/**\n * Service Factory\n * Function that creates a service instance\n */\nexport type ServiceFactory<T = any> = (ctx: PluginContext, scopeId?: string) => T | Promise<T>;\n\n/**\n * Service Registration Options\n */\nexport interface ServiceRegistration {\n name: string;\n factory: ServiceFactory;\n lifecycle: ServiceLifecycle;\n dependencies?: string[];\n}\n\n/**\n * Plugin Metadata with Enhanced Features\n */\nexport interface PluginMetadata extends Plugin {\n /** Semantic version (e.g., \"1.0.0\") */\n version: string;\n \n /** Configuration schema for validation */\n configSchema?: z.ZodSchema;\n \n /** Plugin signature for security verification */\n signature?: string;\n \n /** Plugin health check function */\n healthCheck?(): Promise<PluginHealthStatus>;\n \n /** Startup timeout in milliseconds (default: 30000) */\n startupTimeout?: number;\n \n /** Whether plugin supports hot reload */\n hotReloadable?: boolean;\n}\n\n/**\n * Plugin Health Status\n */\nexport interface PluginHealthStatus {\n healthy: boolean;\n message?: string;\n details?: Record<string, any>;\n lastCheck?: Date;\n}\n\n/**\n * Plugin Load Result\n */\nexport interface PluginLoadResult {\n success: boolean;\n plugin?: PluginMetadata;\n error?: Error;\n loadTime?: number;\n}\n\n/**\n * Plugin Startup Result\n */\nexport interface PluginStartupResult {\n success: boolean;\n pluginName: string;\n startTime?: number;\n error?: Error;\n timedOut?: boolean;\n}\n\n/**\n * Version Compatibility Result\n */\nexport interface VersionCompatibility {\n compatible: boolean;\n pluginVersion: string;\n requiredVersion?: string;\n message?: string;\n}\n\n/**\n * Enhanced Plugin Loader\n * Provides advanced plugin loading capabilities with validation, security, and lifecycle management\n */\nexport class PluginLoader {\n private logger: Logger;\n private context?: PluginContext;\n private configValidator: PluginConfigValidator;\n private loadedPlugins: Map<string, PluginMetadata> = new Map();\n private serviceFactories: Map<string, ServiceRegistration> = new Map();\n private serviceInstances: Map<string, any> = new Map();\n private scopedServices: Map<string, Map<string, any>> = new Map();\n private creating: Set<string> = new Set();\n\n constructor(logger: Logger) {\n this.logger = logger;\n this.configValidator = new PluginConfigValidator(logger);\n }\n\n /**\n * Set the plugin context for service factories\n */\n setContext(context: PluginContext): void {\n this.context = context;\n }\n\n /**\n * Get a synchronous service instance if it exists (Sync Helper)\n */\n getServiceInstance<T>(name: string): T | undefined {\n return this.serviceInstances.get(name) as T;\n }\n\n /**\n * Load a plugin asynchronously with validation\n */\n async loadPlugin(plugin: Plugin): Promise<PluginLoadResult> {\n const startTime = Date.now();\n \n try {\n this.logger.info(`Loading plugin: ${plugin.name}`);\n \n // Convert to PluginMetadata\n const metadata = this.toPluginMetadata(plugin);\n \n // Validate plugin structure\n this.validatePluginStructure(metadata);\n \n // Check version compatibility\n const versionCheck = this.checkVersionCompatibility(metadata);\n if (!versionCheck.compatible) {\n throw new Error(`Version incompatible: ${versionCheck.message}`);\n }\n \n // Validate configuration if schema is provided\n if (metadata.configSchema) {\n this.validatePluginConfig(metadata);\n }\n \n // Verify signature if provided\n if (metadata.signature) {\n await this.verifyPluginSignature(metadata);\n }\n \n // Store loaded plugin\n this.loadedPlugins.set(metadata.name, metadata);\n \n const loadTime = Date.now() - startTime;\n this.logger.info(`Plugin loaded: ${plugin.name} (${loadTime}ms)`);\n \n return {\n success: true,\n plugin: metadata,\n loadTime,\n };\n } catch (error) {\n this.logger.error(`Failed to load plugin: ${plugin.name}`, error as Error);\n return {\n success: false,\n error: error as Error,\n loadTime: Date.now() - startTime,\n };\n }\n }\n\n /**\n * Register a service with factory function\n */\n registerServiceFactory(registration: ServiceRegistration): void {\n if (this.serviceFactories.has(registration.name)) {\n throw new Error(`Service factory '${registration.name}' already registered`);\n }\n \n this.serviceFactories.set(registration.name, registration);\n this.logger.debug(`Service factory registered: ${registration.name} (${registration.lifecycle})`);\n }\n\n /**\n * Get or create a service instance based on lifecycle type\n */\n async getService<T>(name: string, scopeId?: string): Promise<T> {\n const registration = this.serviceFactories.get(name);\n \n if (!registration) {\n // Fall back to static service instances\n const instance = this.serviceInstances.get(name);\n if (!instance) {\n throw new Error(`Service '${name}' not found`);\n }\n return instance as T;\n }\n \n switch (registration.lifecycle) {\n case ServiceLifecycle.SINGLETON:\n return await this.getSingletonService<T>(registration);\n \n case ServiceLifecycle.TRANSIENT:\n return await this.createTransientService<T>(registration);\n \n case ServiceLifecycle.SCOPED:\n if (!scopeId) {\n throw new Error(`Scope ID required for scoped service '${name}'`);\n }\n return await this.getScopedService<T>(registration, scopeId);\n \n default:\n throw new Error(`Unknown service lifecycle: ${registration.lifecycle}`);\n }\n }\n\n /**\n * Register a static service instance (legacy support)\n */\n registerService(name: string, service: any): void {\n if (this.serviceInstances.has(name)) {\n throw new Error(`Service '${name}' already registered`);\n }\n this.serviceInstances.set(name, service);\n }\n\n /**\n * Replace an existing service instance.\n * Used by optimization plugins to swap kernel internals.\n * @throws Error if service does not exist\n */\n replaceService(name: string, service: any): void {\n if (!this.hasService(name)) {\n throw new Error(`Service '${name}' not found`);\n }\n this.serviceInstances.set(name, service);\n }\n\n /**\n * Check if a service is registered (either as instance or factory)\n */\n hasService(name: string): boolean {\n return this.serviceInstances.has(name) || this.serviceFactories.has(name);\n }\n\n /**\n * Detect circular dependencies in service factories\n * Note: This only detects cycles in service dependencies, not plugin dependencies.\n * Plugin dependency cycles are detected in the kernel's resolveDependencies method.\n */\n detectCircularDependencies(): string[] {\n const cycles: string[] = [];\n const visited = new Set<string>();\n const visiting = new Set<string>();\n \n const visit = (serviceName: string, path: string[] = []) => {\n if (visiting.has(serviceName)) {\n const cycle = [...path, serviceName].join(' -> ');\n cycles.push(cycle);\n return;\n }\n \n if (visited.has(serviceName)) {\n return;\n }\n \n visiting.add(serviceName);\n \n const registration = this.serviceFactories.get(serviceName);\n if (registration?.dependencies) {\n for (const dep of registration.dependencies) {\n visit(dep, [...path, serviceName]);\n }\n }\n \n visiting.delete(serviceName);\n visited.add(serviceName);\n };\n \n for (const serviceName of this.serviceFactories.keys()) {\n visit(serviceName);\n }\n \n return cycles;\n }\n\n /**\n * Check plugin health\n */\n async checkPluginHealth(pluginName: string): Promise<PluginHealthStatus> {\n const plugin = this.loadedPlugins.get(pluginName);\n \n if (!plugin) {\n return {\n healthy: false,\n message: 'Plugin not found',\n lastCheck: new Date(),\n };\n }\n \n if (!plugin.healthCheck) {\n return {\n healthy: true,\n message: 'No health check defined',\n lastCheck: new Date(),\n };\n }\n \n try {\n const status = await plugin.healthCheck();\n return {\n ...status,\n lastCheck: new Date(),\n };\n } catch (error) {\n return {\n healthy: false,\n message: `Health check failed: ${(error as Error).message}`,\n lastCheck: new Date(),\n };\n }\n }\n\n /**\n * Clear scoped services for a scope\n */\n clearScope(scopeId: string): void {\n this.scopedServices.delete(scopeId);\n this.logger.debug(`Cleared scope: ${scopeId}`);\n }\n\n /**\n * Get all loaded plugins\n */\n getLoadedPlugins(): Map<string, PluginMetadata> {\n return new Map(this.loadedPlugins);\n }\n\n // Private helper methods\n\n private toPluginMetadata(plugin: Plugin): PluginMetadata {\n // Fix: Do not use object spread {...plugin} as it destroys the prototype chain for Class-based plugins.\n // Instead, cast the original object and inject default values if missing.\n const metadata = plugin as PluginMetadata;\n \n if (!metadata.version) {\n metadata.version = '0.0.0';\n }\n \n return metadata;\n }\n\n private validatePluginStructure(plugin: PluginMetadata): void {\n if (!plugin.name) {\n throw new Error('Plugin name is required');\n }\n \n if (!plugin.init) {\n throw new Error('Plugin init function is required');\n }\n \n if (!this.isValidSemanticVersion(plugin.version)) {\n throw new Error(`Invalid semantic version: ${plugin.version}`);\n }\n }\n\n private checkVersionCompatibility(plugin: PluginMetadata): VersionCompatibility {\n // Basic semantic version compatibility check\n // In a real implementation, this would check against kernel version\n const version = plugin.version;\n \n if (!this.isValidSemanticVersion(version)) {\n return {\n compatible: false,\n pluginVersion: version,\n message: 'Invalid semantic version format',\n };\n }\n \n return {\n compatible: true,\n pluginVersion: version,\n };\n }\n\n private isValidSemanticVersion(version: string): boolean {\n const semverRegex = /^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?(\\+[a-zA-Z0-9.-]+)?$/;\n return semverRegex.test(version);\n }\n\n private validatePluginConfig(plugin: PluginMetadata, config?: any): void {\n if (!plugin.configSchema) {\n return;\n }\n\n if (config === undefined) {\n // In loadPlugin, we often don't have the config yet.\n // We skip validation here or valid against empty object if schema allows?\n // For now, let's keep the logging behavior but note it's delegating\n this.logger.debug(`Plugin ${plugin.name} has configuration schema (config validation postponed)`);\n return;\n }\n\n this.configValidator.validatePluginConfig(plugin, config);\n }\n\n private async verifyPluginSignature(plugin: PluginMetadata): Promise<void> {\n if (!plugin.signature) {\n return;\n }\n \n // Cryptographic verification of a third-party plugin's PUBLISHER and\n // PLATFORM signatures is performed against the `.osplugin` artifact\n // bytes + version identity at materialize/install time, by\n // `verifyPluginArtifact` (security/plugin-artifact-signature.ts —\n // ADR-0025 §3.7). By the time a plugin reaches loadPlugin() it is an\n // in-memory module with no artifact bytes, so we cannot re-run the\n // artifact chains here; we only validate that any signature carried\n // on the metadata is well-formed (`ed25519:<keyId>:<base64url>`) and\n // surface its keyId, failing fast on a malformed value.\n const parsed = parseSignature(plugin.signature);\n if (!parsed) {\n throw new Error(\n `Plugin ${plugin.name} carries a malformed signature (expected ed25519:<keyId>:<base64url>)`,\n );\n }\n this.logger.debug(\n `Plugin ${plugin.name} signature well-formed (alg=${parsed.alg}, keyId=${parsed.keyId}); ` +\n `artifact verification occurs at materialize time`,\n );\n }\n\n private async getSingletonService<T>(registration: ServiceRegistration): Promise<T> {\n let instance = this.serviceInstances.get(registration.name);\n \n if (!instance) {\n // Create instance (would need context)\n instance = await this.createServiceInstance(registration);\n this.serviceInstances.set(registration.name, instance);\n this.logger.debug(`Singleton service created: ${registration.name}`);\n }\n \n return instance as T;\n }\n\n private async createTransientService<T>(registration: ServiceRegistration): Promise<T> {\n const instance = await this.createServiceInstance(registration);\n this.logger.debug(`Transient service created: ${registration.name}`);\n return instance as T;\n }\n\n private async getScopedService<T>(registration: ServiceRegistration, scopeId: string): Promise<T> {\n if (!this.scopedServices.has(scopeId)) {\n this.scopedServices.set(scopeId, new Map());\n }\n\n const scope = this.scopedServices.get(scopeId)!;\n let instance = scope.get(registration.name);\n\n if (!instance) {\n instance = await this.createServiceInstance(registration, scopeId);\n scope.set(registration.name, instance);\n this.logger.debug(`Scoped service created: ${registration.name} (scope: ${scopeId})`);\n }\n\n return instance as T;\n }\n\n private async createServiceInstance(registration: ServiceRegistration, scopeId?: string): Promise<any> {\n if (!this.context) {\n throw new Error(`[PluginLoader] Context not set - cannot create service '${registration.name}'`);\n }\n\n if (this.creating.has(registration.name)) {\n throw new Error(`Circular dependency detected: ${Array.from(this.creating).join(' -> ')} -> ${registration.name}`);\n }\n\n this.creating.add(registration.name);\n try {\n return await registration.factory(this.context, scopeId);\n } finally {\n this.creating.delete(registration.name);\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Environment utilities for universal (Node/Browser) compatibility.\n */\n\n// Check if running in a Node.js environment\nexport const isNode = typeof process !== 'undefined' && \n process.versions != null && \n process.versions.node != null;\n\n/**\n * Safely access environment variables\n */\nexport function getEnv(key: string, defaultValue?: string): string | undefined {\n // Node.js\n if (typeof process !== 'undefined' && process.env) {\n return process.env[key] || defaultValue;\n }\n \n // Browser (Vite/Webpack replacement usually handles process.env, \n // but if not, we check safe global access)\n try {\n // @ts-ignore\n if (typeof globalThis !== 'undefined' && globalThis.process?.env) {\n // @ts-ignore\n return globalThis.process.env[key] || defaultValue;\n }\n } catch (e) {\n // Ignore access errors\n }\n \n return defaultValue;\n}\n\n/**\n * Safely exit the process if in Node.js\n */\nexport function safeExit(code: number = 0): void {\n if (isNode) {\n process.exit(code);\n }\n}\n\n/**\n * Safely get memory usage\n */\nexport function getMemoryUsage(): { heapUsed: number; heapTotal: number } {\n if (isNode) {\n return process.memoryUsage();\n }\n return { heapUsed: 0, heapTotal: 0 };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * In-memory Map-backed cache fallback.\n *\n * Implements the ICacheService contract with basic get/set/delete/has/clear\n * and TTL expiry. Used by ObjectKernel as an automatic fallback when no\n * real cache plugin (e.g. Redis) is registered.\n *\n * [#4058] Self-describes as `degraded`, not `stub` (ADR-0076 D12): this is a\n * real cache — it stores, expires, and reports true stats — just process-local\n * and unshared. The non-standard `_fallback: true` it used to carry was read by\n * nothing (`readServiceSelfInfo` reads only `__serviceInfo` — `_dev`, the other\n * marker it knew back then, was itself retired in #4319), so discovery reported\n * it as fully `available`. `handlerReady: false` because\n * no HTTP surface is mounted for `cache` at all — the same reason realtime\n * reports false.\n */\nexport function createMemoryCache() {\n const store = new Map<string, { value: unknown; expires?: number }>();\n let hits = 0;\n let misses = 0;\n return {\n __serviceInfo: {\n status: 'degraded' as const,\n handlerReady: false,\n message: 'In-process Map cache — not shared across instances, lost on restart. Register a cache plugin (e.g. Redis) for a real one.',\n },\n _serviceName: 'cache',\n async get<T = unknown>(key: string): Promise<T | undefined> {\n const entry = store.get(key);\n if (!entry || (entry.expires && Date.now() > entry.expires)) {\n store.delete(key);\n misses++;\n return undefined;\n }\n hits++;\n return entry.value as T;\n },\n async set<T = unknown>(key: string, value: T, ttl?: number): Promise<void> {\n store.set(key, { value, expires: ttl ? Date.now() + ttl * 1000 : undefined });\n },\n async delete(key: string): Promise<boolean> { return store.delete(key); },\n async has(key: string): Promise<boolean> { return store.has(key); },\n async clear(): Promise<void> { store.clear(); },\n async stats() { return { hits, misses, keyCount: store.size }; },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * In-memory publish/subscribe queue fallback.\n *\n * Implements the IQueueService contract with synchronous in-process delivery.\n * Used by ObjectKernel as an automatic fallback when no real queue plugin\n * (e.g. BullMQ / RabbitMQ) is registered.\n *\n * [#4058] `degraded`, not `stub` (ADR-0076 D12): messages really reach real\n * subscribers — synchronously, in-process, with no durability or retry.\n * `getQueueSize()` answering 0 follows from that rather than faking it: nothing\n * is ever buffered. `handlerReady: false` — no HTTP surface exists for `queue`.\n */\nexport function createMemoryQueue() {\n const handlers = new Map<string, Function[]>();\n let msgId = 0;\n return {\n __serviceInfo: {\n status: 'degraded' as const,\n handlerReady: false,\n message: 'Synchronous in-process delivery — no durability, retry, or cross-instance fan-out. Register a queue plugin (e.g. BullMQ) for a real one.',\n },\n _serviceName: 'queue',\n async publish<T = unknown>(queue: string, data: T): Promise<string> {\n const id = `fallback-msg-${++msgId}`;\n const fns = handlers.get(queue) ?? [];\n for (const fn of fns) fn({ id, data, attempts: 1, timestamp: Date.now() });\n return id;\n },\n async subscribe(queue: string, handler: (msg: any) => Promise<void>): Promise<void> {\n handlers.set(queue, [...(handlers.get(queue) ?? []), handler]);\n },\n async unsubscribe(queue: string): Promise<void> { handlers.delete(queue); },\n async getQueueSize(): Promise<number> { return 0; },\n async purge(queue: string): Promise<void> { handlers.delete(queue); },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { normalizeSupportedLocales } from '@objectstack/spec/system';\n\n/**\n * Recursively merge `source` into `target`. Nested plain objects are merged\n * rather than replaced, so multiple plugins can each contribute their own\n * slice of a locale's translations (e.g. `{objects: {account: ...}}` and\n * `{objects: {task: ...}}`) without clobbering one another.\n * Exported for the authored-translation sync (#2591).\n */\nexport function deepMerge(\n target: Record<string, unknown>,\n source: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...target };\n for (const key of Object.keys(source)) {\n const tVal = target[key];\n const sVal = source[key];\n if (\n tVal && sVal\n && typeof tVal === 'object' && !Array.isArray(tVal)\n && typeof sVal === 'object' && !Array.isArray(sVal)\n ) {\n result[key] = deepMerge(\n tVal as Record<string, unknown>,\n sVal as Record<string, unknown>,\n );\n } else {\n result[key] = sVal;\n }\n }\n return result;\n}\n\n/**\n * Resolve a locale code against available locales with fallback.\n *\n * Fallback chain:\n * 1. Exact match (e.g. `zh-CN` → `zh-CN`)\n * 2. Case-insensitive match (e.g. `zh-cn` → `zh-CN`)\n * 3. Base language match (e.g. `zh-CN` → `zh`)\n * 4. Variant expansion (e.g. `zh` → `zh-CN`)\n *\n * Returns the matched locale code, or `undefined` when no match is found.\n */\nexport function resolveLocale(requestedLocale: string, availableLocales: string[]): string | undefined {\n if (availableLocales.length === 0) return undefined;\n\n // 1. Exact match\n if (availableLocales.includes(requestedLocale)) return requestedLocale;\n\n // 2. Case-insensitive match\n const lower = requestedLocale.toLowerCase();\n const caseMatch = availableLocales.find(l => l.toLowerCase() === lower);\n if (caseMatch) return caseMatch;\n\n // 3. Base language match (zh-CN → zh)\n const baseLang = requestedLocale.split('-')[0].toLowerCase();\n const baseMatch = availableLocales.find(l => l.toLowerCase() === baseLang);\n if (baseMatch) return baseMatch;\n\n // 4. Variant expansion (zh → zh-CN, zh-TW, etc. — first match wins)\n const variantMatch = availableLocales.find(l => l.split('-')[0].toLowerCase() === baseLang);\n if (variantMatch) return variantMatch;\n\n return undefined;\n}\n\n/**\n * In-memory i18n service fallback.\n *\n * Implements the II18nService contract with basic translate/load/getLocales\n * operations. Used by ObjectKernel as an automatic fallback when no real\n * i18n plugin (e.g. I18nServicePlugin) is registered.\n *\n * Supports runtime translation loading, locale management, and\n * locale code fallback (e.g. `zh` → `zh-CN`).\n * Does not load files from disk — operates purely in-memory.\n */\nexport function createMemoryI18n() {\n const translations = new Map<string, Record<string, unknown>>();\n // Runtime-AUTHORED overlay (#2591): translations published as `translation`\n // metadata. Kept separate from the static map so a re-sync can REPLACE the\n // whole authored layer (clear-then-reload — deleted keys must not linger),\n // while authored values win over static bundle values on read.\n const authored = new Map<string, Record<string, unknown>>();\n let defaultLocale = 'en';\n // [#7679] The app's DECLARED `i18n.supportedLocales`, injected by\n // `AppPlugin.loadTranslations` the same way `defaultLocale` is. `undefined`\n // means the app declared nothing, which must keep reporting every loaded\n // locale. Held as a read-time filter, never as a prune of `translations`:\n // platform plugins push their bundles at `kernel:ready`, after the app\n // plugin has run, so anything pruned once would grow back.\n let supportedLocales: string[] | undefined;\n\n /**\n * Resolve a dot-notation key from a nested object.\n */\n function resolveKey(data: Record<string, unknown>, key: string): string | undefined {\n const parts = key.split('.');\n let current: unknown = data;\n for (const part of parts) {\n if (current == null || typeof current !== 'object') return undefined;\n current = (current as Record<string, unknown>)[part];\n }\n return typeof current === 'string' ? current : undefined;\n }\n\n /** Merged (static ⊕ authored) view of a single, exact locale key. */\n function mergedLocale(locale: string): Record<string, unknown> | undefined {\n const stat = translations.get(locale);\n const auth = authored.get(locale);\n if (stat && auth) return deepMerge(stat, auth);\n return auth ?? stat;\n }\n\n /**\n * Find translation data for a locale, with fallback resolution.\n */\n function resolveTranslations(locale: string): Record<string, unknown> | undefined {\n // Exact match\n const exact = mergedLocale(locale);\n if (exact) return exact;\n\n // Locale fallback (zh → zh-CN, en-us → en-US, etc.)\n const allLocales = [...new Set([...translations.keys(), ...authored.keys()])];\n const resolved = resolveLocale(locale, allLocales);\n if (resolved) return mergedLocale(resolved);\n\n return undefined;\n }\n\n return {\n // [#4058] `degraded` (ADR-0076 D12): translations, locale fallback and\n // interpolation are all real — what is missing is persistence and the\n // authoring surface service-i18n adds. `handlerReady` left at the\n // `degraded` default (true): the dispatcher's `/i18n` domain does serve\n // this implementation.\n __serviceInfo: {\n status: 'degraded' as const,\n message: 'In-memory translations — real lookup and locale fallback, but nothing is persisted. Register I18nServicePlugin from @objectstack/service-i18n for the full implementation.',\n },\n _serviceName: 'i18n',\n\n t(key: string, locale: string, params?: Record<string, unknown>): string {\n const data = resolveTranslations(locale) ?? mergedLocale(defaultLocale);\n const value = data ? resolveKey(data, key) : undefined;\n if (value == null) return key;\n if (!params) return value;\n // Interpolation format: {{paramName}} — matches FileI18nAdapter convention\n return value.replace(/\\{\\{(\\w+)\\}\\}/g, (_, name) => String(params[name] ?? `{{${name}}}`));\n },\n\n getTranslations(locale: string): Record<string, unknown> {\n return resolveTranslations(locale) ?? {};\n },\n\n loadTranslations(locale: string, data: Record<string, unknown>): void {\n const existing = translations.get(locale);\n if (existing) {\n translations.set(locale, deepMerge(existing, data));\n } else {\n translations.set(locale, { ...data });\n }\n },\n\n /**\n * Replace the ENTIRE runtime-authored translation layer (#2591). Called\n * by the authored-translation sync with the full current set of active\n * `translation` metadata items keyed by locale. Wholesale replacement —\n * not a merge — so deleted items/keys stop resolving on the next sync.\n */\n replaceAuthoredTranslations(byLocale: Record<string, Record<string, unknown>>): void {\n authored.clear();\n for (const [locale, data] of Object.entries(byLocale ?? {})) {\n if (!data || typeof data !== 'object') continue;\n authored.set(locale, { ...data });\n }\n },\n\n /**\n * Report the locales this stack offers.\n *\n * [#7679] When the app declared `i18n.supportedLocales`, that declaration\n * IS the answer — in declared order, and including a declared locale no\n * bundle was ever loaded for (declared-but-unserved). Reporting the\n * declaration rather than an intersection is what gives a client the\n * signal that the locale it is being offered has nothing behind it yet;\n * quietly dropping it would leave the gap invisible on both sides. It is\n * also the only answer that does not depend on how much had loaded by the\n * time this was called.\n *\n * With nothing declared, the loaded set — the behaviour every app that\n * never opted in already has.\n */\n getLocales(): string[] {\n if (supportedLocales) return [...supportedLocales];\n return [...new Set([...translations.keys(), ...authored.keys()])];\n },\n\n /** @see II18nService.setSupportedLocales — [#7679] */\n setSupportedLocales(locales: readonly string[] | undefined): void {\n supportedLocales = normalizeSupportedLocales(locales);\n },\n\n getDefaultLocale(): string {\n return defaultLocale;\n },\n\n setDefaultLocale(locale: string): void {\n defaultLocale = locale;\n },\n };\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * [#7378] The `IMetadataService` register/read argument contract, enforced —\n * the shared guard every shipped implementation of the contract's CRUD members\n * calls, so the maintainer's three-cell ruling has ONE implementation instead\n * of three re-derivations that can drift.\n *\n * Maintainer ruling, 2026-08-12 (#7378, 裁定人:维护者 huangyiirene), quoted\n * verbatim and untranslated:\n *\n * > 1. **Row 1(key 归属)= (c) 响亮拒绝。** `register(type, name, data)` 中\n * > `name` 参数与 `data.name` 不一致时,所有实现统一拒绝并报错定位 ——\n * > 不一致几乎必是作者 bug,任一方向的静默解决都可能把条目放错位置。\n * > 2. **Row 2(objects/object 别名)= 所有实现一个答案,与\n * > `check:meta-type-normalized` 收敛。** 类型名归一化是契约级规则,不是\n * > 各实现自留的民俗。\n * > 3. **Row 3(非对象 data 静默丢弃)= 响亮拒绝(throw)。** 接受后丢失、且\n * > 任何成员都读不回来,无可辩护;拒绝一个实现无法键控的 `data` 与契约\n * > 同样一致。修的是「接受再丢」,不强求「必须存下」。\n *\n * This module hosts rows 1 and 3 ({@link assertMetadataRegisterContract}) and\n * row 2 ({@link canonicalMetadataServiceType}). It lives in `@objectstack/core`\n * because core is the lowest common dependency of the three shipped\n * implementations — `createMemoryMetadata` (this package), `MetadataManager`\n * (`@objectstack/metadata`) and `MetadataFacade` (`@objectstack/objectql`).\n * The contract's own reference double (`packages/spec`) cannot import from\n * here — spec is the dependency root — so its copy of these rules rides the\n * spec-side half of the ruling, tracked on #7378.\n *\n * ## Row 2 — why the fold, and whose direction it is\n *\n * Plural→singular type-name folding is a decided, enforced platform direction,\n * not this module's invention. The owners this converges with, per the ruling's\n * own instruction to read the gate's existing direction first (「实现者先读该\n * 闸门的既有方向再落」):\n *\n * - `canonicalMetaType` (`metadata-protocol/src/protocol.ts`, #4432)\n * canonicalizes every `/meta` request type at the protocol boundary via the\n * same `PLURAL_TO_SINGULAR` map this module reads;\n * - `check:meta-type-normalized` (`scripts/check-meta-type-normalized.mjs`)\n * is the CI gate whose whole job is to refuse a DECISION made on the\n * un-normalized `:type` — its header carries the three authorization\n * bypasses (#3984, #5881, #6241) that made the direction a rule. Its scan\n * surface is `packages/rest/src`; what this module converges with is its\n * DIRECTION: normalize once, at the entry, and let every decision — here,\n * every store key — read the normalized value;\n * - Prime Directive #3: metadata type names are canonically **singular**.\n *\n * Before this ruling, `MetadataManager` and `createMemoryMetadata` keyed their\n * type stores on the raw string, so `register('objects', n, d)` landed in a\n * store `get('object', n)` never read — two stores for one type, differing\n * from `MetadataFacade`, whose `SchemaRegistry` reads alias both spellings.\n * One answer now: the store key is the canonical type.\n *\n * ## Rows 1 and 3 — what the refusals close\n *\n * Row 1: a `data.name` that disagrees with the `name` argument was resolved\n * silently in both directions in shipped code — argument-wins\n * (`MetadataManager`, `createMemoryMetadata`) and document-wins (the\n * pre-ruling `MetadataFacade`) — and either way an author's item could be\n * filed under a key the author never wrote. Refusing is the only answer that\n * cannot misplace the item.\n *\n * Row 3: a `data` that is not a plain object cannot be a metadata document.\n * The pre-ruling `MetadataFacade` accepted such a write and filed it under the\n * literal key `undefined` — readable back through no member (silent loss, the\n * #6725 family) — and the interim fix coerced it into a `{ name, content }`\n * box, which collides with `content` being a REAL authorable field on live\n * metadata types (`doc`, `knowledge_document`). The ruling forbids both:\n * refuse, do not coerce into storability. `null` and arrays are refused with\n * primitives — neither can carry the document identity a metadata store keys\n * on, and `{ ...[a, b] }` is `{ 0: a, 1: b }`, the same corruption one shape\n * over.\n *\n * The executable form of all three rows is `METADATA_ROUNDTRIP_CASES`\n * (`@objectstack/spec/contracts`) replayed by\n * `packages/objectql/src/metadata-service-roundtrip-conformance.test.ts`.\n */\n\n// The `/api` import is TYPE-ONLY on purpose — erased at compile time, so this\n// module makes no runtime demand on that subpath. This module is loaded by\n// every consumer of `@objectstack/core`, and several packages' vitest configs\n// alias the bare `@objectstack/spec` specifier to `spec/src/index.ts` (a FILE)\n// with per-subpath entries spelled out above it; an alias list matches by\n// PREFIX, so any subpath NOT spelled out resolves under the file and dies with\n// ENOTDIR at import time (measured: `@objectstack/plugin-hono-server` and\n// `@objectstack/driver-memory`, 39 test files dead at load between them). The\n// typed literal below keeps the closed-set compile check without the runtime\n// import — the `packages/spec/src/contracts/storage-service.ts` pattern.\n// `/shared` cannot get the same treatment: `pluralToSingular` is a runtime\n// value and its map has ONE owner (#7378 row 2 — copying it here would be the\n// per-implementation folk normalization the ruling forbids), so the consumer\n// configs carry a `/shared` alias entry instead.\nimport type { StandardErrorCode } from '@objectstack/spec/api';\nimport { pluralToSingular } from '@objectstack/spec/shared';\n\n/** The standard catalog's generic argument-validation code, type-checked against the closed set. */\nconst REGISTER_REFUSAL_CODE: StandardErrorCode = 'VALIDATION_ERROR';\n\n/**\n * The canonical spelling an `IMetadataService` type store is keyed on\n * (#7378 row 2). Folds a plural manifest spelling to the singular metadata\n * type name (`'objects'` → `'object'`, `'views'` → `'view'`, …) through the\n * platform's one plural↔singular map (`PLURAL_TO_SINGULAR`,\n * `@objectstack/spec/shared`); a name with no plural mapping — which includes\n * every canonical singular type — passes through unchanged.\n */\nexport function canonicalMetadataServiceType(type: string): string {\n return pluralToSingular(type);\n}\n\n/**\n * An ADR-0112-enveloped refusal (`code` + `status` on the error), so a caller\n * — and a rejection-class test — can assert the refusal rather than merely\n * \"it threw\". `VALIDATION_ERROR` is the standard catalog's generic\n * argument-validation code; the ledger's own guidance is to use the standard\n * catalog rather than register a synonym for a generic condition.\n */\nfunction registerRefusal(message: string): Error & { code: string; status: number } {\n const err = new Error(message) as Error & { code: string; status: number };\n err.code = REGISTER_REFUSAL_CODE;\n err.status = 400;\n return err;\n}\n\n/**\n * Enforce rows 1 and 3 of the #7378 ruling on a\n * `register(type, name, data)` payload — call it before the first store write,\n * so a refusal writes nothing anywhere.\n *\n * Refuses, with a locating `VALIDATION_ERROR` (status 400):\n *\n * - **a non-document `data`** (row 3): anything that is not a plain object —\n * primitives, `null`, arrays. The contract declares `data: unknown`, so\n * this is a runtime refusal, not a type error;\n * - **a `data.name` that disagrees with the `name` argument** (row 1), in\n * either direction. A document with NO `name` of its own is fine — the\n * argument is the key, and there is no disagreement to refuse.\n *\n * Deliberately NOT called by `registerInMemory`: that optional member is a\n * boot-time seeding primitive outside the ruled surface (the ruling names\n * `register`), and its callers hand it artefacts whose shape source control\n * owns. It shares the row-2 canonical fold — a store key is a store fact, not\n * a per-member choice — just not the refusals.\n */\nexport function assertMetadataRegisterContract(\n type: string,\n name: string,\n data: unknown,\n): asserts data is Record<string, unknown> {\n if (typeof data !== 'object' || data === null || Array.isArray(data)) {\n const shape = data === null ? 'null' : Array.isArray(data) ? 'an array' : `a ${typeof data}`;\n throw registerRefusal(\n `IMetadataService.register('${type}', '${name}'): data is ${shape}, not a metadata document. ` +\n `register() stores plain-object documents only — accepting a value the service cannot key was measured as ` +\n `accept-then-drop on document-keyed stores (#7378 row 3: refuse loudly, never coerce into storability). ` +\n `Wrap the value in a document object whose shape the '${type}' type's schema accepts, or store it under a type that declares one.`,\n );\n }\n const documentName = (data as { name?: unknown }).name;\n if (documentName !== undefined && documentName !== name) {\n throw registerRefusal(\n `IMetadataService.register('${type}', '${name}'): data.name is '${String(documentName)}', which disagrees with the ` +\n `name argument '${name}'. A disagreement is almost always an authoring bug, and resolving it silently in either ` +\n `direction can file the item under a key the caller never wrote (#7378 row 1: refuse loudly, locate the mismatch). ` +\n `Register under one name: pass the intended key as the argument and make data.name match it, or omit data.name.`,\n );\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport {\n assertMetadataRegisterContract,\n canonicalMetadataServiceType,\n} from '../metadata-service-contract.js';\n\n/**\n * In-memory metadata service fallback.\n *\n * Implements the IMetadataService contract with a simple Map-of-Maps store.\n * Used by ObjectKernel as an automatic fallback when no real metadata plugin\n * (e.g. MetadataPlugin with file-system persistence) is registered.\n *\n * [#7378] Carries the ruled register/read argument contract\n * (`../metadata-service-contract.ts` — the ruling is quoted there):\n * `register` refuses a `data.name` that disagrees with the `name` argument and\n * refuses a non-document `data` (rows 1/3), and every type store is keyed on\n * the CANONICAL type (row 2), so `register('objects', n, d)` and\n * `get('object', n)` address one store rather than two.\n */\nexport function createMemoryMetadata() {\n // canonical type -> name -> data\n const store = new Map<string, Map<string, any>>();\n\n // [#7378 row 2] The fold lives on the single accessor every member reads\n // and writes through, so no member can address a raw-spelling store.\n function getTypeMap(type: string): Map<string, any> {\n const canonical = canonicalMetadataServiceType(type);\n let map = store.get(canonical);\n if (!map) {\n map = new Map();\n store.set(canonical, map);\n }\n return map;\n }\n\n return {\n // [#4058] `degraded` (ADR-0076 D12): the registry is real — everything\n // registered is listable and readable back — it simply never reaches disk\n // or a database. `handlerReady` keeps the `degraded` default (true): the\n // dispatcher's `/meta` domain serves this implementation.\n __serviceInfo: {\n status: 'degraded' as const,\n message: 'In-memory metadata registry — real reads and writes, no persistence (lost on restart). Register MetadataPlugin for a persisted registry.',\n },\n _serviceName: 'metadata',\n async register(type: string, name: string, data: any): Promise<void> {\n // [#7378 rows 1/3] Refuse — before the store is touched — a data.name\n // that disagrees with the name argument, and a non-document data. The\n // guard's own header carries the ruling and the reasons.\n assertMetadataRegisterContract(type, name, data);\n getTypeMap(type).set(name, data);\n },\n // Mirror MetadataManager.registerInMemory (synchronous, no persistence).\n // AppPlugin gates code-defined-datasource / stack-RBAC registration on\n // `typeof metadata.registerInMemory === 'function'` (it must register\n // GitOps-managed artefacts *listably* but never persist them). Without this\n // method the guard was false on the host-config / standalone boot path —\n // where this fallback (not MetadataPlugin) provides the `metadata` service —\n // so `defineStack({ datasources })` entries silently never reached the\n // registry and were absent from GET /api/v1/datasources and\n // GET /api/v1/meta/datasource (ADR-0015 §18). This store is already\n // in-memory only, so registerInMemory and register share a store — but\n // NOT the [#7378] refusals: the ruling names `register`, and this member\n // is a boot-time seeding primitive for source-control-owned artefacts\n // (see assertMetadataRegisterContract's header for the boundary). It does\n // share the row-2 canonical type fold, via getTypeMap.\n registerInMemory(type: string, name: string, data: any): void {\n getTypeMap(type).set(name, data);\n },\n async get(type: string, name: string): Promise<any> {\n return getTypeMap(type).get(name);\n },\n async list(type: string): Promise<any[]> {\n return Array.from(getTypeMap(type).values());\n },\n async unregister(type: string, name: string): Promise<void> {\n getTypeMap(type).delete(name);\n },\n async exists(type: string, name: string): Promise<boolean> {\n return getTypeMap(type).has(name);\n },\n async listNames(type: string): Promise<string[]> {\n return Array.from(getTypeMap(type).keys());\n },\n async getObject(name: string): Promise<any> {\n return getTypeMap('object').get(name);\n },\n async listObjects(): Promise<any[]> {\n return Array.from(getTypeMap('object').values());\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * In-memory job registry — schedule/cancel/trigger bookkeeping with NO timer.\n *\n * [#10746] NOT pre-injected by ObjectKernel any more (it used to be, via\n * `CORE_FALLBACK_FACTORIES`): a fallback must not fake capability (maintainer\n * ruling 2026-08-22). Advertising a `schedule()` that records and never fires\n * made every \"prefer the platform job service, else own a timer\" consumer\n * take the job-service branch and then silently never run. The export remains\n * for embedders who deliberately want a manual-trigger job registry — e.g. in\n * tests that drive handlers via `trigger()` — and have read this docblock.\n *\n * [#4058] `degraded` (ADR-0076 D12), with the missing half named in the\n * message rather than left for a deployer to discover: `trigger()` really runs\n * the registered handler, but nothing here owns a timer, so a `schedule()`d job\n * NEVER fires on its own. That is reduced capability, not fabricated output —\n * no call returns a made-up answer. `handlerReady: false`: no HTTP surface.\n */\nexport function createMemoryJob() {\n const jobs = new Map<string, any>();\n return {\n __serviceInfo: {\n status: 'degraded' as const,\n handlerReady: false,\n message: 'In-process job registry — trigger() runs handlers, but scheduled jobs never fire on their own (no timer). Register a job plugin (e.g. Agenda) for real scheduling.',\n },\n _serviceName: 'job',\n async schedule(name: string, schedule: any, handler: any): Promise<void> { jobs.set(name, { schedule, handler }); },\n async cancel(name: string): Promise<void> { jobs.delete(name); },\n async trigger(name: string, data?: unknown): Promise<void> {\n const job = jobs.get(name);\n if (job?.handler) await job.handler({ jobId: name, data });\n },\n async getExecutions(): Promise<any[]> { return []; },\n async listJobs(): Promise<string[]> { return [...jobs.keys()]; },\n };\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Runtime-authored translation sync (#2591).\n *\n * Translations authored in the Studio persist as `translation` sys_metadata\n * rows (`allowRuntimeCreate: true`), but historically only STATIC bundles\n * (app `bundle.translations`, plugin `translations/`) were ever loaded into\n * the i18n runtime — a published translation was a dead-end on publish AND\n * after a restart.\n *\n * This module is the single shared implementation of the fix, wired by both\n * hosts an `i18n` service can come from:\n * - the runtime's AppPlugin (covers the kernel's in-memory fallback — the\n * dev/standalone reality) and\n * - @objectstack/service-i18n's I18nServicePlugin (the file-based adapter).\n * Both adapters expose `replaceAuthoredTranslations(byLocale)`; the sync\n * computes the full authored layer from the rows and REPLACES it wholesale\n * (clear-then-reload), so deleted items/keys stop resolving.\n *\n * Item payload is a single-locale `TranslationItem` — the same `objects.`\n * groups the file-authored bundles use, plus the `locale` it translates\n * (#3778; before that, this type was registered against an object-first\n * `o.<object>` dialect no resolver read, so an authored translation saved\n * cleanly and rendered nothing). Locale resolution: the top-level `locale`,\n * then the item name when it looks like a BCP-47 tag — the name fallback\n * covers rows written before `locale` became required. Rows still carrying\n * the retired shape are skipped with a warning naming the row, since their\n * content can never resolve. Multiple items on one locale deep-merge in name\n * order (deterministic).\n *\n * Trigger points (wired by {@link wireAuthoredTranslationSync}):\n * • `kernel:ready` — cold-boot coverage;\n * • `metadata:reloaded` — publish-while-running coverage (#2576);\n * • protocol `onMetadataMutation` — direct-active saves / deletes of\n * `translation` rows that don't go through a package publish (#2588's\n * mutation stream).\n *\n * Rows are read straight from `sys_metadata` through the engine — the same\n * discipline as the authored-hook re-sync (#2588): env-scoped kernels\n * surface authored rows nowhere else, and the i18n map is process-wide so\n * rows are taken across all organizations. Best-effort: a failed read keeps\n * the currently applied authored layer.\n */\n\nimport { LEGACY_OBJECT_FIRST_KEYS } from '@objectstack/spec/system';\nimport type { IDataEngine } from '@objectstack/spec/contracts';\n\nimport { deepMerge } from './memory-i18n.js';\n\ntype AnyRecord = Record<string, any>;\n\ninterface MinimalCtx {\n logger: { debug?: (...a: any[]) => void; info?: (...a: any[]) => void; warn?: (...a: any[]) => void };\n getService(name: string): any;\n hook?(name: string, fn: () => Promise<void> | void): void;\n}\n\n/**\n * Ownership marker: several plugins may wire the sync against the same\n * kernel (AppPlugin AND I18nServicePlugin on a production server). The first\n * wirer to touch a given i18n service instance claims it; later wirers\n * no-op, so the layer is computed once per change instead of N times.\n */\nconst OWNER_PROP = '__authoredTranslationSyncOwner';\n\n/**\n * The `i18n` slot as this wirer uses it: the authored-layer replace seam, plus\n * the ownership marker stamped on the instance.\n *\n * [#4251] `replaceAuthoredTranslations` is NOT on `II18nService` — it is the\n * authored-overlay seam service-i18n grew for this sync, and every call site\n * probes for it. `OWNER_PROP` is this module's own marker, stamped on whatever\n * object occupies the slot so two wirers cannot both drive one instance.\n * Declared here rather than erased to `any` so both facts stay legible: what\n * the slot must supply, and what this module writes onto it.\n */\ninterface AuthoredTranslationSink {\n replaceAuthoredTranslations(layer: Record<string, unknown>): void;\n [OWNER_PROP]?: symbol;\n}\n\n// Deliberately narrow (language + optional script/region only): item names\n// are snake_case, so a permissive multi-segment pattern would classify names\n// like `my_custom_strings` as locales.\nconst LOCALE_LIKE = /^[a-z]{2,3}([_-]([A-Za-z]{4}|[A-Za-z]{2}|[0-9]{3}))?$/;\n\n/**\n * Read ACTIVE `translation` metadata rows and compute the authored layer,\n * keyed by locale. Returns `null` when the read failed (callers must keep\n * the current layer, never tear it down on an error).\n */\nexport async function readAuthoredTranslationLayer(\n engine: { find(object: string, opts?: AnyRecord): Promise<any[]> },\n logger?: MinimalCtx['logger'],\n): Promise<Record<string, Record<string, unknown>> | null> {\n let rows: any[];\n try {\n rows = (await engine.find('sys_metadata', {\n where: { type: 'translation', state: 'active' },\n })) ?? [];\n if (rows.length === 0) {\n // Legacy plural rows — mirrors the protocol's singular/plural fallback.\n rows = (await engine.find('sys_metadata', {\n where: { type: 'translations', state: 'active' },\n })) ?? [];\n }\n } catch (err: any) {\n logger?.debug?.('[i18n] authored-translation read failed — keeping current layer', {\n error: err?.message,\n });\n return null;\n }\n\n const byLocale: Record<string, Record<string, unknown>> = {};\n const sorted = [...rows].sort((a, b) => String(a?.name ?? '').localeCompare(String(b?.name ?? '')));\n for (const row of sorted) {\n let data: any;\n try {\n data = typeof row.metadata === 'string' ? JSON.parse(row.metadata) : row.metadata;\n } catch {\n continue; // malformed row — skip it, keep the rest\n }\n if (!data || typeof data !== 'object') continue;\n\n // Rows written against the retired object-first shape resolve to nothing\n // no matter what locale they claim, so say that plainly rather than\n // letting them look loaded. New saves are rejected at the metadata door\n // by `TranslationItemSchema`; this covers rows that predate it.\n const legacyKeys = LEGACY_OBJECT_FIRST_KEYS.filter((key) => data[key] !== undefined);\n if (legacyKeys.length > 0) {\n logger?.warn?.(\n `[i18n] authored translation '${row?.name}' uses the retired object-first shape `\n + `(${legacyKeys.join(', ')}) — nothing resolves from it; re-author it under `\n + \"'objects.<object_name>' with a top-level 'locale' — skipped\",\n );\n continue;\n }\n\n const locale: string | undefined =\n (typeof data?.locale === 'string' && data.locale)\n || (typeof row?.name === 'string' && LOCALE_LIKE.test(row.name) ? row.name : undefined)\n || undefined;\n if (!locale) {\n logger?.warn?.(\n `[i18n] authored translation '${row?.name}' has no resolvable locale `\n + \"(set the top-level 'locale', or name the item after its BCP-47 locale) — skipped\",\n );\n continue;\n }\n // Strip authoring bookkeeping; everything else is translation data. The\n // lock/package fields are stamped by the metadata protocol on published\n // rows — merging them would seed junk keys into the i18n layer.\n const {\n name: _n, locale: _l,\n _packageId: _p, _packageVersion: _pv, _provenance: _pr,\n _lock: _lk, _lockReason: _lr, _lockDocsUrl: _ld, _lockSource: _ls,\n ...payload\n } = data;\n byLocale[locale] = deepMerge(byLocale[locale] ?? {}, payload as Record<string, unknown>);\n }\n return byLocale;\n}\n\n/**\n * Wire the authored-translation sync into a plugin context: registers the\n * `kernel:ready` / `metadata:reloaded` hooks and (at kernel:ready) the\n * protocol mutation subscription. Idempotent per i18n service instance via\n * the ownership marker. Safe to call on kernels with no engine, no protocol,\n * or an i18n service without `replaceAuthoredTranslations` — every path\n * degrades to a no-op.\n */\nexport function wireAuthoredTranslationSync(ctx: MinimalCtx): void {\n if (typeof ctx.hook !== 'function') return;\n\n const token = Symbol('authored-translation-sync');\n const resolveOwnedI18n = (): AuthoredTranslationSink | null => {\n let i18n: AuthoredTranslationSink | undefined;\n try { i18n = ctx.getService('i18n'); } catch { return null; }\n if (!i18n || typeof i18n.replaceAuthoredTranslations !== 'function') return null;\n const current = i18n[OWNER_PROP];\n if (current === undefined) {\n i18n[OWNER_PROP] = token;\n return i18n;\n }\n return current === token ? i18n : null; // another wirer owns this instance\n };\n\n // Serialized: overlapping publishes must not finish out of order and leave\n // the older authored snapshot applied.\n let chain: Promise<void> = Promise.resolve();\n const sync = (): Promise<void> => {\n const run = chain.then(async () => {\n const i18n = resolveOwnedI18n();\n if (!i18n) return;\n let engine: IDataEngine | undefined;\n try { engine = ctx.getService('objectql'); } catch { return; }\n if (!engine || typeof engine.find !== 'function') return;\n const layer = await readAuthoredTranslationLayer(engine, ctx.logger);\n if (layer === null) return; // failed read — keep current layer\n i18n.replaceAuthoredTranslations(layer);\n ctx.logger.info?.('[i18n] synced runtime-authored translations', {\n locales: Object.keys(layer),\n });\n });\n chain = run.catch(() => undefined);\n return run;\n };\n\n ctx.hook('kernel:ready', async () => {\n // Subscribe to translation mutations through the protocol choke point\n // (#2588). Only the owning wirer subscribes.\n if (resolveOwnedI18n()) {\n let protocol: any = null;\n try { protocol = ctx.getService('protocol'); } catch { /* no protocol on this kernel */ }\n if (protocol && typeof protocol.onMetadataMutation === 'function') {\n protocol.onMetadataMutation((evt: { type: string; name: string; state: string }) => {\n if (evt?.type !== 'translation' || evt.state === 'draft') return;\n void sync().catch((err: any) => {\n ctx.logger.warn?.('[i18n] authored-translation re-sync after mutation failed', {\n item: evt.name,\n error: err?.message,\n });\n });\n });\n }\n }\n await sync();\n });\n ctx.hook('metadata:reloaded', async () => {\n await sync();\n });\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { createMemoryCache } from './memory-cache.js';\nimport { createMemoryQueue } from './memory-queue.js';\nimport { createMemoryI18n } from './memory-i18n.js';\nimport { createMemoryMetadata } from './memory-metadata.js';\n\nexport { createMemoryCache } from './memory-cache.js';\nexport { createMemoryQueue } from './memory-queue.js';\nexport { createMemoryJob } from './memory-job.js';\nexport { createMemoryI18n, resolveLocale, deepMerge } from './memory-i18n.js';\nexport { createMemoryMetadata } from './memory-metadata.js';\nexport {\n wireAuthoredTranslationSync,\n readAuthoredTranslationLayer,\n} from './authored-translation-sync.js';\n\n/**\n * Map of core-criticality service names to their in-memory fallback factories.\n * This IS the kernel's pre-injection list: `ObjectKernel.preInjectCoreFallbacks()`\n * registers an entry for every unprovided `core` service before Phase 2, and\n * `validateSystemRequirements()` consults the same map as its final check.\n *\n * [#10746] `job` is deliberately ABSENT — a fallback must not fake capability\n * (maintainer ruling 2026-08-22). `createMemoryJob()`'s `schedule()` records a\n * job and never fires it, so pre-injecting it made every \"prefer the platform\n * job service, else own a timer\" consumer take the job-service branch and then\n * silently never run: `plugin-reports` logged `dispatcher registered with job\n * service` and dispatched nothing, ever (measured: 0 reads of\n * `sys_report_schedule` in 5600 ms with the success line present). With no\n * entry here, `getService('job')` throws when no job plugin is installed,\n * every consumer's documented no-job-service path becomes reachable (they all\n * already run on `LiteKernel`, which injects no fallbacks), and the kernel\n * says the absence out loud at boot: `validateSystemRequirements()` warns\n * \"Core service missing, functionality may be degraded: job\". Do NOT re-add\n * the entry to quiet that warning — install `@objectstack/service-job`, or\n * register a real scheduler, instead. `createMemoryJob` stays exported below\n * for embedders who deliberately want a manual-trigger job registry and have\n * read its docblock.\n */\nexport const CORE_FALLBACK_FACTORIES: Record<string, () => Record<string, any>> = {\n metadata: createMemoryMetadata,\n cache: createMemoryCache,\n queue: createMemoryQueue,\n i18n: createMemoryI18n,\n};\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Logger } from '@objectstack/spec/contracts';\n\n/**\n * The DECLARED duplicate-plugin-registration contract — one statement, shared\n * by both kernels (#9864, maintainer ruling 2026-08-19, option B).\n *\n * ## What the contract says\n *\n * Registering a plugin whose `name` is already registered **OVERWRITES** the\n * earlier registration — last-one-wins — and emits ONE `warn` naming the\n * plugin and BOTH versions. It is not an error on either kernel.\n *\n * ## Why it is written down here rather than in each kernel\n *\n * It was previously written twice, and the two copies disagreed:\n * `ObjectKernel.use()` accepted the duplicate silently (a bare\n * `plugins.set(name, meta)` with no check and no distinguishing log line),\n * while `LiteKernel.use()` threw `[Kernel] Plugin '<name>' already registered`.\n * `ObjectKernel` is the kernel `os serve` runs, so the production meaning of a\n * duplicate registration was the silent one — and it is load-bearing: it is\n * exactly what lets an app config's `plugins` entry supersede a plugin the CLI\n * auto-registered earlier in the same boot (#9863's `AuditPlugin` case). That\n * behaviour is PRESERVED here on purpose; what changes is that it is now\n * declared, audible, and pinned against both kernels\n * (`plugin-registration.contract.test.ts`) instead of being an accident of\n * whichever kernel a reader happened to open.\n *\n * This is the fourth measured instance of one contract implemented twice\n * across `ObjectKernel`/`LiteKernel` (#5170 hook-error propagation, #5282\n * `ObjectKernel` not inheriting `ObjectKernelBase`, #8357 adjacent one layer\n * up). `ObjectKernel` still does not extend `ObjectKernelBase`, so a shared\n * base class is not available as the sharing mechanism — a module both kernels\n * import is, and it is the same mechanism `plugin-order.ts` and\n * `hook-dispatch.ts` already use for the contracts they own.\n *\n * ⛔ Deliberately NOT exported from the package barrel. Under the ruling this\n * card declares EXISTING behaviour; it does not mint a public registration or\n * supersede API (that was the shape of the option that was NOT ruled). Both\n * kernels import it by relative path, as they do `plugin-order.js`'s internals.\n */\n\n/**\n * The only members the registration contract reads. Satisfied by both kernels'\n * registry value types — `Plugin` (LiteKernel, `version` optional) and\n * `PluginMetadata` (ObjectKernel, `version` always present because\n * `PluginLoader.toPluginMetadata()` defaults it).\n */\nexport interface NamedRegistration {\n name: string;\n version?: string;\n}\n\n/** Render a version for the warning; a plugin may legitimately carry none. */\nfunction versionLabel(plugin: NamedRegistration): string {\n return plugin.version ? `v${plugin.version}` : 'unversioned';\n}\n\n/**\n * The superseding warning's text.\n *\n * ## Why it cannot be confused with a first registration\n *\n * The failure this card exists to end is that a superseding registration was\n * indistinguishable from a first one: `ObjectKernel` logged\n * `Plugin registered: <name>@<version>` for BOTH, so one plugin replacing\n * another read as two plugins running. Four properties keep them apart, and\n * each is pinned by the contract test:\n *\n * 1. **A different verb, first thing on the line** — `Plugin superseded:`, not\n * `Plugin registered:`. A boot log greps and eyeballs the same way.\n * 2. **A different LEVEL** — `warn`, never `info`. This is not decoration on\n * the `os serve` path: the CLI's default kernel level is `warn`\n * (`DEFAULT_LOG_LEVEL`, `packages/cli/src/utils/log-level.ts`), at which\n * `Plugin registered:` is not emitted at all; and the boot-quiet window\n * (`BootLogCapture`, `BOOT_DIAGNOSTIC_FLOOR = 'warn'`) DISCARDS in-window\n * `info` and replays only `warn` and above. An `info`-level supersede notice\n * would be invisible on precisely the boot path where the defect lives.\n * 3. **BOTH versions, in order** — `(v1.0.0) → (v2.0.0)`. A plugin silently\n * replaced by a differently-configured instance of ITSELF is the expensive\n * direction, and there the two names are identical; the versions and the\n * arrow are what make one line say which instance survived.\n * 4. **The consequence, stated** — the earlier instance is discarded before it\n * ever boots, so a reader is not left to infer whether two plugins are now\n * running.\n *\n * `ObjectKernel` additionally SUPPRESSES its `Plugin registered:` line for a\n * superseding registration, so the count of `Plugin registered:` lines in a\n * boot log equals the number of plugins that will actually boot.\n */\nexport function describeSupersededRegistration(\n previous: NamedRegistration,\n next: NamedRegistration,\n): string {\n return (\n `Plugin superseded: '${next.name}' — the later registration (${versionLabel(next)}) ` +\n `REPLACED the earlier one (${versionLabel(previous)}). Only the later instance is ` +\n `initialized and started; the earlier one is discarded without ever running init(). ` +\n `Duplicate registration by name is last-one-wins on both kernels by declared contract ` +\n `(#9864) — register the plugin once if that is not what you meant.`\n );\n}\n\n/**\n * Apply the declared contract: write `plugin` into `registry` under its name,\n * warning when that displaces an earlier registration.\n *\n * @returns the displaced registration, or `undefined` for a first registration.\n * Callers use it to decide whether this was a plain registration —\n * `ObjectKernel` suppresses its `Plugin registered:` line when it was\n * not.\n *\n * ## Why no teardown of the displaced plugin (measured, #9864)\n *\n * Both kernels refuse registration outside the `idle` state\n * (`ObjectKernelBase.validateIdle()`; `ObjectKernel.use()`'s own state check),\n * so a supersede can only ever displace a plugin the kernel has **not yet\n * initialized** — `init()`, `start()` and `destroy()` all run from\n * `bootstrap()`/`destroy()`, over the resolved order read out of this very\n * registry, from which the displaced entry is already gone. `PluginLoader.\n * loadPlugin()`, which `ObjectKernel` runs BEFORE this point, is pure\n * validation plus a name-keyed map write of its own (so it drops the displaced\n * metadata for the same reason, rather than accumulating it); it invokes\n * nothing on the plugin.\n *\n * The kernel therefore acquired nothing on the displaced plugin's behalf, and\n * there is nothing here to tear down. Calling `destroy()` on it would be the\n * bug, not the fix: `destroy()` is the paired teardown for `init()`, and\n * running it against a never-initialized instance runs cleanup over state that\n * was never set up. Anything the displaced instance holds was acquired by the\n * CALLER's own `new` before `use()` was reached — a survey of all 52 in-tree\n * `implements Plugin` classes found none that acquires an OS-level resource in\n * its constructor (41 have a constructor body; every one normalizes options or\n * builds in-memory helpers — e.g. `HonoServerPlugin`'s `new HonoHttpServer()`\n * only constructs a `Hono` app, its socket opening at `kernel:listening`).\n */\nexport function registerPluginByName<T extends NamedRegistration>(\n registry: Map<string, T>,\n plugin: T,\n logger: Pick<Logger, 'warn'>,\n): T | undefined {\n const previous = registry.get(plugin.name);\n\n if (previous !== undefined) {\n // `warn`, not `error`: this is a FUNCTIONAL, fully-visible outcome —\n // the composition the host asked for is the one that boots, and\n // nothing that claims to be persisted fails to land. See AGENTS.md\n // \"Degradation log levels\" for why that distinction decides the level.\n logger.warn(describeSupersededRegistration(previous, plugin), {\n plugin: plugin.name,\n supersededVersion: previous.version,\n supersedingVersion: plugin.version,\n });\n }\n\n registry.set(plugin.name, plugin);\n\n return previous;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * The one-shot timeout guard both kernel lifecycle races arm.\n *\n * A guard is a `setTimeout` plus the promise a `Promise.race` holds a reaction\n * on. Reclaiming it after the race is decided therefore has TWO halves, and\n * the kernel used to do exactly one of them at each site — a different one:\n *\n * - `raceStartupTimeout` cleared the timer and left the promise pending.\n * - `shutdown()` `unref()`d the timer, never cleared it, and also left the\n * promise pending.\n *\n * Clearing the timer reclaims the TIMER. It does not settle the PROMISE: the\n * race attached `then(resolve, reject)` to that participant when it started,\n * and a promise that never settles keeps both itself and that reaction alive\n * for as long as anything can reach them. That is what\n * `vitest --detectAsyncLeaks` reports as a leaking PROMISE — two frames per\n * site, one for the guard promise and one for the race's reaction on it\n * (#10604). So `reclaim()` clears the timer AND settles the promise.\n *\n * ⛔ The guard is deliberately NOT `unref()`ed at arm time (#4813). An unref'd\n * guard also stops pinning the event loop, but it stops being a guard as well:\n * if the operation never settles and nothing else keeps the loop alive, Node\n * exits before the timer can fire and the timeout is never reported. The guard\n * has to stay ref'd exactly as long as the race is undecided — which is what\n * clearing on settle expresses and what `unref()` cannot. `unref()` is the\n * failure mode this class exists to prevent, not a second belt to add.\n */\nexport class TimeoutGuard {\n private timer: ReturnType<typeof setTimeout> | undefined;\n\n /**\n * Settles `expiry` without a value. `Promise<never>` has no resolvable\n * value in the type system, but settling it is the entire point: it is\n * only ever called from `reclaim()`, i.e. after the race it guarded has\n * already been decided, so the resolution is discarded by construction and\n * can never become a race winner. The cast localises that argument here\n * rather than pushing a lie into every caller's return type.\n */\n private settleExpiry: () => void = () => {};\n\n /**\n * The racing participant. Rejects with the caller's error when the guard\n * expires; resolves — settles, harmlessly — once `reclaim()` runs.\n */\n readonly expiry: Promise<never>;\n\n constructor(timeoutMs: number, createTimeoutError: () => Error) {\n this.expiry = new Promise<never>((resolve, reject) => {\n this.settleExpiry = resolve as unknown as () => void;\n this.timer = setTimeout(() => reject(createTimeoutError()), timeoutMs);\n });\n }\n\n /**\n * Reclaim the guard once the race it protects has been decided. Both\n * halves, always: the timer is cleared so it cannot fire against a\n * lifecycle phase that is already over, and `expiry` is settled so neither\n * it nor the race's reaction on it is retained.\n *\n * Idempotent — `clearTimeout` on a cleared handle and a second resolve on\n * a settled promise are both no-ops.\n */\n reclaim(): void {\n clearTimeout(this.timer);\n this.timer = undefined;\n this.settleExpiry();\n }\n}\n\n/**\n * Race `operation` against a startup/shutdown timeout guard, reclaiming the\n * guard the moment the race settles — whichever side won.\n *\n * `operation` is widened to `T | PromiseLike<T>` because the Plugin contract\n * permits a synchronous hook (`init`/`start` return `void | Promise<void>`);\n * such a hook wins the race immediately and the guard is reclaimed on the same\n * turn.\n *\n * `createTimeoutError` is a factory rather than an `Error` so a caller that\n * discriminates the timeout by IDENTITY (`shutdown()`, #5274) can hand back\n * its own pre-built instance, while callers that only need a message pay for\n * the `Error` — and its stack — only when the guard actually fires.\n */\nexport async function raceWithTimeout<T>(\n operation: T | PromiseLike<T>,\n timeoutMs: number,\n createTimeoutError: () => Error,\n): Promise<T> {\n const guard = new TimeoutGuard(timeoutMs, createTimeoutError);\n\n try {\n return await Promise.race([operation, guard.expiry]);\n } finally {\n guard.reclaim();\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Plugin } from './types.js';\nimport { createLogger, ObjectLogger } from './logger.js';\nimport type { LoggerConfig } from '@objectstack/spec/system';\nimport { ObjectKernelBase } from './kernel-base.js';\nimport { registerPluginByName } from './plugin-registration.js';\n\n/**\n * ObjectKernel - MiniKernel Architecture\n * \n * A highly modular, plugin-based microkernel that:\n * - Manages plugin lifecycle (init, start, destroy)\n * - Provides dependency injection via service registry\n * - Implements event/hook system for inter-plugin communication\n * - Handles dependency resolution (topological sort)\n * - Provides configurable logging for server and browser\n * \n * Core philosophy:\n * - Business logic is completely separated into plugins\n * - Kernel only manages lifecycle, DI, and hooks\n * - Plugins are loaded as equal building blocks\n */\nexport class LiteKernel extends ObjectKernelBase {\n constructor(config?: { logger?: Partial<LoggerConfig> }) {\n const logger = createLogger(config?.logger);\n super(logger);\n \n // Initialize context after logger is created\n this.context = this.createContext();\n }\n\n /**\n * Register a plugin\n * @param plugin - Plugin instance\n *\n * Duplicate names OVERWRITE, with one `warn` naming both versions — the\n * declared contract in `plugin-registration.ts`, applied identically by\n * `ObjectKernel.use()` (#9864, maintainer ruling 2026-08-19).\n *\n * This method used to `throw` `[Kernel] Plugin '<name>' already\n * registered` here while `ObjectKernel` overwrote silently, so one input\n * had two meanings depending on which kernel was running — and the kernel\n * that runs in production was the silent one. The ruling converged them on\n * the behaviour that already works (an app config superseding a plugin the\n * CLI auto-registered, #9863) and made it audible rather than removing it.\n */\n use(plugin: Plugin): this {\n this.validateIdle();\n\n registerPluginByName(this.plugins, plugin, this.logger);\n\n return this;\n }\n\n /**\n * Bootstrap the kernel\n * 1. Resolve dependencies (topological sort)\n * 2. Init phase - plugins register services\n * 3. Start phase - plugins execute business logic\n * 4. Trigger 'kernel:ready' hook\n */\n async bootstrap(): Promise<void> {\n this.validateState('idle');\n\n this.state = 'initializing';\n this.logger.info('Bootstrap started');\n\n // Resolve dependencies\n const orderedPlugins = this.resolveDependencies();\n\n // Pre-Phase-1 ordering contract (ADR-0116, #4131): a plugin that\n // requires a service provided only by a later plugin fails HERE,\n // named, before any init side effects.\n this.validateInitServices(orderedPlugins);\n\n // Phase 1: Init - Plugins register services\n this.logger.info('Phase 1: Init plugins');\n for (const plugin of orderedPlugins) {\n await this.runPluginInit(plugin);\n }\n\n // Phase 2: Start - Plugins execute business logic\n this.logger.info('Phase 2: Start plugins');\n this.state = 'running';\n \n for (const plugin of orderedPlugins) {\n await this.runPluginStart(plugin);\n }\n\n // The three boot-path lifecycle hooks all use PROPAGATING dispatch,\n // identical to `ObjectKernel.bootstrap()`'s `context.trigger` (a bare\n // awaited loop that never catches): a handler that throws FAILS THE\n // BOOT on both kernels, the remaining handlers are skipped, the\n // original error reaches the caller unwrapped, and the kernel is left\n // 'stopped' rather than 'running' so a failed boot never reads as a\n // live kernel. `kernel:ready` got this in #5170; `kernel:bootstrapped`\n // and `kernel:listening` in #5257.\n //\n // Why the boot path is the wrong place to be forgiving: everything\n // between here and the log line below is a PRECONDITION of the\n // \"✅ Bootstrap complete\" this method is about to print. Swallowing a\n // throw does not make the boot succeed — it only makes the failure\n // invisible while `bootstrap()` resolves normally. `kernel:listening`\n // is the sharpest case: it is where HTTP server plugins actually open\n // their socket (`HonoServerPlugin` awaits `server.listen(port)` with\n // no try/catch of its own, deliberately — propagation is the correct\n // behaviour there), so a swallowed EACCES / unavailable-listen on an\n // edge or serverless host produced a live process, a cheerful\n // \"Bootstrap complete\", and not one socket listening.\n try {\n // Route/middleware registration phase, and the only correct moment\n // for a plugin to assert that what it DECLARED can actually be\n // delivered — the registries are still filling during init(), so a\n // boot gate has nowhere earlier to run (#5170).\n await this.triggerHookOrThrow('kernel:ready');\n // \"All synchronous bootstrap has settled\" anchor, strictly after\n // every kernel:ready handler has settled and before any HTTP socket\n // opens. Carries reconcile/backfill/audit work. NOTE: does not\n // guarantee background app seed data has settled — subscribe\n // `app:seeded` for that (see plugin-lifecycle-events.ts).\n await this.triggerHookOrThrow('kernel:bootstrapped');\n // HTTP servers open their listening socket here — strictly after\n // every kernel:ready and kernel:bootstrapped handler has completed.\n await this.triggerHookOrThrow('kernel:listening');\n } catch (error) {\n this.state = 'stopped';\n throw error;\n }\n this.logger.info('✅ Bootstrap complete', {\n pluginCount: this.plugins.size\n });\n }\n\n /**\n * Shutdown the kernel\n * Calls destroy on all plugins in reverse order\n */\n async shutdown(): Promise<void> {\n await this.destroy();\n }\n\n /**\n * Graceful shutdown - destroy all plugins in reverse order\n */\n async destroy(): Promise<void> {\n if (this.state === 'stopped') {\n this.logger.warn('Kernel already stopped');\n return;\n }\n\n this.state = 'stopping';\n this.logger.info('Shutdown started');\n\n // Trigger shutdown hook — FAIL-SOFT dispatch ({@link triggerHook}),\n // deliberately, and NOT the propagating dispatcher the boot-path hooks\n // above use (#5257). This is a per-hook judgement written down, not an\n // inherited default: on the shutdown path there is no \"refuse to\n // proceed\" left to buy. The remaining work — every other subscriber's\n // cleanup, then each plugin's destroy() in reverse order — is what\n // flushes buffers, closes connections and releases locks, so letting\n // one subscriber's failure abort the rest converts a single bad\n // handler into leaked resources and unflushed writes. A failing\n // shutdown handler is logged (`Hook handler failed: kernel:shutdown`)\n // and the cleanup continues.\n await this.triggerHook('kernel:shutdown');\n\n // Destroy plugins in reverse order\n const orderedPlugins = this.resolveDependencies();\n for (const plugin of orderedPlugins.reverse()) {\n await this.runPluginDestroy(plugin);\n }\n\n this.state = 'stopped';\n this.logger.info('✅ Shutdown complete');\n \n // Cleanup logger resources\n if (this.logger && typeof (this.logger as ObjectLogger).destroy === 'function') {\n await (this.logger as ObjectLogger).destroy();\n }\n }\n\n /**\n * Get a service from the registry\n * Convenience method for external access\n */\n getService<T>(name: string): T {\n return this.context.getService<T>(name);\n }\n\n /**\n * Check if kernel is running\n */\n isRunning(): boolean {\n return this.state === 'running';\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nexport * from './adapter.js';\nexport * from './runner.js';\nexport * from './http-adapter.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport * as QA from '@objectstack/spec/qa';\nimport { TestExecutionAdapter } from './adapter.js';\n\nexport interface TestResult {\n scenarioId: string;\n passed: boolean;\n steps: StepResult[];\n error?: unknown;\n duration: number;\n}\n\nexport interface StepResult {\n stepName: string;\n passed: boolean;\n error?: unknown;\n output?: unknown;\n duration: number;\n}\n\n/**\n * Name the runtime shape of a value the way a suite author sees it in their fixture.\n * `typeof` answers `object` for both `null` and an array, which are the two shapes a\n * `contains` author most needs told apart from a plain record.\n */\nfunction describeActualType(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'array';\n return typeof value;\n}\n\n/**\n * Say WHICH of the two things is wrong, because the message is the only thing the\n * author has: `undefined`/`null` mean the path did not resolve to a value, so the\n * FIXTURE (the field path, or the response shape it was written against) is the\n * suspect; anything else means the path resolved fine and the ASSERTION picked an\n * operator that does not apply to what it found.\n */\nfunction containsInapplicableHint(actual: unknown): string {\n if (actual === undefined) {\n return (\n 'The path resolved to nothing — the field is absent from the result, or the path is misspelled. ' +\n \"Use 'is_null' if asserting absence is what you meant.\"\n );\n }\n if (actual === null) {\n return \"The path resolved to null. Use 'is_null' if asserting absence is what you meant.\";\n }\n return (\n \"'contains' tests array membership and string substrings only. \" +\n \"Use 'equals' to compare a scalar, or point the field at the array or string you meant to look inside.\"\n );\n}\n\nexport class TestRunner {\n constructor(private adapter: TestExecutionAdapter) {}\n\n async runSuite(suite: QA.TestSuite): Promise<TestResult[]> {\n const results: TestResult[] = [];\n for (const scenario of suite.scenarios) {\n results.push(await this.runScenario(scenario));\n }\n return results;\n }\n\n async runScenario(scenario: QA.TestScenario): Promise<TestResult> {\n const startTime = Date.now();\n const context: Record<string, unknown> = {}; // Variable context\n \n // Initialize context from initial payload if needed? Currently schema doesn't have initial context prop on Scenario\n // But we defined TestContextSchema separately.\n \n // Setup\n if (scenario.setup) {\n for (const step of scenario.setup) {\n try {\n await this.runStep(step, context);\n } catch (e) {\n return {\n scenarioId: scenario.id,\n passed: false,\n steps: [],\n error: `Setup failed: ${e instanceof Error ? e.message : String(e)}`,\n duration: Date.now() - startTime\n };\n }\n }\n }\n\n const stepResults: StepResult[] = [];\n let scenarioPassed = true;\n let scenarioError: unknown = undefined;\n\n // Main Steps\n for (const step of scenario.steps) {\n const stepStartTime = Date.now();\n try {\n const output = await this.runStep(step, context);\n stepResults.push({\n stepName: step.name,\n passed: true,\n output,\n duration: Date.now() - stepStartTime\n });\n } catch (e) {\n scenarioPassed = false;\n scenarioError = e;\n stepResults.push({\n stepName: step.name,\n passed: false,\n error: e,\n duration: Date.now() - stepStartTime\n });\n break; // Stop on first failure\n }\n }\n\n // Teardown (run even if failed)\n if (scenario.teardown) {\n for (const step of scenario.teardown) {\n try {\n await this.runStep(step, context);\n } catch (e) {\n // Log teardown failure but don't override main failure if it exists\n if (scenarioPassed) {\n scenarioPassed = false;\n scenarioError = `Teardown failed: ${e instanceof Error ? e.message : String(e)}`;\n }\n }\n }\n }\n\n return {\n scenarioId: scenario.id,\n passed: scenarioPassed,\n steps: stepResults,\n error: scenarioError,\n duration: Date.now() - startTime\n };\n }\n\n private async runStep(step: QA.TestStep, context: Record<string, unknown>): Promise<unknown> {\n // 1. Resolve Variables with Context (Simple interpolation or just pass context?)\n // For now, assume adpater handles context resolution or we do basic replacement\n const resolvedAction = this.resolveVariables(step.action, context);\n\n // 2. Execute Action\n const result = await this.adapter.execute(resolvedAction, context);\n\n // 3. Capture Outputs\n if (step.capture) {\n for (const [varName, path] of Object.entries(step.capture)) {\n context[varName] = this.getValueByPath(result, path);\n }\n }\n\n // 4. Run Assertions\n if (step.assertions) {\n for (const assertion of step.assertions) {\n this.assert(result, assertion, context);\n }\n }\n\n return result;\n }\n\n private resolveVariables(action: QA.TestAction, context: Record<string, unknown>): QA.TestAction {\n const actionStr = JSON.stringify(action);\n const resolved = actionStr.replace(/\\{\\{([^}]+)\\}\\}/g, (_match, varPath: string) => {\n const value = this.getValueByPath(context, varPath.trim());\n if (value === undefined) return _match; // Keep unresolved\n return typeof value === 'string' ? value : JSON.stringify(value);\n });\n try {\n return JSON.parse(resolved) as QA.TestAction;\n } catch {\n return action; // Fallback to original if parse fails\n }\n }\n\n private getValueByPath(obj: unknown, path: string): unknown {\n if (!path) return obj;\n const parts = path.split('.');\n let current: any = obj;\n for (const part of parts) {\n if (current === null || current === undefined) return undefined;\n current = current[part];\n }\n return current;\n }\n\n private assert(result: unknown, assertion: QA.TestAssertion, _context: Record<string, unknown>) {\n const actual = this.getValueByPath(result, assertion.field);\n // Resolve expected value if it's a variable ref? \n const expected = assertion.expectedValue; // Simplify for now\n\n switch (assertion.operator) {\n case 'equals':\n if (actual !== expected) throw new Error(`Assertion failed: ${assertion.field} expected ${expected}, got ${actual}`);\n break;\n case 'not_equals':\n if (actual === expected) throw new Error(`Assertion failed: ${assertion.field} expected not ${expected}, got ${actual}`);\n break;\n case 'contains':\n if (Array.isArray(actual)) {\n if (!actual.includes(expected)) throw new Error(`Assertion failed: ${assertion.field} array does not contain ${expected}`);\n } else if (typeof actual === 'string') {\n if (!actual.includes(String(expected))) throw new Error(`Assertion failed: ${assertion.field} string does not contain ${expected}`);\n } else {\n // `contains` is defined over arrays (membership) and strings (substring), and\n // over nothing else. This branch used to be absent, so every other shape fell\n // out of the switch and the assertion reported PASSED (#7256) — a `contains`\n // written against a path the result does not carry was the test silently\n // deleting itself, and CI believed the green. An assertion the engine cannot\n // evaluate is a FAILED assertion, which is the posture every other unhandled\n // shape in this engine already takes (`default:` below; the HTTP adapter's\n // unknown action type).\n throw new Error(\n `Assertion failed: ${assertion.field} cannot be evaluated by 'contains' — ` +\n `expected an array or a string at that path, got ${describeActualType(actual)}. ` +\n containsInapplicableHint(actual)\n );\n }\n break;\n case 'not_null':\n if (actual === null || actual === undefined) throw new Error(`Assertion failed: ${assertion.field} is null`);\n break;\n case 'is_null':\n if (actual !== null && actual !== undefined) throw new Error(`Assertion failed: ${assertion.field} is not null`);\n break;\n // ... Add other operators\n default:\n throw new Error(`Unknown assertion operator: ${assertion.operator}`);\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport * as QA from '@objectstack/spec/qa';\nimport { RestApiConfigSchema, CrudEndpointsConfigSchema } from '@objectstack/spec/api';\nimport { TestExecutionAdapter } from './adapter.js';\n\n/** Memoised {@link conventionMounts} — the schemas are `lazySchema`, so build them once. */\nlet conventionCache: { apiBase: string; dataPath: string } | undefined;\n\n/**\n * The paths a STOCK ObjectStack server serves the API base and the Data\n * Protocol under, derived from the two schemas `RestServer` resolves from.\n *\n * ## [#7848] Why this is derived and not written down\n *\n * Every record-shaped action type used to build `${baseUrl}/api/data/:object` —\n * a literal, one version segment short of the route the server registers, so\n * `create_record`, `read_record`, `update_record`, `delete_record` and\n * `query_records` (5 of the 8 declared `TestActionTypeSchema` members) answered\n * `HTTP Error 404: {\"error\":\"Not found\"}` against a stock boot. The suite author\n * reading that 404 has every reason to think it is their own URL.\n *\n * Replacing one literal with a corrected literal only moves the drift: the\n * server composes this path out of two declared pieces, and both of them are\n * configurable. So this asks the SAME schemas the server's own resolution asks:\n *\n * - `RestApiConfigSchema` → `apiPath ?? `${basePath}/${version}`` — the exact\n * expression `RestServer.getApiBasePath()` evaluates (`/api` + `v1`);\n * - `CrudEndpointsConfigSchema.dataPrefix` — what `RestServer` appends to it\n * to get `dataPath` (`/data`).\n *\n * These are the CONVENTION: what the schemas default to, which is what a\n * deployment serves until it says otherwise. `HttpTestAdapter` is handed an\n * origin and nothing else, so the convention is also all it can know before it\n * asks the server — which is what {@link HttpTestAdapter.resolveDataMount}\n * does (#7983). The `/discovery` path is derived here for the same reason the\n * data path is: `RestServer.registerDiscoveryEndpoints` mounts it at\n * `${getApiBasePath()}/discovery`, so a second literal would be a second place\n * to forget.\n */\nfunction conventionMounts(): { apiBase: string; dataPath: string } {\n if (conventionCache === undefined) {\n const api = RestApiConfigSchema.parse({});\n const crud = CrudEndpointsConfigSchema.parse({});\n const apiBase = api.apiPath ?? `${api.basePath}/${api.version}`;\n conventionCache = { apiBase, dataPath: `${apiBase}${crud.dataPrefix}` };\n }\n return conventionCache;\n}\n\n/**\n * Where this adapter decided the Data Protocol is mounted, and on what evidence.\n *\n * `why` is not decoration — it is the whole difference between a 404 that reads\n * as the suite author's own URL mistake and one that names the mount it used\n * and where that mount came from (Route & surface ownership §3).\n */\ninterface ResolvedDataMount {\n /** The path prefix record actions address, e.g. `/api/v1/data`. */\n readonly path: string;\n /** `discovery` = the server told us; `convention` = the schemas' defaults. */\n readonly source: 'discovery' | 'convention';\n /** One clause naming the probe and its outcome. */\n readonly why: string;\n}\n\nexport class HttpTestAdapter implements TestExecutionAdapter {\n /**\n * The single discovery probe of a run, memoised as the in-flight promise so\n * concurrent record actions share one request rather than racing N.\n *\n * `os test` builds ONE adapter for the whole run (`packages/cli/src/commands/\n * test.ts`) and hands it to every suite, so instance scope IS run scope.\n */\n private mountPromise?: Promise<ResolvedDataMount>;\n\n constructor(private baseUrl: string, private authToken?: string) {}\n\n /** The resolved data mount; probes at most once per adapter. */\n private dataMount(): Promise<ResolvedDataMount> {\n if (this.mountPromise === undefined) {\n this.mountPromise = this.resolveDataMount();\n }\n return this.mountPromise;\n }\n\n /**\n * Ask the server where it serves the Data Protocol, and fall back to the\n * convention — loudly — when it cannot say.\n *\n * ## [#7983] What the probe recovers, measured rather than assumed\n *\n * `@objectstack/client` answers the same question through discovery\n * (`getRoute`, `packages/client/src/index.ts`), and this follows it: prefer\n * the server's own `routes.data`, fall back to the convention. Measured on a\n * booted stack (REST generator + dispatcher bridge, three configs):\n *\n * | deployment | `{apiBase}/discovery` | serves |\n * |--------------------------------|-----------------------|---------------|\n * | stock | 200 `/api/v1/data` | `/api/v1/data`|\n * | `crud.dataPrefix: '/objects'` | 200 `/api/v1/objects` | `/api/v1/objects` |\n * | `api.apiPath: '/api/2026-01'` | **404** | `/api/2026-01/data` |\n *\n * So the probe closes the `dataPrefix` row exactly: `RestServer`'s discovery\n * handler substitutes the configured prefix into `routes.data`, and reading\n * it is strictly better than recomputing it here. The `apiPath` row it cannot\n * close, and the reason is structural rather than an oversight — `apiPath`\n * moves the base that discovery itself is mounted under, so the document that\n * would name the new mount is behind the very prefix we are missing.\n *\n * ⛔ And the one discovery document at a FIXED path does not rescue it:\n * `/.well-known/objectstack` is mounted at the site root by the dispatcher\n * bridge, but its `routes.data` is the DISPATCHER's own `${prefix}/data` —\n * measured as `/api/v1/data` under all three configs above, including the two\n * where the server serves elsewhere. Falling back to it would turn \"we could\n * not resolve the mount\" into \"discovery told us `/api/v1/data`\": the same\n * 404, now with a false provenance attached. Not probed, deliberately.\n *\n * Hence: one probe, then a diagnostic that NAMES the mount, the evidence and\n * the remedy. `api_call` takes the path it is given and is unaffected either\n * way — it stays the escape hatch for a host this cannot reach.\n */\n private async resolveDataMount(): Promise<ResolvedDataMount> {\n const { apiBase, dataPath } = conventionMounts();\n const probeUrl = `${this.baseUrl}${apiBase}/discovery`;\n let why: string;\n\n try {\n const headers: Record<string, string> = {};\n if (this.authToken) {\n headers['Authorization'] = `Bearer ${this.authToken}`;\n }\n const response = await fetch(probeUrl, { method: 'GET', headers });\n if (response.ok) {\n const body = await response.json();\n // `@objectstack/rest` answers the document bare; the dispatcher bridge\n // wraps it as `{ data: … }`. Pick the object that actually carries\n // `routes` rather than `body.data || body` — the document's own\n // `routes.data` key makes the looser test ambiguous to read here.\n const doc = (body && typeof body === 'object' && 'routes' in body)\n ? (body as { routes?: Record<string, unknown> })\n : ((body as { data?: { routes?: Record<string, unknown> } } | null)?.data);\n const advertised = doc?.routes?.data;\n if (typeof advertised === 'string' && advertised.length > 0) {\n return {\n path: advertised,\n source: 'discovery',\n why: `GET ${probeUrl} advertised routes.data`,\n };\n }\n why = `GET ${probeUrl} answered ${response.status} but carried no routes.data`;\n } else {\n why = `GET ${probeUrl} answered ${response.status}`;\n }\n } catch (error) {\n why = `GET ${probeUrl} could not be reached (${(error as Error).message})`;\n }\n\n const mount: ResolvedDataMount = { path: dataPath, source: 'convention', why };\n // Absence must be loud (Route & surface ownership §3): state the mount that\n // will be addressed, the evidence for it, and what to do about a host this\n // cannot reach — never leave the bare 404 to be diagnosed.\n console.warn(\n `[HttpTestAdapter] Data Protocol mount NOT resolved from discovery. `\n + `Record actions (create_record, read_record, update_record, delete_record, query_records) `\n + `will address ${this.baseUrl}${dataPath}, the convention declared by RestApiConfigSchema `\n + `(apiPath ?? basePath/version) + CrudEndpointsConfigSchema.dataPrefix. Evidence: ${why}. `\n + `A deployment that sets crud.dataPrefix is normally recovered by this probe; one that sets `\n + `api.apiPath moves discovery itself out from under it, and no fixed-path document reports `\n + `the REST mount — write those steps as api_call, which takes the path you give it.`,\n );\n return mount;\n }\n\n /** `{baseUrl}{dataMount}/{object}` — the collection URL. */\n private collectionUrl(mount: ResolvedDataMount, objectName: string): string {\n return `${this.baseUrl}${mount.path}/${encodeURIComponent(objectName)}`;\n }\n\n /** `{collection}/{id}` — the single-record URL. */\n private recordUrl(mount: ResolvedDataMount, objectName: string, id: unknown): string {\n return `${this.collectionUrl(mount, objectName)}/${encodeURIComponent(String(id))}`;\n }\n\n /**\n * The provenance clause appended to a failed record action's error.\n *\n * The card this closes is about a 404 that reads like the author's own URL\n * mistake; the mount is the one fact that distinguishes the two, so it rides\n * on the failure itself rather than only on a warning printed earlier in the\n * transcript.\n */\n private mountNote(mount: ResolvedDataMount): string {\n const how = mount.source === 'discovery'\n ? 'resolved from discovery'\n : 'assumed by convention — the server was not able to state it';\n return `record actions addressed ${this.baseUrl}${mount.path} (data mount ${how}: ${mount.why})`;\n }\n\n async execute(action: QA.TestAction, _context: Record<string, unknown>): Promise<unknown> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this.authToken) {\n headers['Authorization'] = `Bearer ${this.authToken}`;\n }\n // If action.user is specified, maybe add a specific header for impersonation if supported?\n if (action.user) {\n headers['X-Run-As'] = action.user;\n }\n\n switch (action.type) {\n case 'create_record':\n return this.createRecord(action.target, action.payload || {}, headers);\n case 'update_record':\n return this.updateRecord(action.target, action.payload || {}, headers);\n case 'delete_record':\n return this.deleteRecord(action.target, action.payload || {}, headers);\n case 'read_record':\n return this.readRecord(action.target, action.payload || {}, headers);\n case 'query_records':\n return this.queryRecords(action.target, action.payload || {}, headers);\n case 'api_call':\n return this.rawApiCall(action.target, action.payload || {}, headers);\n case 'wait':\n const ms = Number(action.payload?.duration || 1000);\n return new Promise(resolve => setTimeout(() => resolve({ waited: ms }), ms));\n default:\n throw new Error(`Unsupported action type in HttpAdapter: ${action.type}`);\n }\n }\n\n private async createRecord(objectName: string, data: Record<string, unknown>, headers: Record<string, string>) {\n const mount = await this.dataMount();\n const response = await fetch(this.collectionUrl(mount, objectName), {\n method: 'POST',\n headers,\n body: JSON.stringify(data)\n });\n return this.handleResponse(response, this.mountNote(mount));\n }\n\n private async updateRecord(objectName: string, data: Record<string, unknown>, headers: Record<string, string>) {\n const { id, ...fields } = data;\n if (!id) throw new Error('Update record requires id in payload');\n // PATCH, not PUT: `PATCH {dataMount}/:object/:id` is the route the server\n // registers, and there is no PUT sibling — the old verb 404'd even once the\n // path was right (#7848). The body is the field patch, so `id` is peeled off\n // rather than posted back as a column write.\n const mount = await this.dataMount();\n const response = await fetch(this.recordUrl(mount, objectName, id), {\n method: 'PATCH',\n headers,\n body: JSON.stringify(fields)\n });\n return this.handleResponse(response, this.mountNote(mount));\n }\n\n private async deleteRecord(objectName: string, data: Record<string, unknown>, headers: Record<string, string>) {\n const id = data.id;\n if (!id) throw new Error('Delete record requires id in payload');\n const mount = await this.dataMount();\n const response = await fetch(this.recordUrl(mount, objectName, id), {\n method: 'DELETE',\n headers\n });\n return this.handleResponse(response, this.mountNote(mount));\n }\n\n private async readRecord(objectName: string, data: Record<string, unknown>, headers: Record<string, string>) {\n const id = data.id;\n if (!id) throw new Error('Read record requires id in payload');\n const mount = await this.dataMount();\n const response = await fetch(this.recordUrl(mount, objectName, id), {\n method: 'GET',\n headers\n });\n return this.handleResponse(response, this.mountNote(mount));\n }\n\n private async queryRecords(objectName: string, data: Record<string, unknown>, headers: Record<string, string>) {\n // `POST {dataMount}/:object/query` — the spec-shape advanced query\n // (QueryAST in the body), the same route `client.data.query()` posts to.\n const mount = await this.dataMount();\n const response = await fetch(`${this.collectionUrl(mount, objectName)}/query`, {\n method: 'POST',\n headers,\n body: JSON.stringify(data)\n });\n return this.handleResponse(response, this.mountNote(mount));\n }\n\n private async rawApiCall(endpoint: string, data: Record<string, unknown>, headers: Record<string, string>) {\n // Deliberately does NOT consult the resolved mount — `api_call` takes the\n // path it is given, which is what makes it the escape hatch for a host\n // the discovery probe cannot reach (#7983). It probes nothing either: a\n // suite written entirely in `api_call` steps issues no discovery request.\n const method = (data.method as string) || 'GET';\n const body = data.body ? JSON.stringify(data.body) : undefined;\n const url = endpoint.startsWith('http') ? endpoint : `${this.baseUrl}${endpoint}`;\n\n const response = await fetch(url, {\n method,\n headers,\n body\n });\n return this.handleResponse(response);\n }\n\n private async handleResponse(response: Response, mountNote?: string) {\n if (!response.ok) {\n const text = await response.text();\n // The `HTTP Error <status>: <body>` prefix is unchanged — a suite author\n // (and every transcript predating #7983) reads the same first clause;\n // the mount provenance is APPENDED, never substituted.\n //\n // Appended on 404/405 only: those are the statuses a wrong mount\n // produces (nothing registered at the path / registered under another\n // verb), and they are exactly the ones this card calls unreadable. A\n // 400 or 422 reached the right route and got a real answer from it —\n // decorating those with the mount would bury the server's own message\n // under a URL the author has no reason to doubt.\n const wrongUrlShaped = response.status === 404 || response.status === 405;\n throw new Error(\n `HTTP Error ${response.status}: ${text}${wrongUrlShaped && mountNote ? ` — ${mountNote}` : ''}`,\n );\n }\n const contentType = response.headers.get('content-type');\n if (contentType && contentType.includes('application/json')) {\n return response.json();\n }\n return response.text();\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Logger } from '@objectstack/spec/contracts';\nimport type { PluginMetadata } from '../plugin-loader.js';\n\n// Conditionally import crypto for Node.js environments\nlet cryptoModule: typeof import('crypto') | null = null;\n\n\n/**\n * Plugin Signature Configuration\n * Controls how plugin signatures are verified\n */\nexport interface PluginSignatureConfig {\n /**\n * Map of publisher IDs to their trusted public keys\n * Format: { 'com.objectstack': '-----BEGIN PUBLIC KEY-----...' }\n */\n trustedPublicKeys: Map<string, string>;\n \n /**\n * Signature algorithm to use\n * - RS256: RSA with SHA-256\n * - ES256: ECDSA with SHA-256\n */\n algorithm: 'RS256' | 'ES256';\n \n /**\n * Strict mode: reject plugins without signatures\n * - true: All plugins must be signed\n * - false: Unsigned plugins are allowed with warning\n */\n strictMode: boolean;\n \n /**\n * Allow self-signed plugins in development\n */\n allowSelfSigned?: boolean;\n}\n\n/**\n * Plugin Signature Verification Result\n */\nexport interface SignatureVerificationResult {\n verified: boolean;\n error?: string;\n publisherId?: string;\n algorithm?: string;\n signedAt?: Date;\n}\n\n/**\n * Plugin Signature Verifier\n * \n * Implements cryptographic verification of plugin signatures to ensure:\n * 1. Plugin integrity - code hasn't been tampered with\n * 2. Publisher authenticity - plugin comes from trusted source\n * 3. Non-repudiation - publisher cannot deny signing\n * \n * Architecture:\n * - Uses Node.js crypto module for signature verification\n * - Supports RSA (RS256) and ECDSA (ES256) algorithms\n * - Verifies against trusted public key registry\n * - Computes hash of plugin code for integrity check\n * \n * Security Model:\n * - Public keys are pre-registered and trusted\n * - Plugin signature is verified before loading\n * - Strict mode rejects unsigned plugins\n * - Development mode allows self-signed plugins\n */\nexport class PluginSignatureVerifier {\n private config: PluginSignatureConfig;\n private logger: Logger;\n \n constructor(config: PluginSignatureConfig, logger: Logger) {\n this.config = config;\n this.logger = logger;\n \n this.validateConfig();\n }\n \n /**\n * Verify plugin signature\n * \n * @param plugin - Plugin metadata with signature\n * @returns Verification result\n * @throws Error if verification fails in strict mode\n */\n async verifyPluginSignature(plugin: PluginMetadata): Promise<SignatureVerificationResult> {\n // Handle unsigned plugins\n if (!plugin.signature) {\n return this.handleUnsignedPlugin(plugin);\n }\n \n try {\n // 1. Extract publisher ID from plugin name (reverse domain notation)\n const publisherId = this.extractPublisherId(plugin.name);\n \n // 2. Get trusted public key for publisher\n const publicKey = this.config.trustedPublicKeys.get(publisherId);\n if (!publicKey) {\n const error = `No trusted public key for publisher: ${publisherId}`;\n this.logger.warn(error, { plugin: plugin.name, publisherId });\n \n if (this.config.strictMode && !this.config.allowSelfSigned) {\n throw new Error(error);\n }\n \n return {\n verified: false,\n error,\n publisherId,\n };\n }\n \n // 3. Compute plugin code hash\n const pluginHash = this.computePluginHash(plugin);\n \n // 4. Verify signature using crypto module\n const isValid = await this.verifyCryptoSignature(\n pluginHash,\n plugin.signature,\n publicKey\n );\n \n if (!isValid) {\n const error = `Signature verification failed for plugin: ${plugin.name}`;\n this.logger.error(error, undefined, { plugin: plugin.name, publisherId });\n throw new Error(error);\n }\n \n this.logger.info(`✅ Plugin signature verified: ${plugin.name}`, {\n plugin: plugin.name,\n publisherId,\n algorithm: this.config.algorithm,\n });\n \n return {\n verified: true,\n publisherId,\n algorithm: this.config.algorithm,\n };\n \n } catch (error) {\n this.logger.error(`Signature verification error: ${plugin.name}`, error as Error);\n \n if (this.config.strictMode) {\n throw error;\n }\n \n return {\n verified: false,\n error: (error as Error).message,\n };\n }\n }\n \n /**\n * Register a trusted public key for a publisher\n */\n registerPublicKey(publisherId: string, publicKey: string): void {\n this.config.trustedPublicKeys.set(publisherId, publicKey);\n this.logger.info(`Trusted public key registered for: ${publisherId}`);\n }\n \n /**\n * Remove a trusted public key\n */\n revokePublicKey(publisherId: string): void {\n this.config.trustedPublicKeys.delete(publisherId);\n this.logger.warn(`Public key revoked for: ${publisherId}`);\n }\n \n /**\n * Get list of trusted publishers\n */\n getTrustedPublishers(): string[] {\n return Array.from(this.config.trustedPublicKeys.keys());\n }\n \n // Private methods\n \n private handleUnsignedPlugin(plugin: PluginMetadata): SignatureVerificationResult {\n if (this.config.strictMode) {\n const error = `Plugin missing signature (strict mode): ${plugin.name}`;\n this.logger.error(error, undefined, { plugin: plugin.name });\n throw new Error(error);\n }\n \n this.logger.warn(`⚠️ Plugin not signed: ${plugin.name}`, {\n plugin: plugin.name,\n recommendation: 'Consider signing plugins for production environments',\n });\n \n return {\n verified: false,\n error: 'Plugin not signed',\n };\n }\n \n private extractPublisherId(pluginName: string): string {\n // Extract publisher from reverse domain notation\n // Example: \"com.objectstack.engine.objectql\" -> \"com.objectstack\"\n const parts = pluginName.split('.');\n \n if (parts.length < 2) {\n throw new Error(`Invalid plugin name format: ${pluginName} (expected reverse domain notation)`);\n }\n \n // Return first two parts (domain reversed)\n return `${parts[0]}.${parts[1]}`;\n }\n \n private computePluginHash(plugin: PluginMetadata): string {\n // In browser environment, use SubtleCrypto\n if (typeof (globalThis as any).window !== 'undefined') {\n return this.computePluginHashBrowser(plugin);\n }\n \n // In Node.js environment, use crypto module\n return this.computePluginHashNode(plugin);\n }\n \n private computePluginHashNode(plugin: PluginMetadata): string {\n // Use pre-loaded crypto module\n if (!cryptoModule) {\n this.logger.warn('crypto module not available, using fallback hash');\n return this.computePluginHashFallback(plugin);\n }\n \n // Compute hash of plugin code\n const pluginCode = this.serializePluginCode(plugin);\n return cryptoModule.createHash('sha256').update(pluginCode).digest('hex');\n }\n \n private computePluginHashBrowser(plugin: PluginMetadata): string {\n // Browser environment - use simple hash for now\n // In production, should use SubtleCrypto for proper cryptographic hash\n this.logger.debug('Using browser hash (SubtleCrypto integration pending)');\n return this.computePluginHashFallback(plugin);\n }\n \n private computePluginHashFallback(plugin: PluginMetadata): string {\n // Simple hash fallback (not cryptographically secure)\n const pluginCode = this.serializePluginCode(plugin);\n let hash = 0;\n \n for (let i = 0; i < pluginCode.length; i++) {\n const char = pluginCode.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n \n return hash.toString(16);\n }\n \n private serializePluginCode(plugin: PluginMetadata): string {\n // Serialize plugin code for hashing\n // Include init, start, destroy functions\n const parts: string[] = [\n plugin.name,\n plugin.version,\n plugin.init.toString(),\n ];\n \n if (plugin.start) {\n parts.push(plugin.start.toString());\n }\n \n if (plugin.destroy) {\n parts.push(plugin.destroy.toString());\n }\n \n return parts.join('|');\n }\n \n private async verifyCryptoSignature(\n data: string,\n signature: string,\n publicKey: string\n ): Promise<boolean> {\n // In browser environment, use SubtleCrypto\n if (typeof (globalThis as any).window !== 'undefined') {\n return this.verifyCryptoSignatureBrowser(data, signature, publicKey);\n }\n \n // In Node.js environment, use crypto module\n return this.verifyCryptoSignatureNode(data, signature, publicKey);\n }\n \n private async verifyCryptoSignatureNode(\n data: string,\n signature: string,\n publicKey: string\n ): Promise<boolean> {\n if (!cryptoModule) {\n try {\n // @ts-ignore\n cryptoModule = await import('crypto');\n } catch (e) {\n // ignore\n }\n }\n\n if (!cryptoModule) {\n this.logger.error('Crypto module not available for signature verification');\n return false;\n }\n \n try {\n // Create verify object based on algorithm\n if (this.config.algorithm === 'ES256') {\n // ECDSA verification - requires lowercase 'sha256'\n const verify = cryptoModule.createVerify('sha256');\n verify.update(data);\n return verify.verify(\n {\n key: publicKey,\n format: 'pem',\n type: 'spki',\n },\n signature,\n 'base64'\n );\n } else {\n // RSA verification (RS256)\n const verify = cryptoModule.createVerify('RSA-SHA256');\n verify.update(data);\n return verify.verify(publicKey, signature, 'base64');\n }\n } catch (error) {\n this.logger.error('Signature verification failed', error as Error);\n return false;\n }\n }\n \n private async verifyCryptoSignatureBrowser(\n data: string,\n signature: string,\n publicKey: string\n ): Promise<boolean> {\n try {\n const subtle = globalThis.crypto?.subtle;\n if (!subtle) {\n this.logger.error('SubtleCrypto not available in this environment');\n return false;\n }\n\n // Decode PEM public key to raw DER bytes\n const pemBody = publicKey\n .replace(/-----BEGIN PUBLIC KEY-----/, '')\n .replace(/-----END PUBLIC KEY-----/, '')\n .replace(/\\s/g, '');\n const keyBytes = Uint8Array.from(atob(pemBody), c => c.charCodeAt(0));\n\n // Configure algorithms based on RS256 or ES256\n let importAlgorithm: { name: string; hash?: string; namedCurve?: string };\n let verifyAlgorithm: { name: string; hash?: string };\n\n if (this.config.algorithm === 'ES256') {\n importAlgorithm = { name: 'ECDSA', namedCurve: 'P-256' };\n verifyAlgorithm = { name: 'ECDSA', hash: 'SHA-256' };\n } else {\n importAlgorithm = { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' };\n verifyAlgorithm = { name: 'RSASSA-PKCS1-v1_5' };\n }\n\n const cryptoKey = await subtle.importKey(\n 'spki',\n keyBytes,\n importAlgorithm,\n false,\n ['verify']\n );\n\n // Decode base64 signature to ArrayBuffer\n const signatureBytes = Uint8Array.from(atob(signature), c => c.charCodeAt(0));\n\n // Encode data to ArrayBuffer\n const dataBytes = new TextEncoder().encode(data);\n\n return await subtle.verify(verifyAlgorithm, cryptoKey, signatureBytes, dataBytes);\n } catch (error) {\n this.logger.error('Browser signature verification failed', error as Error);\n return false;\n }\n }\n \n private validateConfig(): void {\n if (!this.config.trustedPublicKeys || this.config.trustedPublicKeys.size === 0) {\n this.logger.warn('No trusted public keys configured - all signatures will fail');\n }\n \n if (!this.config.algorithm) {\n throw new Error('Signature algorithm must be specified');\n }\n \n if (!['RS256', 'ES256'].includes(this.config.algorithm)) {\n throw new Error(`Unsupported algorithm: ${this.config.algorithm}`);\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Logger } from '@objectstack/spec/contracts';\nimport type { PluginCapability, PluginPermissions as GrantedPermissions } from '@objectstack/spec/kernel';\nimport type { PluginContext } from '../types.js';\n\n/**\n * Plugin Permissions\n * Defines what actions a plugin is allowed to perform\n */\nexport interface PluginPermissions {\n canAccessService(serviceName: string): boolean;\n canTriggerHook(hookName: string): boolean;\n canReadFile(path: string): boolean;\n canWriteFile(path: string): boolean;\n canNetworkRequest(url: string): boolean;\n}\n\n/**\n * Permission Check Result\n */\nexport interface PermissionCheckResult {\n allowed: boolean;\n reason?: string;\n capability?: string;\n}\n\n/**\n * Plugin Permission Enforcer\n * \n * Implements capability-based security model to enforce:\n * 1. Service access control - which services a plugin can use\n * 2. Hook restrictions - which hooks a plugin can trigger\n * 3. File system permissions - what files a plugin can read/write\n * 4. Network permissions - what URLs a plugin can access\n * \n * Architecture:\n * - Uses capability declarations from plugin manifest\n * - Checks permissions before allowing operations\n * - Logs all permission denials for security audit\n * - Supports allowlist and denylist patterns\n * \n * Security Model:\n * - Principle of least privilege - plugins get minimal permissions\n * - Explicit declaration - all capabilities must be declared\n * - Runtime enforcement - checks happen at operation time\n * - Audit trail - all denials are logged\n * \n * Usage:\n * ```typescript\n * const enforcer = new PluginPermissionEnforcer(logger);\n * enforcer.registerPluginPermissions(pluginName, capabilities);\n * enforcer.enforceServiceAccess(pluginName, 'database');\n * ```\n */\nexport class PluginPermissionEnforcer {\n private logger: Logger;\n private permissionRegistry: Map<string, PluginPermissions> = new Map();\n private capabilityRegistry: Map<string, PluginCapability[]> = new Map();\n \n constructor(logger: Logger) {\n this.logger = logger;\n }\n \n /**\n * Register plugin capabilities and build permission set\n * \n * @param pluginName - Plugin identifier\n * @param capabilities - Array of capability declarations\n */\n registerPluginPermissions(pluginName: string, capabilities: PluginCapability[]): void {\n this.capabilityRegistry.set(pluginName, capabilities);\n \n const permissions: PluginPermissions = {\n canAccessService: (service) => this.checkServiceAccess(capabilities, service),\n canTriggerHook: (hook) => this.checkHookAccess(capabilities, hook),\n canReadFile: (path) => this.checkFileRead(capabilities, path),\n canWriteFile: (path) => this.checkFileWrite(capabilities, path),\n canNetworkRequest: (url) => this.checkNetworkAccess(capabilities, url),\n };\n \n this.permissionRegistry.set(pluginName, permissions);\n \n this.logger.info(`Permissions registered for plugin: ${pluginName}`, {\n plugin: pluginName,\n capabilityCount: capabilities.length,\n });\n }\n \n /**\n * Register the install-time GRANTED permission set for a plugin\n * (ADR-0025 F4). This is the structured `{ services, hooks, network, fs }`\n * grant that the cloud control plane persists to\n * `sys_package_installation.granted_permissions` after the user consents\n * at install (ADR §3.5 step 2). The runtime calls this when materializing\n * a third-party plugin so {@link SecurePluginContext} enforces exactly the\n * consented surface — independent of whatever the manifest *requested*.\n *\n * Prefer this over {@link registerPluginPermissions} for distributed\n * plugins: it enforces what was granted, not what was declared.\n */\n registerGrantedPermissions(pluginName: string, granted: GrantedPermissions | null | undefined): void {\n this.permissionRegistry.set(pluginName, buildPermissionsFromGrants(granted));\n this.logger.info(`Granted permissions registered for plugin: ${pluginName}`, {\n plugin: pluginName,\n services: granted?.services?.length ?? 0,\n hooks: granted?.hooks?.length ?? 0,\n network: granted?.network?.length ?? 0,\n fs: granted?.fs?.length ?? 0,\n });\n }\n\n /**\n * Enforce service access permission\n *\n * @param pluginName - Plugin requesting access\n * @param serviceName - Service to access\n * @throws Error if permission denied\n */\n enforceServiceAccess(pluginName: string, serviceName: string): void {\n const result = this.checkPermission(pluginName, (perms) => perms.canAccessService(serviceName));\n \n if (!result.allowed) {\n const error = `Permission denied: Plugin ${pluginName} cannot access service ${serviceName}`;\n this.logger.warn(error, {\n plugin: pluginName,\n service: serviceName,\n reason: result.reason,\n });\n throw new Error(error);\n }\n \n this.logger.debug(`Service access granted: ${pluginName} -> ${serviceName}`);\n }\n \n /**\n * Enforce hook trigger permission\n * \n * @param pluginName - Plugin requesting access\n * @param hookName - Hook to trigger\n * @throws Error if permission denied\n */\n enforceHookTrigger(pluginName: string, hookName: string): void {\n const result = this.checkPermission(pluginName, (perms) => perms.canTriggerHook(hookName));\n \n if (!result.allowed) {\n const error = `Permission denied: Plugin ${pluginName} cannot trigger hook ${hookName}`;\n this.logger.warn(error, {\n plugin: pluginName,\n hook: hookName,\n reason: result.reason,\n });\n throw new Error(error);\n }\n \n this.logger.debug(`Hook trigger granted: ${pluginName} -> ${hookName}`);\n }\n \n /**\n * Enforce file read permission\n * \n * @param pluginName - Plugin requesting access\n * @param path - File path to read\n * @throws Error if permission denied\n */\n enforceFileRead(pluginName: string, path: string): void {\n const result = this.checkPermission(pluginName, (perms) => perms.canReadFile(path));\n \n if (!result.allowed) {\n const error = `Permission denied: Plugin ${pluginName} cannot read file ${path}`;\n this.logger.warn(error, {\n plugin: pluginName,\n path,\n reason: result.reason,\n });\n throw new Error(error);\n }\n \n this.logger.debug(`File read granted: ${pluginName} -> ${path}`);\n }\n \n /**\n * Enforce file write permission\n * \n * @param pluginName - Plugin requesting access\n * @param path - File path to write\n * @throws Error if permission denied\n */\n enforceFileWrite(pluginName: string, path: string): void {\n const result = this.checkPermission(pluginName, (perms) => perms.canWriteFile(path));\n \n if (!result.allowed) {\n const error = `Permission denied: Plugin ${pluginName} cannot write file ${path}`;\n this.logger.warn(error, {\n plugin: pluginName,\n path,\n reason: result.reason,\n });\n throw new Error(error);\n }\n \n this.logger.debug(`File write granted: ${pluginName} -> ${path}`);\n }\n \n /**\n * Enforce network request permission\n * \n * @param pluginName - Plugin requesting access\n * @param url - URL to access\n * @throws Error if permission denied\n */\n enforceNetworkRequest(pluginName: string, url: string): void {\n const result = this.checkPermission(pluginName, (perms) => perms.canNetworkRequest(url));\n \n if (!result.allowed) {\n const error = `Permission denied: Plugin ${pluginName} cannot access URL ${url}`;\n this.logger.warn(error, {\n plugin: pluginName,\n url,\n reason: result.reason,\n });\n throw new Error(error);\n }\n \n this.logger.debug(`Network request granted: ${pluginName} -> ${url}`);\n }\n \n /**\n * Get plugin capabilities\n * \n * @param pluginName - Plugin identifier\n * @returns Array of capabilities or undefined\n */\n getPluginCapabilities(pluginName: string): PluginCapability[] | undefined {\n return this.capabilityRegistry.get(pluginName);\n }\n \n /**\n * Get plugin permissions\n * \n * @param pluginName - Plugin identifier\n * @returns Permissions object or undefined\n */\n getPluginPermissions(pluginName: string): PluginPermissions | undefined {\n return this.permissionRegistry.get(pluginName);\n }\n \n /**\n * Revoke all permissions for a plugin\n * \n * @param pluginName - Plugin identifier\n */\n revokePermissions(pluginName: string): void {\n this.permissionRegistry.delete(pluginName);\n this.capabilityRegistry.delete(pluginName);\n this.logger.warn(`Permissions revoked for plugin: ${pluginName}`);\n }\n \n // Private methods\n \n private checkPermission(\n pluginName: string,\n check: (perms: PluginPermissions) => boolean\n ): PermissionCheckResult {\n const permissions = this.permissionRegistry.get(pluginName);\n \n if (!permissions) {\n return {\n allowed: false,\n reason: 'Plugin permissions not registered',\n };\n }\n \n const allowed = check(permissions);\n \n return {\n allowed,\n reason: allowed ? undefined : 'No matching capability found',\n };\n }\n \n private checkServiceAccess(capabilities: PluginCapability[], serviceName: string): boolean {\n // Check if plugin has capability to access this service\n return capabilities.some(cap => {\n const protocolId = cap.protocol.id;\n \n // Check for wildcard service access\n if (protocolId.includes('protocol.service.all')) {\n return true;\n }\n \n // Check for specific service protocol\n if (protocolId.includes(`protocol.service.${serviceName}`)) {\n return true;\n }\n \n // Check for service category match\n const serviceCategory = serviceName.split('.')[0];\n if (protocolId.includes(`protocol.service.${serviceCategory}`)) {\n return true;\n }\n \n return false;\n });\n }\n \n private checkHookAccess(capabilities: PluginCapability[], hookName: string): boolean {\n // Check if plugin has capability to trigger this hook\n return capabilities.some(cap => {\n const protocolId = cap.protocol.id;\n \n // Check for wildcard hook access\n if (protocolId.includes('protocol.hook.all')) {\n return true;\n }\n \n // Check for specific hook protocol\n if (protocolId.includes(`protocol.hook.${hookName}`)) {\n return true;\n }\n \n // Check for hook category match\n const hookCategory = hookName.split(':')[0];\n if (protocolId.includes(`protocol.hook.${hookCategory}`)) {\n return true;\n }\n \n return false;\n });\n }\n \n private matchGlob(pattern: string, str: string): boolean {\n const regexStr = pattern\n .split('**')\n .map(segment => {\n const escaped = segment.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return escaped.replace(/\\*/g, '[^/]*');\n })\n .join('.*');\n return new RegExp(`^${regexStr}$`).test(str);\n }\n \n private checkFileRead(capabilities: PluginCapability[], path: string): boolean {\n // Check if plugin has capability to read this file\n return capabilities.some(cap => {\n const protocolId = cap.protocol.id;\n \n // Check for file read capability\n if (protocolId.includes('protocol.filesystem.read')) {\n const paths = cap.metadata?.paths;\n if (!Array.isArray(paths) || paths.length === 0) {\n return true;\n }\n return paths.some(p => typeof p === 'string' && this.matchGlob(p, path));\n }\n \n return false;\n });\n }\n \n private checkFileWrite(capabilities: PluginCapability[], path: string): boolean {\n // Check if plugin has capability to write this file\n return capabilities.some(cap => {\n const protocolId = cap.protocol.id;\n \n // Check for file write capability\n if (protocolId.includes('protocol.filesystem.write')) {\n const paths = cap.metadata?.paths;\n if (!Array.isArray(paths) || paths.length === 0) {\n return true;\n }\n return paths.some(p => typeof p === 'string' && this.matchGlob(p, path));\n }\n \n return false;\n });\n }\n \n private checkNetworkAccess(capabilities: PluginCapability[], url: string): boolean {\n // Check if plugin has capability to access this URL\n return capabilities.some(cap => {\n const protocolId = cap.protocol.id;\n \n // Check for network capability\n if (protocolId.includes('protocol.network')) {\n const hosts = cap.metadata?.hosts;\n if (!Array.isArray(hosts) || hosts.length === 0) {\n return true;\n }\n return hosts.some(h => typeof h === 'string' && this.matchGlob(h, url));\n }\n \n return false;\n });\n }\n}\n\n/**\n * Secure Plugin Context\n * Wraps PluginContext with permission checks\n */\nexport class SecurePluginContext implements PluginContext {\n constructor(\n private pluginName: string,\n private permissionEnforcer: PluginPermissionEnforcer,\n private baseContext: PluginContext\n ) {}\n \n registerService(name: string, service: any): void {\n // No permission check for service registration (handled during init)\n this.baseContext.registerService(name, service);\n }\n \n getService<T>(name: string): T {\n // Check permission before accessing service\n this.permissionEnforcer.enforceServiceAccess(this.pluginName, name);\n return this.baseContext.getService<T>(name);\n }\n \n replaceService<T>(name: string, implementation: T): void {\n // Check permission before replacing service\n this.permissionEnforcer.enforceServiceAccess(this.pluginName, name);\n this.baseContext.replaceService(name, implementation);\n }\n \n getServices(): Map<string, any> {\n // Return all services (no permission check for listing)\n return this.baseContext.getServices();\n }\n \n hook(name: string, handler: (...args: any[]) => void | Promise<void>): void {\n // No permission check for registering hooks (handled during init)\n this.baseContext.hook(name, handler);\n }\n \n async trigger(name: string, ...args: any[]): Promise<void> {\n // Check permission before triggering hook\n this.permissionEnforcer.enforceHookTrigger(this.pluginName, name);\n await this.baseContext.trigger(name, ...args);\n }\n \n get logger() {\n return this.baseContext.logger;\n }\n \n getKernel() {\n return this.baseContext.getKernel();\n }\n\n registerServiceFactory(name: string, factory: (ctx: PluginContext, scopeId?: string) => any, lifecycle?: import('../plugin-loader.js').ServiceLifecycle, dependencies?: string[]): void {\n this.baseContext.registerServiceFactory(name, factory, lifecycle, dependencies);\n }\n\n getServiceScoped<T>(name: string, scopeId: string): Promise<T> {\n return this.baseContext.getServiceScoped<T>(name, scopeId);\n }\n}\n\n/**\n * Create a plugin permission enforcer\n *\n * @param logger - Logger instance\n * @returns Plugin permission enforcer\n */\nexport function createPluginPermissionEnforcer(logger: Logger): PluginPermissionEnforcer {\n return new PluginPermissionEnforcer(logger);\n}\n\n/**\n * Glob match supporting `*` (within a path segment) and `**` (across\n * segments). A bare `*` entry matches everything.\n */\nfunction grantGlobMatch(pattern: string, value: string): boolean {\n if (pattern === '*' || pattern === '**') return true;\n const regexStr = pattern\n .split('**')\n .map((segment) => segment.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '[^/]*'))\n .join('.*');\n return new RegExp(`^${regexStr}$`).test(value);\n}\n\n/** Extract the host from a URL for network-grant matching; falls back to the raw value. */\nfunction hostOf(url: string): string {\n try {\n return new URL(url).host;\n } catch {\n return url;\n }\n}\n\nconst inList = (list: string[] | undefined, value: string): boolean =>\n Array.isArray(list) && list.some((p) => p === value || grantGlobMatch(p, value));\n\n/**\n * Build the runtime {@link PluginPermissions} bag from a structured\n * install-time grant set (ADR-0025 §3.2 `{ services, hooks, network, fs }`).\n *\n * Matching: an entry allows when it equals the requested value, is a glob\n * that matches it, or is the wildcard `*`. Network grants match against the\n * request URL's host (or the raw URL). `fs` governs both read and write —\n * the structured grant set does not split the two. A null/empty grant set\n * denies everything (principle of least privilege).\n */\nexport function buildPermissionsFromGrants(\n granted: GrantedPermissions | null | undefined,\n): PluginPermissions {\n const services = granted?.services;\n const hooks = granted?.hooks;\n const network = granted?.network;\n const fs = granted?.fs;\n return {\n canAccessService: (name) => inList(services, name),\n canTriggerHook: (name) => inList(hooks, name),\n canReadFile: (path) => inList(fs, path),\n canWriteFile: (path) => inList(fs, path),\n canNetworkRequest: (url) =>\n inList(network, hostOf(url)) || inList(network, url),\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { \n PluginPermission,\n PluginPermissionSet,\n PermissionAction,\n ResourceType\n} from '@objectstack/spec/kernel';\nimport type { ObjectLogger } from '../logger.js';\n\n/**\n * Permission Grant\n * Represents a granted permission at runtime\n */\nexport interface PermissionGrant {\n permissionId: string;\n pluginId: string;\n grantedAt: Date;\n grantedBy?: string;\n expiresAt?: Date;\n conditions?: Record<string, any>;\n}\n\n/**\n * Permission Check Result\n */\nexport interface PermissionCheckResult {\n allowed: boolean;\n reason?: string;\n requiredPermission?: string;\n grantedPermissions?: string[];\n}\n\n/**\n * Plugin Permission Manager\n * \n * Manages fine-grained permissions for plugin security and access control\n */\nexport class PluginPermissionManager {\n private logger: ObjectLogger;\n \n // Plugin permission definitions\n private permissionSets = new Map<string, PluginPermissionSet>();\n \n // Granted permissions (pluginId -> Set of permission IDs)\n private grants = new Map<string, Set<string>>();\n \n // Permission grant details\n private grantDetails = new Map<string, PermissionGrant>();\n\n constructor(logger: ObjectLogger) {\n this.logger = logger.child({ component: 'PermissionManager' });\n }\n\n /**\n * Register permission requirements for a plugin\n */\n registerPermissions(pluginId: string, permissionSet: PluginPermissionSet): void {\n this.permissionSets.set(pluginId, permissionSet);\n \n this.logger.info('Permissions registered for plugin', { \n pluginId,\n permissionCount: permissionSet.permissions.length\n });\n }\n\n /**\n * Grant a permission to a plugin\n */\n grantPermission(\n pluginId: string,\n permissionId: string,\n grantedBy?: string,\n expiresAt?: Date\n ): void {\n // Verify permission exists in plugin's declared permissions\n const permissionSet = this.permissionSets.get(pluginId);\n if (!permissionSet) {\n throw new Error(`No permissions registered for plugin: ${pluginId}`);\n }\n\n const permission = permissionSet.permissions.find(p => p.id === permissionId);\n if (!permission) {\n throw new Error(`Permission ${permissionId} not declared by plugin ${pluginId}`);\n }\n\n // Create grant\n if (!this.grants.has(pluginId)) {\n this.grants.set(pluginId, new Set());\n }\n this.grants.get(pluginId)!.add(permissionId);\n\n // Store grant details\n const grantKey = `${pluginId}:${permissionId}`;\n this.grantDetails.set(grantKey, {\n permissionId,\n pluginId,\n grantedAt: new Date(),\n grantedBy,\n expiresAt,\n });\n\n this.logger.info('Permission granted', { \n pluginId, \n permissionId,\n grantedBy \n });\n }\n\n /**\n * Revoke a permission from a plugin\n */\n revokePermission(pluginId: string, permissionId: string): void {\n const grants = this.grants.get(pluginId);\n if (grants) {\n grants.delete(permissionId);\n \n const grantKey = `${pluginId}:${permissionId}`;\n this.grantDetails.delete(grantKey);\n\n this.logger.info('Permission revoked', { pluginId, permissionId });\n }\n }\n\n /**\n * Grant all permissions for a plugin\n */\n grantAllPermissions(pluginId: string, grantedBy?: string): void {\n const permissionSet = this.permissionSets.get(pluginId);\n if (!permissionSet) {\n throw new Error(`No permissions registered for plugin: ${pluginId}`);\n }\n\n for (const permission of permissionSet.permissions) {\n this.grantPermission(pluginId, permission.id, grantedBy);\n }\n\n this.logger.info('All permissions granted', { pluginId, grantedBy });\n }\n\n /**\n * Check if a plugin has a specific permission\n */\n hasPermission(pluginId: string, permissionId: string): boolean {\n const grants = this.grants.get(pluginId);\n if (!grants) {\n return false;\n }\n\n // Check if granted\n if (!grants.has(permissionId)) {\n return false;\n }\n\n // Check expiration\n const grantKey = `${pluginId}:${permissionId}`;\n const grantDetails = this.grantDetails.get(grantKey);\n if (grantDetails?.expiresAt && grantDetails.expiresAt < new Date()) {\n this.revokePermission(pluginId, permissionId);\n return false;\n }\n\n return true;\n }\n\n /**\n * Check if plugin can perform an action on a resource\n */\n checkAccess(\n pluginId: string,\n resource: ResourceType,\n action: PermissionAction,\n resourceId?: string\n ): PermissionCheckResult {\n const permissionSet = this.permissionSets.get(pluginId);\n if (!permissionSet) {\n return {\n allowed: false,\n reason: 'No permissions registered for plugin',\n };\n }\n\n // Find matching permissions\n const matchingPermissions = permissionSet.permissions.filter(p => {\n // Check resource type\n if (p.resource !== resource) {\n return false;\n }\n\n // Check action\n if (!p.actions.includes(action)) {\n return false;\n }\n\n // Check resource filter if specified\n if (resourceId && p.filter?.resourceIds) {\n if (!p.filter.resourceIds.includes(resourceId)) {\n return false;\n }\n }\n\n return true;\n });\n\n if (matchingPermissions.length === 0) {\n return {\n allowed: false,\n reason: `No permission found for ${action} on ${resource}`,\n };\n }\n\n // Check if any matching permission is granted\n const grantedPermissions = matchingPermissions.filter(p => \n this.hasPermission(pluginId, p.id)\n );\n\n if (grantedPermissions.length === 0) {\n return {\n allowed: false,\n reason: 'Required permissions not granted',\n requiredPermission: matchingPermissions[0].id,\n };\n }\n\n return {\n allowed: true,\n grantedPermissions: grantedPermissions.map(p => p.id),\n };\n }\n\n /**\n * Get all permissions for a plugin\n */\n getPluginPermissions(pluginId: string): PluginPermission[] {\n const permissionSet = this.permissionSets.get(pluginId);\n return permissionSet?.permissions || [];\n }\n\n /**\n * Get granted permissions for a plugin\n */\n getGrantedPermissions(pluginId: string): string[] {\n const grants = this.grants.get(pluginId);\n return grants ? Array.from(grants) : [];\n }\n\n /**\n * Get required but not granted permissions\n */\n getMissingPermissions(pluginId: string): PluginPermission[] {\n const permissionSet = this.permissionSets.get(pluginId);\n if (!permissionSet) {\n return [];\n }\n\n const granted = this.grants.get(pluginId) || new Set();\n \n return permissionSet.permissions.filter(p => \n p.required && !granted.has(p.id)\n );\n }\n\n /**\n * Check if all required permissions are granted\n */\n hasAllRequiredPermissions(pluginId: string): boolean {\n return this.getMissingPermissions(pluginId).length === 0;\n }\n\n /**\n * Get permission grant details\n */\n getGrantDetails(pluginId: string, permissionId: string): PermissionGrant | undefined {\n const grantKey = `${pluginId}:${permissionId}`;\n return this.grantDetails.get(grantKey);\n }\n\n /**\n * Validate permission against scope constraints\n */\n validatePermissionScope(\n permission: PluginPermission,\n context: {\n tenantId?: string;\n userId?: string;\n resourceId?: string;\n }\n ): boolean {\n switch (permission.scope) {\n case 'global':\n return true;\n\n case 'tenant':\n return !!context.tenantId;\n\n case 'user':\n return !!context.userId;\n\n case 'resource':\n return !!context.resourceId;\n\n case 'plugin':\n return true;\n\n default:\n return false;\n }\n }\n\n /**\n * Clear all permissions for a plugin\n */\n clearPluginPermissions(pluginId: string): void {\n this.permissionSets.delete(pluginId);\n \n const grants = this.grants.get(pluginId);\n if (grants) {\n for (const permissionId of grants) {\n const grantKey = `${pluginId}:${permissionId}`;\n this.grantDetails.delete(grantKey);\n }\n this.grants.delete(pluginId);\n }\n\n this.logger.info('All permissions cleared', { pluginId });\n }\n\n /**\n * Shutdown permission manager\n */\n shutdown(): void {\n this.permissionSets.clear();\n this.grants.clear();\n this.grantDetails.clear();\n \n this.logger.info('Permission manager shutdown complete');\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport nodePath from 'node:path';\n\nimport type { \n SandboxConfig\n} from '@objectstack/spec/kernel';\nimport type { ObjectLogger } from '../logger.js';\nimport { getMemoryUsage } from '../utils/env.js';\n\n/**\n * Resource Usage Statistics\n */\nexport interface ResourceUsage {\n memory: {\n current: number;\n peak: number;\n limit?: number;\n };\n cpu: {\n current: number;\n average: number;\n limit?: number;\n };\n connections: {\n current: number;\n limit?: number;\n };\n}\n\n/**\n * Sandbox Execution Context\n * Represents an isolated execution environment for a plugin\n */\nexport interface SandboxContext {\n pluginId: string;\n config: SandboxConfig;\n startTime: Date;\n resourceUsage: ResourceUsage;\n}\n\n/**\n * Plugin Sandbox Runtime\n * \n * Provides isolated execution environments for plugins with resource limits\n * and access controls\n */\nexport class PluginSandboxRuntime {\n private static readonly MONITORING_INTERVAL_MS = 5000;\n\n private logger: ObjectLogger;\n \n // Active sandboxes (pluginId -> context)\n private sandboxes = new Map<string, SandboxContext>();\n \n // Resource monitoring intervals\n private monitoringIntervals = new Map<string, NodeJS.Timeout>();\n\n // Per-plugin resource baselines for delta tracking\n private memoryBaselines = new Map<string, number>();\n private cpuBaselines = new Map<string, { user: number; system: number }>();\n\n constructor(logger: ObjectLogger) {\n this.logger = logger.child({ component: 'SandboxRuntime' });\n }\n\n /**\n * Create a sandbox for a plugin\n */\n createSandbox(pluginId: string, config: SandboxConfig): SandboxContext {\n if (this.sandboxes.has(pluginId)) {\n throw new Error(`Sandbox already exists for plugin: ${pluginId}`);\n }\n\n const context: SandboxContext = {\n pluginId,\n config,\n startTime: new Date(),\n resourceUsage: {\n memory: { current: 0, peak: 0, limit: config.memory?.maxHeap },\n cpu: { current: 0, average: 0, limit: config.cpu?.maxCpuPercent },\n connections: { current: 0, limit: config.network?.maxConnections },\n },\n };\n\n this.sandboxes.set(pluginId, context);\n\n // Capture resource baselines for per-plugin delta tracking\n const baselineMemory = getMemoryUsage();\n this.memoryBaselines.set(pluginId, baselineMemory.heapUsed);\n this.cpuBaselines.set(pluginId, process.cpuUsage());\n\n // Start resource monitoring\n this.startResourceMonitoring(pluginId);\n\n this.logger.info('Sandbox created', { \n pluginId,\n level: config.level,\n memoryLimit: config.memory?.maxHeap,\n cpuLimit: config.cpu?.maxCpuPercent\n });\n\n return context;\n }\n\n /**\n * Destroy a sandbox\n */\n destroySandbox(pluginId: string): void {\n const context = this.sandboxes.get(pluginId);\n if (!context) {\n return;\n }\n\n // Stop monitoring\n this.stopResourceMonitoring(pluginId);\n\n this.memoryBaselines.delete(pluginId);\n this.cpuBaselines.delete(pluginId);\n this.sandboxes.delete(pluginId);\n\n this.logger.info('Sandbox destroyed', { pluginId });\n }\n\n /**\n * Check if resource access is allowed\n */\n checkResourceAccess(\n pluginId: string,\n resourceType: 'file' | 'network' | 'process' | 'env',\n resourcePath?: string\n ): { allowed: boolean; reason?: string } {\n const context = this.sandboxes.get(pluginId);\n if (!context) {\n return { allowed: false, reason: 'Sandbox not found' };\n }\n\n const { config } = context;\n\n switch (resourceType) {\n case 'file':\n return this.checkFileAccess(config, resourcePath);\n \n case 'network':\n return this.checkNetworkAccess(config, resourcePath);\n \n case 'process':\n return this.checkProcessAccess(config);\n \n case 'env':\n return this.checkEnvAccess(config, resourcePath);\n \n default:\n return { allowed: false, reason: 'Unknown resource type' };\n }\n }\n\n /**\n * Check file system access\n * Uses path.resolve() and path.normalize() to prevent directory traversal.\n */\n private checkFileAccess(\n config: SandboxConfig,\n filePath?: string\n ): { allowed: boolean; reason?: string } {\n if (config.level === 'none') {\n return { allowed: true };\n }\n\n if (!config.filesystem) {\n return { allowed: false, reason: 'File system access not configured' };\n }\n\n // If no path specified, check general access\n if (!filePath) {\n return { allowed: config.filesystem.mode !== 'none' };\n }\n\n // Check allowed paths using proper path resolution to prevent directory traversal\n const allowedPaths = config.filesystem.allowedPaths || [];\n const resolvedPath = nodePath.normalize(nodePath.resolve(filePath));\n const isAllowed = allowedPaths.some(allowed => {\n const resolvedAllowed = nodePath.normalize(nodePath.resolve(allowed));\n return resolvedPath.startsWith(resolvedAllowed);\n });\n\n if (allowedPaths.length > 0 && !isAllowed) {\n return { \n allowed: false, \n reason: `Path not in allowed list: ${filePath}` \n };\n }\n\n // Check denied paths using proper path resolution\n const deniedPaths = config.filesystem.deniedPaths || [];\n const isDenied = deniedPaths.some(denied => {\n const resolvedDenied = nodePath.normalize(nodePath.resolve(denied));\n return resolvedPath.startsWith(resolvedDenied);\n });\n\n if (isDenied) {\n return { \n allowed: false, \n reason: `Path is explicitly denied: ${filePath}` \n };\n }\n\n return { allowed: true };\n }\n\n /**\n * Check network access\n * Uses URL parsing to properly validate hostnames.\n */\n private checkNetworkAccess(\n config: SandboxConfig,\n url?: string\n ): { allowed: boolean; reason?: string } {\n if (config.level === 'none') {\n return { allowed: true };\n }\n\n if (!config.network) {\n return { allowed: false, reason: 'Network access not configured' };\n }\n\n // Check if network access is enabled\n if (config.network.mode === 'none') {\n return { allowed: false, reason: 'Network access disabled' };\n }\n\n // If no URL specified, check general access\n if (!url) {\n return { allowed: (config.network.mode as string) !== 'none' };\n }\n\n // Parse URL and check hostname against allowed/denied hosts\n let parsedHostname: string;\n try {\n parsedHostname = new URL(url).hostname;\n } catch {\n return { allowed: false, reason: `Invalid URL: ${url}` };\n }\n\n // Check allowed hosts\n const allowedHosts = config.network.allowedHosts || [];\n if (allowedHosts.length > 0) {\n const isAllowed = allowedHosts.some(host => {\n return parsedHostname === host;\n });\n\n if (!isAllowed) {\n return { \n allowed: false, \n reason: `Host not in allowed list: ${url}` \n };\n }\n }\n\n // Check denied hosts\n const deniedHosts = config.network.deniedHosts || [];\n const isDenied = deniedHosts.some(host => {\n return parsedHostname === host;\n });\n\n if (isDenied) {\n return { \n allowed: false, \n reason: `Host is blocked: ${url}` \n };\n }\n\n return { allowed: true };\n }\n\n /**\n * Check process spawning access\n */\n private checkProcessAccess(\n config: SandboxConfig\n ): { allowed: boolean; reason?: string } {\n if (config.level === 'none') {\n return { allowed: true };\n }\n\n if (!config.process) {\n return { allowed: false, reason: 'Process access not configured' };\n }\n\n if (!config.process.allowSpawn) {\n return { allowed: false, reason: 'Process spawning not allowed' };\n }\n\n return { allowed: true };\n }\n\n /**\n * Check environment variable access\n */\n private checkEnvAccess(\n config: SandboxConfig,\n varName?: string\n ): { allowed: boolean; reason?: string } {\n if (config.level === 'none') {\n return { allowed: true };\n }\n\n if (!config.process) {\n return { allowed: false, reason: 'Environment access not configured' };\n }\n\n // If no variable specified, check general access\n if (!varName) {\n return { allowed: true };\n }\n\n // For now, allow all env access if process is configured\n // In a real implementation, would check specific allowed vars\n return { allowed: true };\n }\n\n /**\n * Check resource limits\n */\n checkResourceLimits(pluginId: string): { \n withinLimits: boolean; \n violations: string[] \n } {\n const context = this.sandboxes.get(pluginId);\n if (!context) {\n return { withinLimits: true, violations: [] };\n }\n\n const violations: string[] = [];\n const { resourceUsage, config } = context;\n\n // Check memory limit\n if (config.memory?.maxHeap && \n resourceUsage.memory.current > config.memory.maxHeap) {\n violations.push(`Memory limit exceeded: ${resourceUsage.memory.current} > ${config.memory.maxHeap}`);\n }\n\n // Check CPU limit (would need runtime config)\n if (config.runtime?.resourceLimits?.maxCpu && \n resourceUsage.cpu.current > config.runtime.resourceLimits.maxCpu) {\n violations.push(`CPU limit exceeded: ${resourceUsage.cpu.current}% > ${config.runtime.resourceLimits.maxCpu}%`);\n }\n\n // Check connection limit\n if (config.network?.maxConnections && \n resourceUsage.connections.current > config.network.maxConnections) {\n violations.push(`Connection limit exceeded: ${resourceUsage.connections.current} > ${config.network.maxConnections}`);\n }\n\n return {\n withinLimits: violations.length === 0,\n violations,\n };\n }\n\n /**\n * Get resource usage for a plugin\n */\n getResourceUsage(pluginId: string): ResourceUsage | undefined {\n const context = this.sandboxes.get(pluginId);\n return context?.resourceUsage;\n }\n\n /**\n * Start monitoring resource usage\n */\n private startResourceMonitoring(pluginId: string): void {\n // Monitor at the configured interval\n const interval = setInterval(() => {\n this.updateResourceUsage(pluginId);\n }, PluginSandboxRuntime.MONITORING_INTERVAL_MS);\n\n this.monitoringIntervals.set(pluginId, interval);\n }\n\n /**\n * Stop monitoring resource usage\n */\n private stopResourceMonitoring(pluginId: string): void {\n const interval = this.monitoringIntervals.get(pluginId);\n if (interval) {\n clearInterval(interval);\n this.monitoringIntervals.delete(pluginId);\n }\n }\n\n /**\n * Update resource usage statistics\n * \n * Tracks per-plugin memory and CPU usage using delta from baseline\n * captured at sandbox creation time. This is an approximation since\n * true per-plugin isolation isn't possible in a single Node.js process.\n */\n private updateResourceUsage(pluginId: string): void {\n const context = this.sandboxes.get(pluginId);\n if (!context) {\n return;\n }\n\n // In a real implementation, this would collect actual metrics\n // For now, this is a placeholder structure\n \n // Update memory usage using delta from baseline for per-plugin approximation\n const memoryUsage = getMemoryUsage();\n const memoryBaseline = this.memoryBaselines.get(pluginId) ?? 0;\n const memoryDelta = Math.max(0, memoryUsage.heapUsed - memoryBaseline);\n context.resourceUsage.memory.current = memoryDelta;\n context.resourceUsage.memory.peak = Math.max(\n context.resourceUsage.memory.peak,\n memoryDelta\n );\n\n // Update CPU usage using delta from baseline for per-plugin approximation\n const cpuBaseline = this.cpuBaselines.get(pluginId) ?? { user: 0, system: 0 };\n const cpuCurrent = process.cpuUsage();\n const cpuDeltaUser = cpuCurrent.user - cpuBaseline.user;\n const cpuDeltaSystem = cpuCurrent.system - cpuBaseline.system;\n // Convert microseconds to a percentage approximation over the monitoring interval\n const totalCpuMicros = cpuDeltaUser + cpuDeltaSystem;\n const intervalMicros = PluginSandboxRuntime.MONITORING_INTERVAL_MS * 1000;\n context.resourceUsage.cpu.current = (totalCpuMicros / intervalMicros) * 100;\n // Update baseline for next interval\n this.cpuBaselines.set(pluginId, cpuCurrent);\n\n // Check for violations\n const { withinLimits, violations } = this.checkResourceLimits(pluginId);\n if (!withinLimits) {\n this.logger.warn('Resource limit violations detected', { \n pluginId, \n violations \n });\n }\n }\n\n /**\n * Get all active sandboxes\n */\n getAllSandboxes(): Map<string, SandboxContext> {\n return new Map(this.sandboxes);\n }\n\n /**\n * Shutdown sandbox runtime\n */\n shutdown(): void {\n // Stop all monitoring\n for (const pluginId of this.monitoringIntervals.keys()) {\n this.stopResourceMonitoring(pluginId);\n }\n\n this.sandboxes.clear();\n this.memoryBaselines.clear();\n this.cpuBaselines.clear();\n \n this.logger.info('Sandbox runtime shutdown complete');\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { \n KernelSecurityVulnerability,\n KernelSecurityScanResult\n} from '@objectstack/spec/kernel';\nimport type { ObjectLogger } from '../logger.js';\n\n/**\n * Scan Target\n */\nexport interface ScanTarget {\n pluginId: string;\n version: string;\n files?: string[];\n dependencies?: Record<string, string>;\n}\n\n/**\n * Security Issue\n */\nexport interface SecurityIssue {\n id: string;\n severity: 'critical' | 'high' | 'medium' | 'low' | 'info';\n category: 'vulnerability' | 'malware' | 'license' | 'code-quality' | 'configuration';\n title: string;\n description: string;\n location?: {\n file?: string;\n line?: number;\n column?: number;\n };\n remediation?: string;\n cve?: string;\n cvss?: number;\n}\n\n/**\n * Plugin Security Scanner\n * \n * Scans plugins for security vulnerabilities, malware, and license issues\n */\nexport class PluginSecurityScanner {\n private logger: ObjectLogger;\n \n // Known vulnerabilities database (CVE cache)\n private vulnerabilityDb = new Map<string, KernelSecurityVulnerability>();\n \n // Scan results cache\n private scanResults = new Map<string, KernelSecurityScanResult>();\n\n private passThreshold: number = 70;\n\n constructor(logger: ObjectLogger, config?: { passThreshold?: number }) {\n this.logger = logger.child({ component: 'SecurityScanner' });\n if (config?.passThreshold !== undefined) {\n this.passThreshold = config.passThreshold;\n }\n }\n\n /**\n * Perform a comprehensive security scan on a plugin\n */\n async scan(target: ScanTarget): Promise<KernelSecurityScanResult> {\n this.logger.info('Starting security scan', { \n pluginId: target.pluginId,\n version: target.version \n });\n\n const issues: SecurityIssue[] = [];\n\n try {\n // 1. Scan for code vulnerabilities\n const codeIssues = await this.scanCode(target);\n issues.push(...codeIssues);\n\n // 2. Scan dependencies for known vulnerabilities\n const depIssues = await this.scanDependencies(target);\n issues.push(...depIssues);\n\n // 3. Scan for malware patterns\n const malwareIssues = await this.scanMalware(target);\n issues.push(...malwareIssues);\n\n // 4. Check license compliance\n const licenseIssues = await this.scanLicenses(target);\n issues.push(...licenseIssues);\n\n // 5. Check configuration security\n const configIssues = await this.scanConfiguration(target);\n issues.push(...configIssues);\n\n // Calculate security score (0-100, higher is better)\n const score = this.calculateSecurityScore(issues);\n\n const result: KernelSecurityScanResult = {\n timestamp: new Date().toISOString(),\n scanner: { name: 'ObjectStack Security Scanner', version: '1.0.0' },\n status: score >= this.passThreshold ? 'passed' : 'failed',\n vulnerabilities: issues.map(issue => ({\n id: issue.id,\n severity: issue.severity,\n category: issue.category,\n title: issue.title,\n description: issue.description,\n location: issue.location ? `${issue.location.file}:${issue.location.line}` : undefined,\n remediation: issue.remediation,\n affectedVersions: [],\n exploitAvailable: false,\n patchAvailable: false,\n })),\n summary: {\n totalVulnerabilities: issues.length,\n criticalCount: issues.filter(i => i.severity === 'critical').length,\n highCount: issues.filter(i => i.severity === 'high').length,\n mediumCount: issues.filter(i => i.severity === 'medium').length,\n lowCount: issues.filter(i => i.severity === 'low').length,\n infoCount: issues.filter(i => i.severity === 'info').length,\n },\n };\n\n this.scanResults.set(`${target.pluginId}:${target.version}`, result);\n\n this.logger.info('Security scan complete', { \n pluginId: target.pluginId,\n score,\n status: result.status,\n summary: result.summary\n });\n\n return result;\n } catch (error) {\n this.logger.error('Security scan failed', { \n pluginId: target.pluginId, \n error \n });\n\n throw error;\n }\n }\n\n /**\n * Scan code for vulnerabilities\n */\n private async scanCode(target: ScanTarget): Promise<SecurityIssue[]> {\n const issues: SecurityIssue[] = [];\n\n // In a real implementation, this would:\n // - Parse code with AST (e.g., using @typescript-eslint/parser)\n // - Check for dangerous patterns (eval, Function constructor, etc.)\n // - Check for XSS vulnerabilities\n // - Check for SQL injection patterns\n // - Check for insecure crypto usage\n // - Check for path traversal vulnerabilities\n\n this.logger.debug('Code scan complete', { \n pluginId: target.pluginId,\n issuesFound: issues.length \n });\n\n return issues;\n }\n\n /**\n * Scan dependencies for known vulnerabilities\n */\n private async scanDependencies(target: ScanTarget): Promise<SecurityIssue[]> {\n const issues: SecurityIssue[] = [];\n\n if (!target.dependencies) {\n return issues;\n }\n\n // In a real implementation, this would:\n // - Query npm audit API\n // - Check GitHub Advisory Database\n // - Check Snyk vulnerability database\n // - Check OSV (Open Source Vulnerabilities)\n\n for (const [depName, version] of Object.entries(target.dependencies)) {\n const vulnKey = `${depName}@${version}`;\n const vulnerability = this.vulnerabilityDb.get(vulnKey);\n\n if (vulnerability) {\n issues.push({\n id: `vuln-${vulnerability.cve || depName}`,\n severity: vulnerability.severity,\n category: 'vulnerability',\n title: `Vulnerable dependency: ${depName}`,\n description: `${depName}@${version} has known security vulnerabilities`,\n remediation: vulnerability.fixedIn \n ? `Upgrade to ${vulnerability.fixedIn.join(' or ')}`\n : 'No fix available',\n cve: vulnerability.cve,\n });\n }\n }\n\n this.logger.debug('Dependency scan complete', { \n pluginId: target.pluginId,\n dependencies: Object.keys(target.dependencies).length,\n vulnerabilities: issues.length \n });\n\n return issues;\n }\n\n /**\n * Scan for malware patterns\n */\n private async scanMalware(target: ScanTarget): Promise<SecurityIssue[]> {\n const issues: SecurityIssue[] = [];\n\n // In a real implementation, this would:\n // - Check for obfuscated code\n // - Check for suspicious network activity patterns\n // - Check for crypto mining patterns\n // - Check for data exfiltration patterns\n // - Use ML-based malware detection\n // - Check file hashes against known malware databases\n\n this.logger.debug('Malware scan complete', { \n pluginId: target.pluginId,\n issuesFound: issues.length \n });\n\n return issues;\n }\n\n /**\n * Check license compliance\n */\n private async scanLicenses(target: ScanTarget): Promise<SecurityIssue[]> {\n const issues: SecurityIssue[] = [];\n\n if (!target.dependencies) {\n return issues;\n }\n\n // In a real implementation, this would:\n // - Check license compatibility\n // - Detect GPL contamination\n // - Flag proprietary dependencies\n // - Check for missing licenses\n // - Verify SPDX identifiers\n\n this.logger.debug('License scan complete', { \n pluginId: target.pluginId,\n issuesFound: issues.length \n });\n\n return issues;\n }\n\n /**\n * Check configuration security\n */\n private async scanConfiguration(target: ScanTarget): Promise<SecurityIssue[]> {\n const issues: SecurityIssue[] = [];\n\n // In a real implementation, this would:\n // - Check for hardcoded secrets\n // - Check for weak permissions\n // - Check for insecure defaults\n // - Check for missing security headers\n // - Check CSP policies\n\n this.logger.debug('Configuration scan complete', { \n pluginId: target.pluginId,\n issuesFound: issues.length \n });\n\n return issues;\n }\n\n /**\n * Calculate security score based on issues\n */\n private calculateSecurityScore(issues: SecurityIssue[]): number {\n // Start with perfect score\n let score = 100;\n\n // Deduct points based on severity\n for (const issue of issues) {\n switch (issue.severity) {\n case 'critical':\n score -= 20;\n break;\n case 'high':\n score -= 10;\n break;\n case 'medium':\n score -= 5;\n break;\n case 'low':\n score -= 2;\n break;\n case 'info':\n score -= 0;\n break;\n }\n }\n\n // Ensure score doesn't go below 0\n return Math.max(0, score);\n }\n\n /**\n * Add a vulnerability to the database\n */\n addVulnerability(\n packageName: string,\n version: string,\n vulnerability: KernelSecurityVulnerability\n ): void {\n const key = `${packageName}@${version}`;\n this.vulnerabilityDb.set(key, vulnerability);\n \n this.logger.debug('Vulnerability added to database', { \n package: packageName, \n version,\n cve: vulnerability.cve \n });\n }\n\n /**\n * Get scan result from cache\n */\n getScanResult(pluginId: string, version: string): KernelSecurityScanResult | undefined {\n return this.scanResults.get(`${pluginId}:${version}`);\n }\n\n /**\n * Clear scan results cache\n */\n clearCache(): void {\n this.scanResults.clear();\n this.logger.debug('Scan results cache cleared');\n }\n\n /**\n * Update vulnerability database from external source\n */\n async updateVulnerabilityDatabase(): Promise<void> {\n this.logger.info('Updating vulnerability database');\n\n // In a real implementation, this would:\n // - Fetch from GitHub Advisory Database\n // - Fetch from npm audit\n // - Fetch from NVD (National Vulnerability Database)\n // - Parse and cache vulnerability data\n\n this.logger.info('Vulnerability database updated', { \n entries: this.vulnerabilityDb.size \n });\n }\n\n /**\n * Shutdown security scanner\n */\n shutdown(): void {\n this.vulnerabilityDb.clear();\n this.scanResults.clear();\n \n this.logger.info('Security scanner shutdown complete');\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * api-key — hand-rolled API-key primitives + verifier for `sys_api_key`.\n *\n * better-auth 1.6.x ships no apiKey plugin, so ObjectStack owns the full\n * lifecycle: generation, at-rest hashing, header extraction, validation, and\n * the verify-time principal lookup. This is the SINGLE shared source of truth\n * used by BOTH inbound surfaces — the runtime dispatcher / MCP path\n * (`resolveExecutionContext`) and the REST data API (`@objectstack/rest`) — so\n * the two can never drift on how a key authenticates. It lives in\n * `@objectstack/core` (server-side; both `runtime` and `rest` depend on it,\n * and `core` depends on neither, so there is no cycle).\n *\n * SECURITY (zero-tolerance):\n * - The raw key is returned EXACTLY ONCE, by {@link generateApiKey}. It is\n * never persisted; only `sha256(raw)` (hex) is stored in `sys_api_key.key`.\n * - The raw key and its hash must never enter logs, HTTP responses, error\n * messages, commit messages or comments.\n * - Validation is fail-closed: anything ambiguous (missing, revoked, expired,\n * malformed) resolves to \"no principal\", never to an elevated one.\n */\n\nimport { createHash, randomBytes } from 'node:crypto';\n\nimport { postureEnforcesWall, postureUsesUnionScope, normalizeTenancyPosture } from '@objectstack/spec/security';\nimport type { TenancyPosture } from '@objectstack/spec/security';\n\n/** Default visible prefix for generated keys (helps users identify a key). */\nexport const API_KEY_PREFIX = 'osk_';\n\n/** Bytes of entropy in the secret portion of a generated key (256 bits). */\nconst API_KEY_ENTROPY_BYTES = 32;\n\n/** Length of the human-visible prefix stored in `sys_api_key.prefix`. */\nconst VISIBLE_PREFIX_LEN = 12;\n\n/**\n * Derive the at-rest hash for an API key. Inbound keys are hashed the same way\n * before the DB lookup. Because the lookup matches an indexed, high-entropy\n * hash exactly, this doubles as a constant-effort comparison: an attacker\n * cannot recover the raw key by probing for partial matches.\n */\nexport function hashApiKey(raw: string): string {\n return createHash('sha256').update(raw, 'utf8').digest('hex');\n}\n\n/** Result of {@link generateApiKey}. `raw` is shown to the user only once. */\nexport interface GeneratedApiKey {\n /** The full secret to hand to the client. NEVER persist this. */\n raw: string;\n /** `sha256(raw)` hex — store this in `sys_api_key.key`. */\n hash: string;\n /** Short non-secret prefix for display/identification (`sys_api_key.prefix`). */\n prefix: string;\n}\n\n/**\n * Generate a fresh API key. Returns the raw secret (caller must surface it to\n * the user exactly once and then discard it), its at-rest hash, and a short\n * non-secret prefix for display.\n */\nexport function generateApiKey(prefix: string = API_KEY_PREFIX): GeneratedApiKey {\n // base64url so the token is URL/header-safe with no padding.\n const secret = randomBytes(API_KEY_ENTROPY_BYTES).toString('base64url');\n const raw = `${prefix}${secret}`;\n return {\n raw,\n hash: hashApiKey(raw),\n prefix: raw.slice(0, VISIBLE_PREFIX_LEN),\n };\n}\n\n/**\n * Extract an API key from request headers. Accepts, in order:\n * - `X-API-Key: <token>`\n * - `Authorization: ApiKey <token>` (case-insensitive scheme)\n * - `Authorization: Bearer <token>` ONLY when `<token>` carries the ObjectStack\n * api-key prefix (`osk_`). Remote MCP clients (Claude Desktop / Cursor /\n * Claude Code) authenticate to `/api/v1/mcp` with the key as a Bearer per the\n * MCP spec, so rejecting Bearer outright made every standard MCP client fail.\n * A better-auth *session* token never starts with `osk_`, so a session Bearer\n * still falls through to the session path — this can't shadow it.\n */\nexport function extractApiKey(headers: any): string | undefined {\n const x = readHeader(headers, 'x-api-key');\n if (x && x.trim()) return x.trim();\n const auth = readHeader(headers, 'authorization');\n if (!auth) return undefined;\n const apiKeyScheme = auth.match(/^ApiKey\\s+(\\S.*)$/i);\n if (apiKeyScheme?.[1]?.trim()) return apiKeyScheme[1].trim();\n // Bearer is accepted only for prefixed api-keys (never for session tokens).\n const bearer = auth.match(/^Bearer\\s+(\\S.*)$/i)?.[1]?.trim();\n if (bearer && bearer.startsWith(API_KEY_PREFIX)) return bearer;\n return undefined;\n}\n\n/** Parse a `scopes` value that may be a JSON-string textarea or a real array. */\nexport function parseScopes(value: unknown): string[] {\n if (Array.isArray(value)) {\n return value.filter((s): s is string => typeof s === 'string' && s.length > 0);\n }\n if (typeof value === 'string' && value.trim()) {\n const parsed = safeJsonParse<unknown>(value, []);\n if (Array.isArray(parsed)) {\n return parsed.filter((s): s is string => typeof s === 'string' && s.length > 0);\n }\n }\n return [];\n}\n\n/** Return true when an expiry timestamp is in the past (i.e. the key is dead). */\nexport function isExpired(value: unknown, nowMs: number): boolean {\n if (value == null) return false;\n let ms: number;\n if (typeof value === 'number') {\n // Heuristic: seconds vs milliseconds epoch.\n ms = value < 1e12 ? value * 1000 : value;\n } else if (value instanceof Date) {\n ms = value.getTime();\n } else if (typeof value === 'string') {\n ms = Date.parse(value);\n } else {\n return false;\n }\n if (Number.isNaN(ms)) return false;\n return ms <= nowMs;\n}\n\n/** The principal resolved from a valid `sys_api_key`. */\nexport interface ApiKeyPrincipal {\n userId: string;\n /**\n * The organization this key authenticates INTO — read from the row's\n * `active_organization_id` and adopted by `resolveAuthzContext` as the\n * request's active organization (`ExecutionContext.tenantId`), which is what\n * lets the ADR-0105 Layer 0 wall match. `undefined` for a key minted before\n * #8287, or one minted under the `single` posture where there is no\n * organization to inherit.\n */\n tenantId?: string;\n scopes: string[];\n}\n\n/**\n * [#8287] Why a key was refused. Distinct from \"no key present\" and from \"this\n * key is unknown/revoked/expired\": a refusal means the credential is real and\n * intact but cannot be admitted under this deployment's tenancy posture.\n */\nexport type ApiKeyRefusalReason = 'organization_required' | 'organization_membership_ended';\n\n/**\n * The verdict on an inbound API key. Three outcomes, deliberately distinct:\n *\n * - `none` — no key header, or a key that is unknown / revoked / expired /\n * owner-less. Indistinguishable by design (never tell a prober which), and\n * the caller MAY fall through to the session path exactly as before.\n * - `admitted` — a usable principal.\n * - `refused` — a real, intact key the posture cannot admit. The caller must\n * NOT fall through to the session path: falling through would be more\n * permissive than today's behaviour (an API key already outranks a session),\n * and the whole point of the refusal is that it is LOUD at call time.\n */\nexport type ApiKeyAdmission =\n | { outcome: 'none' }\n | { outcome: 'admitted'; principal: ApiKeyPrincipal }\n | { outcome: 'refused'; reason: ApiKeyRefusalReason; message: string };\n\n/**\n * The shape of the kernel's `tenancy` service this module reads a posture from.\n * Structural on purpose: `@objectstack/core` must not depend on the plugin that\n * provides it, and an embedding without that plugin simply supplies nothing.\n */\nexport interface TenancyPostureSource {\n posture?: string;\n isolationActive?: boolean;\n}\n\n/**\n * [#8287] Resolve the EFFECTIVE tenancy posture from the kernel's `tenancy`\n * service — the same reconciliation `plugin-security` performs before handing a\n * posture to `computeTenantLayer0Filter`, so the wall and the API-key admission\n * can never disagree about which posture is in force.\n *\n * ⚠️ Deliberately NOT `resolveTenancyPosture()` from `@objectstack/types`, which\n * reads `OS_TENANCY_POSTURE` directly. That answers what the operator ASKED\n * for, not what is ENFORCED: under ADR-0093 D4/D5 a deployment that requests\n * `isolated` without the enterprise `@objectstack/organizations` runtime\n * resolves to `single` and runs with NO organization wall. Reading the env\n * there would refuse org-less API keys on a deployment whose wall is not even\n * active — breaking working automation to enforce a boundary that does not\n * exist. The `tenancy` service is the one place that already knows the\n * difference.\n *\n * Returns `undefined` when no service is available, which callers must treat as\n * \"no posture-conditional refusal\" — see {@link resolveApiKeyAdmission}.\n */\nexport function effectiveTenancyPosture(\n tenancy: TenancyPostureSource | undefined | null,\n): TenancyPosture | undefined {\n if (!tenancy) return undefined;\n return normalizeTenancyPosture(tenancy.posture) ?? (tenancy.isolationActive ? 'isolated' : 'single');\n}\n\n/**\n * Verify an inbound API key against `sys_api_key` and resolve its principal.\n * This is the ONE verify path shared by the dispatcher/MCP and REST surfaces.\n *\n * Fail-closed: returns `undefined` for a missing key, an unusable data engine,\n * a lookup error, or a key that is unknown / revoked / expired / owner-less.\n *\n * @param ql A data engine with `find(object, { where, limit, context })`.\n * @param headers Request headers (Web `Headers` or a plain object).\n * @param nowMs Clock for expiry checks (injectable for tests).\n */\nexport async function resolveApiKeyPrincipal(\n ql: any,\n headers: any,\n nowMs: number = Date.now(),\n tenancyPosture?: TenancyPosture,\n): Promise<ApiKeyPrincipal | undefined> {\n const admission = await resolveApiKeyAdmission(ql, headers, nowMs, tenancyPosture);\n return admission.outcome === 'admitted' ? admission.principal : undefined;\n}\n\n/**\n * [#8287] The full verdict behind {@link resolveApiKeyPrincipal} — same lookup,\n * but it distinguishes a POSTURE REFUSAL from \"no principal\".\n *\n * `resolveApiKeyPrincipal` collapses `refused` into `undefined` so every\n * existing caller keeps working and keeps failing closed; a caller that can\n * report WHY (the shared `resolveAuthzContext`) uses this instead.\n *\n * The only refusal decided here is the org-less one, because it needs nothing\n * but the row and the posture. The ex-member refusal needs the caller's\n * membership set and is decided in `resolveAuthzContext`, where that set is\n * already resolved.\n */\nexport async function resolveApiKeyAdmission(\n ql: any,\n headers: any,\n nowMs: number = Date.now(),\n tenancyPosture?: TenancyPosture,\n): Promise<ApiKeyAdmission> {\n const apiKey = extractApiKey(headers);\n if (!apiKey) return { outcome: 'none' };\n if (!ql || typeof ql.find !== 'function') return { outcome: 'none' };\n\n // Match by the indexed at-rest hash only — never query by the raw key.\n let rows: any;\n try {\n rows = await ql.find('sys_api_key', {\n where: { key: hashApiKey(apiKey), revoked: false },\n limit: 1,\n context: { isSystem: true },\n });\n } catch {\n return { outcome: 'none' };\n }\n if (rows && (rows as any).value) rows = (rows as any).value;\n const row = Array.isArray(rows) ? rows[0] : undefined;\n if (!row || row.revoked === true) return { outcome: 'none' };\n\n const expiresAt = row.expires_at ?? row.expiresAt;\n if (isExpired(expiresAt, nowMs)) return { outcome: 'none' };\n\n const userId = row.user_id ?? row.userId;\n if (!userId || typeof userId !== 'string') return { outcome: 'none' };\n\n // [#8287 / PD #12] ONE spelling. The producer (`runtime` `/keys`) writes\n // `active_organization_id` and nothing else, so this reads that column and\n // nothing else. The `row.organization_id ?? row.organizationId` chain that\n // stood here was a consumer-side tolerance for a producer that did not exist\n // yet — it read a column no mint path ever wrote, which is why the ruling's\n // \"key auth establishes that organization\" clause was already implemented\n // and still measured as inert.\n const tenantId = typeof row.active_organization_id === 'string' && row.active_organization_id\n ? row.active_organization_id\n : undefined;\n\n // [#8287] Posture-conditional refusal for a key that carries no organization.\n //\n // ⛔ Never backfilled — inferring the org from the owner's CURRENT membership\n // would silently upgrade a credential minted under a different promise.\n // Refusal is scoped to the one posture where such a key is provably dead:\n //\n // - `single` — no wall exists; an org-less key works, and always did.\n // - `group` — the wall is `organization_id IN accessible_org_ids`, and\n // that set derives from the owner's `sys_member` rows\n // INDEPENDENTLY of the active organization, so an org-less\n // key already reads the union of its owner's orgs. Refusing\n // would break working deployments for no security gain.\n // - `isolated` — the wall is `organization_id = activeOrganizationId`; with\n // no active organization NOTHING can match, which is exactly\n // the `200 + total 0` this card was filed for. Refuse, so the\n // failure is loud at call time instead of silently empty.\n //\n // ⚠️ An ABSENT posture means \"the caller could not tell us which posture is in\n // force\", and the answer to that is to admit — i.e. today's behaviour. Not\n // fail-closed, deliberately, and this is the one place in this module where\n // that is the right call: refusing on an unknown posture would break every\n // org-less key on every `single` deployment whose transport has not been\n // wired, to enforce a wall that may not exist. Fail-closed belongs on\n // questions about THIS credential; this is a question about the deployment.\n if (!tenantId && tenancyPosture) {\n const posture = tenancyPosture;\n if (postureEnforcesWall(posture) && !postureUsesUnionScope(posture)) {\n return {\n outcome: 'refused',\n reason: 'organization_required',\n message:\n 'This API key carries no organization and cannot be used under the `isolated` tenancy '\n + 'posture, where every organization-scoped read is walled to an active organization. '\n + 'Mint a replacement key — new keys inherit the minter’s active organization.',\n };\n }\n }\n\n return {\n outcome: 'admitted',\n principal: { userId, tenantId, scopes: parseScopes(row.scopes) },\n };\n}\n\nfunction readHeader(headers: any, name: string): string | undefined {\n if (!headers) return undefined;\n const lower = name.toLowerCase();\n if (typeof headers.get === 'function') {\n const v = headers.get(name) ?? headers.get(lower);\n return v == null ? undefined : String(v);\n }\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lower) {\n const v = headers[key];\n return Array.isArray(v) ? v[0] : v == null ? undefined : String(v);\n }\n }\n return undefined;\n}\n\nfunction safeJsonParse<T>(s: string, fallback: T): T {\n try {\n return JSON.parse(s) as T;\n } catch {\n return fallback;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * resolveAuthzContext — the SINGLE source of truth for resolving an inbound\n * request's identity + authorization context (positions, permissions, RLS scoping).\n *\n * Every HTTP entry point (REST server, runtime dispatcher, MCP, any future\n * transport) MUST resolve authorization through this function — never by\n * re-reading `sys_member` / `sys_user_position` / `sys_*_permission_set` itself.\n *\n * Why this exists: authorization resolution used to be DUPLICATED across the\n * REST server (`@objectstack/rest`) and the runtime dispatcher\n * (`@objectstack/runtime`). On a security path, duplicated logic drifts and the\n * drift is silent: the REST copy had quietly omitted `sys_user_position` (so custom\n * roles granted via the ADR-0057 D4 platform-RBAC path didn't apply over REST),\n * `sys_position_permission_set`, `mapMembershipRole` normalization, the\n * platform-admin derivation, and the `ai_seat` synthesis. The API-key half was\n * already shared here (`resolveApiKeyPrincipal`); this completes the extraction\n * by bringing session + role/permission aggregation home too. There is now ONE\n * implementation; both entry points are thin adapters that supply `ql` /\n * `getSession` their own way and delegate here.\n *\n * Fail-closed: every read is defensive. Missing services / tables yield a\n * partial context (even `{ positions: [], permissions: [] }`) — enforcement is the\n * SecurityPlugin's job, never this resolver's.\n */\n\nimport {\n mapMembershipRole,\n BUILTIN_IDENTITY_PLATFORM_ADMIN,\n ADMIN_FULL_ACCESS,\n ORGANIZATION_ADMIN_GRANTS,\n} from '@objectstack/spec';\nimport type { AuthzPosture, TenancyPosture } from '@objectstack/spec/security';\nimport { postureEnforcesWall } from '@objectstack/spec/security';\n\nimport { resolveApiKeyAdmission } from './api-key.js';\nimport type { ApiKeyRefusalReason } from './api-key.js';\nimport { isGrantActive } from './grant-validity.js';\nimport { derivePosture } from './posture-ladder.js';\nimport { isRowActive } from './row-active.js';\n\n/** The transport-agnostic authorization envelope produced from a request. */\nexport interface ResolvedAuthzContext {\n userId?: string;\n tenantId?: string;\n email?: string;\n accessToken?: string;\n positions: string[];\n permissions: string[];\n systemPermissions: string[];\n tabPermissions?: Record<string, 'visible' | 'hidden' | 'default_on' | 'default_off'>;\n /** Fellow-org user IDs for RLS scoping of identity tables (`id IN (...)`). */\n org_user_ids: string[];\n /**\n * [ADR-0105 D2] Every organization this principal currently holds a VALID\n * membership in — the caller's org access set, and the read reach of the\n * `group` tenancy posture (Layer 0 becomes `organization_id IN (...)`).\n * Resolved here, once, so no surface re-derives it; empty for an anonymous or\n * membership-less principal, which fails the group wall closed.\n */\n accessible_org_ids: string[];\n /**\n * [ADR-0095 D2/D3] The monotonic posture rung this principal resolves to,\n * DERIVED once here from held capability grants (never a better-auth role):\n * `PLATFORM_ADMIN` (unscoped `admin_full_access`) > `TENANT_ADMIN`\n * (`organization_admin`) > `MEMBER` (the authenticated floor). `EXTERNAL` is\n * defined/test-locked but never resolved yet (no external principal type —\n * see `posture-ladder.ts`). Present only for an authenticated principal;\n * anonymous requests carry no rung.\n */\n posture?: AuthzPosture;\n /**\n * [#8287] Set when an inbound API key was REFUSED — a real, intact\n * credential this deployment's tenancy posture cannot admit. The context is\n * otherwise EMPTY (no `userId`), so every transport already fails it closed\n * to 401 with no change; this field only lets a transport that wants to say\n * WHY do so, instead of answering the operator with a bare \"unauthenticated\"\n * for a key they can see is neither revoked nor expired.\n *\n * ⚠️ `reason` is NOT an `error.code`. The wire vocabulary is closed\n * (ADR-0112: `StandardErrorCode ∪ ERROR_CODE_LEDGER`, both in `packages/spec`)\n * and a refused credential's standard member is `UNAUTHENTICATED`. This is a\n * diagnostic discriminator for the message, deliberately lowercase so it can\n * never be mistaken for one.\n */\n authRefusal?: { reason: ApiKeyRefusalReason; message: string };\n}\n\nexport interface ResolveAuthzInput {\n /** Data engine (ObjectQL) exposing `find(object, { where, limit, context })`. */\n ql: any;\n /** Inbound request headers (Web `Headers` or a plain record). */\n headers: any;\n /**\n * Resolve a better-auth session from `headers`, returning `{ user?, session? }`\n * (or undefined). Optional — when omitted or throwing, only the API-key path\n * runs and anonymous requests resolve to an empty context.\n */\n getSession?: (headers: any) => Promise<any> | any;\n /** Clock injection for API-key expiry (tests). */\n nowMs?: number;\n /**\n * [#8287] The deployment's EFFECTIVE tenancy posture, as resolved from the\n * kernel's `tenancy` service (`effectiveTenancyPosture`) — never from\n * `OS_TENANCY_POSTURE`, which reports what was requested rather than what is\n * enforced (ADR-0093 D4/D5).\n *\n * Supplied by the transport because this resolver is deliberately\n * kernel-agnostic. OMITTING it disables the two posture-conditional API-key\n * refusals and leaves behaviour exactly as it was — so an unwired caller is\n * never made WORSE, only less strict.\n */\n tenancyPosture?: TenancyPosture;\n}\n\nfunction safeJsonParse<T>(s: string, fallback: T): T {\n try { return JSON.parse(s) as T; } catch { return fallback; }\n}\n\nasync function tryFind(\n ql: any,\n object: string,\n where: any,\n limit = 100,\n /**\n * Resolve inside ONE organization. Threaded into the execution context, so\n * the read routes through `SqlDriver.applyTenantScope` — the governed\n * chokepoint — rather than being re-implemented here as a bare equality.\n * Omitted keeps the pre-existing installation-wide read, which is what the\n * user-keyed reads above want (they are already narrowed by `user_id`) and\n * what a `single`-posture deployment wants everywhere.\n */\n organizationId?: string,\n): Promise<any[]> {\n if (!ql || typeof ql.find !== 'function') return [];\n try {\n const context = organizationId ? { isSystem: true, tenantId: organizationId } : { isSystem: true };\n let rows = await ql.find(object, { where, limit, context } as any);\n if (rows && (rows as any).value) rows = (rows as any).value;\n return Array.isArray(rows) ? rows : [];\n } catch {\n return [];\n }\n}\n\n/**\n * Resolve the authorization context for an inbound request. Always resolves —\n * never throws. Anonymous requests yield `{ positions: [], permissions: [], ... }`.\n */\nexport async function resolveAuthzContext(input: ResolveAuthzInput): Promise<ResolvedAuthzContext> {\n const { ql, headers } = input;\n const ctx: ResolvedAuthzContext = {\n positions: [],\n permissions: [],\n systemPermissions: [],\n org_user_ids: [],\n accessible_org_ids: [],\n };\n\n let userId: string | undefined;\n let tenantId: string | undefined;\n\n // 1. API key (explicit opt-in via header) takes precedence over session.\n const admission = await resolveApiKeyAdmission(ql, headers, input.nowMs, input.tenancyPosture);\n // [#8287] A REFUSED key stops here and does NOT fall through to the session\n // path. Falling through would be more permissive than the behaviour this\n // replaced (an API key already outranks a session), and a refusal that\n // quietly becomes a session login is not a refusal.\n if (admission.outcome === 'refused') {\n ctx.authRefusal = { reason: admission.reason, message: admission.message };\n return ctx;\n }\n const keyPrincipal = admission.outcome === 'admitted' ? admission.principal : undefined;\n if (keyPrincipal) {\n userId = keyPrincipal.userId;\n tenantId = keyPrincipal.tenantId;\n for (const scope of keyPrincipal.scopes) {\n if (!ctx.permissions.includes(scope)) ctx.permissions.push(scope);\n }\n }\n\n // 2. Session / Bearer path — fall back when no API key resolved a user.\n if (!userId && typeof input.getSession === 'function') {\n try {\n const sessionData = await input.getSession(headers);\n userId = sessionData?.user?.id ?? sessionData?.session?.userId;\n tenantId = tenantId ?? sessionData?.session?.activeOrganizationId;\n ctx.accessToken = sessionData?.session?.token ?? ctx.accessToken;\n if (sessionData?.user?.email) ctx.email = String(sessionData.user.email);\n } catch {\n // no auth configured / bad session → anonymous\n }\n }\n\n if (!userId) return ctx;\n ctx.userId = userId;\n if (tenantId) ctx.tenantId = tenantId;\n if (!ql || typeof ql.find !== 'function') return ctx;\n\n // The principal is now known — delegate ALL position/permission/RLS\n // aggregation to the shared userId-driven resolver. Seed it with the API-key\n // scopes already collected (step 1) and any session-supplied email so the\n // resulting order + email fallback are byte-identical to the logic this\n // replaced. `resolveUserAuthzGrants` is the single place that reads\n // `sys_member` / `sys_user_position` / `sys_*_permission_set`, so a non-HTTP\n // surface that already knows the user id (a `runAs:'user'` automation run,\n // #3356) can build the SAME envelope without re-implementing any of it.\n const grants = await resolveUserAuthzGrants(ql, userId, {\n tenantId,\n nowMs: input.nowMs,\n seedPermissions: ctx.permissions,\n seedEmail: ctx.email,\n });\n // [#8287] An API key stamped with an organization is only as good as its\n // owner's CURRENT membership in that organization. Checked here, at VERIFY\n // time, rather than by revoking keys when a membership ends: membership can\n // end through better-auth's org endpoints, SCIM deprovisioning, a direct\n // `sys_member` delete, or an ADR-0091 validity window simply lapsing — a\n // revoke-on-removal hook has to catch EVERY one of those or it silently\n // misses, while a verify-time check cannot be bypassed by an unhooked path.\n //\n // It is also free: `resolveUserAuthzGrants` has just read `sys_member` for\n // this very user to build `accessible_org_ids`, so this is a set membership\n // test on data already in hand — zero additional queries.\n //\n // Scoped to the walled postures on purpose. Under `single` there is no\n // organization boundary to cross, and a deployment with no membership rows\n // at all would otherwise refuse every stamped key it has.\n //\n // The result is NO PRINCIPAL, not a degrade to a user-only principal.\n // Degrading would hand back exactly the `200 + total 0` silent-empty this\n // card exists to kill — an ex-member's automation would keep answering\n // success while reading nothing.\n if (keyPrincipal?.tenantId && input.tenancyPosture) {\n const posture = input.tenancyPosture;\n if (postureEnforcesWall(posture) && !grants.accessible_org_ids.includes(keyPrincipal.tenantId)) {\n return {\n positions: [],\n permissions: [],\n systemPermissions: [],\n org_user_ids: [],\n accessible_org_ids: [],\n authRefusal: {\n reason: 'organization_membership_ended',\n message:\n 'This API key authenticates into an organization its owner is no longer a member of. '\n + 'The key was not revoked — the membership that backed it ended.',\n },\n };\n }\n }\n\n ctx.positions = grants.positions;\n ctx.permissions = grants.permissions;\n ctx.systemPermissions = grants.systemPermissions;\n ctx.org_user_ids = grants.org_user_ids;\n ctx.accessible_org_ids = grants.accessible_org_ids;\n if (grants.tabPermissions) ctx.tabPermissions = grants.tabPermissions;\n if (grants.posture) ctx.posture = grants.posture;\n if (grants.email && !ctx.email) ctx.email = grants.email;\n\n return ctx;\n}\n\n/** The authorization grants a KNOWN user holds — a subset of {@link ResolvedAuthzContext}. */\nexport interface UserAuthzGrants {\n positions: string[];\n permissions: string[];\n systemPermissions: string[];\n /** Fellow-org user IDs for RLS scoping of identity tables (`id IN (...)`). */\n org_user_ids: string[];\n /** [ADR-0105 D2] Organizations this user holds a currently-valid membership in. */\n accessible_org_ids: string[];\n tabPermissions?: Record<string, 'visible' | 'hidden' | 'default_on' | 'default_off'>;\n posture?: AuthzPosture;\n /** The user's unique email (`sys_user`), for `current_user.email` owner RLS. */\n email?: string;\n}\n\nexport interface ResolveUserAuthzGrantsOptions {\n /** Active org/tenant id — scopes org-bound grants (a null-org row is global). */\n tenantId?: string;\n /** Clock injection for grant validity windows (tests). */\n nowMs?: number;\n /**\n * Permission names the CALLER already resolved (e.g. API-key scopes) to seed\n * `permissions` BEFORE permission-set names are appended, so a mixed\n * API-key+session principal keeps every scope and the ordering is preserved.\n * Copied, never mutated.\n */\n seedPermissions?: string[];\n /** A caller-supplied email (e.g. from the session) that wins over the `sys_user` read. */\n seedEmail?: string;\n}\n\n/**\n * resolveUserAuthzGrants — the userId-driven core of {@link resolveAuthzContext}.\n *\n * Given a KNOWN user id, aggregate the authorization grants that user holds:\n * org-admin positions (`sys_member`), platform-RBAC positions\n * (`sys_user_position`), user- and position-bound permission sets\n * (`sys_user_permission_set` / `sys_position_permission_set` →\n * `sys_permission_set`), the derived `platform_admin` built-in + posture rung,\n * fellow-org peers for identity-table RLS, and the env-side `ai_seat`.\n *\n * Factored out of `resolveAuthzContext` so a surface that already knows WHO the\n * principal is — with no HTTP request to resolve it from — can build the SAME\n * envelope through the ONE resolver, instead of re-reading `sys_member` /\n * `sys_user_position` / `sys_*_permission_set` itself. The motivating consumer\n * is a `runAs:'user'` automation run resolving the triggering user's grants\n * (#3356): the record-change hook session carries only a `userId`, so the\n * automation engine calls this to run the flow's data ops exactly as that user\n * — not the bare member/everyone fallback the missing grants used to leave it.\n *\n * Fail-closed like its parent: every read is defensive, a missing engine/table\n * yields an empty-but-valid envelope, and it never throws.\n */\nexport async function resolveUserAuthzGrants(\n ql: any,\n userId: string,\n opts: ResolveUserAuthzGrantsOptions = {},\n): Promise<UserAuthzGrants> {\n const { tenantId } = opts;\n const grants: UserAuthzGrants = {\n positions: [],\n permissions: Array.isArray(opts.seedPermissions) ? [...opts.seedPermissions] : [],\n systemPermissions: [],\n org_user_ids: [userId],\n accessible_org_ids: [],\n };\n if (opts.seedEmail) grants.email = opts.seedEmail;\n if (!ql || typeof ql.find !== 'function') return grants;\n\n // sys_user is needed for both the `current_user.email` fallback (API-key auth,\n // where the session didn't supply an email) and the ai_seat synthesis below.\n // Read the row at most once per resolution — the two reads were a duplicate\n // query on the API-key path.\n let userRowLoaded = false;\n let userRow: any;\n const getUserRow = async (): Promise<any> => {\n if (!userRowLoaded) {\n userRowLoaded = true;\n const rows = await tryFind(ql, 'sys_user', { id: userId }, 1);\n userRow = rows[0];\n }\n return userRow;\n };\n\n // ── [#10825] Leg 1: every read that depends only on (userId, tenantId) is\n // issued CONCURRENTLY — sys_user, both sys_member reads, sys_user_position\n // and sys_user_permission_set used to run as five sequential round trips\n // (legs 6–9 + 13 of an authenticated request; cloud#1539 measured that LEGS,\n // not queries, are the latency multiplier). Same queries, same filters, same\n // limits, same tenancy scoping — only the waiting overlaps, so the rows each\n // downstream block sees are byte-identical to the sequential version\n // (equivalence pinned in resolve-authz-context.batch-equivalence.test.ts).\n // `getUserRow` stays memoized; issuing it here is not a new read on any\n // path that reads it at all — the email fallback below and §7's ai_seat\n // synthesis consume the same memo. The CONDITION mirrors the sequential\n // implementation exactly: `sys_user` was read iff the caller seeded no\n // email OR did not seed `ai_seat` — a fully-seeded API-key principal never\n // touched the table, and the batch must not start (equivalence suite pins\n // the query multiset per fixture).\n const needsUserRow = !grants.email || !grants.permissions.includes('ai_seat');\n const [, members, userPositionRows, orgMembersLeg, upsRowsAll] = await Promise.all([\n needsUserRow ? getUserRow() : Promise.resolve(undefined),\n tryFind(ql, 'sys_member', { user_id: userId }, 200),\n tryFind(ql, 'sys_user_position', { user_id: userId }, 200),\n tenantId ? tryFind(ql, 'sys_member', { organization_id: tenantId }, 1000) : Promise.resolve([] as any[]),\n tryFind(ql, 'sys_user_permission_set', { user_id: userId }, 100),\n ]);\n\n // Resolve the caller's unique email for `current_user.email` RLS owner\n // policies when the caller didn't supply it (e.g. API-key auth).\n if (!grants.email) {\n const u = await getUserRow();\n if (u?.email) grants.email = String(u.email);\n }\n\n // Single clock for every validity-window check in this resolution\n // (ADR-0091 D2 — a grant row outside [valid_from, valid_until) does not\n // resolve, fail-closed, with no background job involved).\n const nowMs = opts.nowMs ?? Date.now();\n\n // 3. Memberships via sys_member (better-auth). ONE read serves two purposes,\n // so the two facts can never disagree about what the user belongs to:\n //\n // (a) [ADR-0095 D3] Org-administration roles for the ACTIVE organization,\n // normalized to the canonical built-in names (owner→org_owner,\n // admin→org_admin, …). This is the ONE PROVISIONING boundary where a\n // better-auth role is read: it is projected into `positions` here, and\n // separately drives the `organization_admin` capability grant\n // (auto-org-admin-grant.ts). No enforcement code path reads the raw\n // role — posture/adjudication run off the resulting capability grants,\n // so the #2836 dual-track cannot recur.\n //\n // (b) [ADR-0105 D2] `accessible_org_ids` — EVERY organization the user\n // currently belongs to, regardless of which one is active. This is the\n // `group` posture's read reach (Layer 0 becomes `organization_id IN\n // (...)`), so it must span the whole membership set, not the active\n // org. Rows outside their ADR-0091 validity window do not resolve; the\n // columns are absent on `sys_member` today, and `isGrantActive` treats\n // an absent bound as unbounded, so this is a no-op until they exist and\n // correct the moment they do.\n // (read in Leg 1 above)\n const accessibleOrgIds = new Set<string>();\n for (const m of members) {\n if (!isGrantActive(m, nowMs)) continue;\n const org = m.organization_id ?? m.organizationId;\n if (typeof org === 'string' && org) accessibleOrgIds.add(org);\n }\n grants.accessible_org_ids = Array.from(accessibleOrgIds);\n\n // Positions come from the ACTIVE org's membership only: a role held in one\n // organization must not grant its capabilities while the caller operates in\n // another. With no active org, every membership contributes — exactly the\n // pre-D2 behavior of the org-less read.\n //\n // [ADR-0091 D2] Rows outside their validity window are dropped BEFORE the\n // role derivation — the same discipline §6 gives `sys_user_permission_set`,\n // so a lapsed membership can no more yield `org_owner` than an expired\n // `admin_full_access` can yield `platform_admin`. Maintainer ruling\n // 2026-08-22 (live session, item 2): a lapsed membership is NO MEMBERSHIP,\n // not merely no org access — so this half now answers the same question as\n // `accessible_org_ids` above, off the same rows, and (a)'s \"correct the\n // moment they do\" promise covers BOTH derivations rather than one. Fail\n // closed (D2). `sys_member` declares neither bound today and `isGrantActive`\n // reads an absent bound as unbounded, so no shipped row changes answer.\n const activeMembers = members.filter(\n (m) =>\n isGrantActive(m, nowMs)\n && (!tenantId || (m.organization_id ?? m.organizationId) === tenantId),\n );\n for (const m of activeMembers) {\n if (m.role && typeof m.role === 'string') {\n for (const raw of m.role.split(',').map((s: string) => s.trim()).filter(Boolean)) {\n const r = mapMembershipRole(raw);\n if (!grants.positions.includes(r)) grants.positions.push(r);\n }\n }\n }\n\n // 4. [ADR-0057 D4] Platform-owned RBAC role assignments (sys_user_position) — the\n // source of truth for custom roles, decoupled from sys_member.role.\n // `organization_id = null` = global (cross-tenant); else match active org.\n // (read in Leg 1 above)\n for (const ur of userPositionRows) {\n const org = ur.organization_id ?? null;\n if (org && tenantId && org !== tenantId) continue;\n if (!isGrantActive(ur, nowMs)) continue;\n const r = ur.position;\n if (typeof r === 'string' && r && !grants.positions.includes(r)) grants.positions.push(r);\n }\n\n // 5. Fellow-org user IDs so RLS can scope identity tables to collaborators.\n if (tenantId) {\n const orgMembers = orgMembersLeg; // (read in Leg 1 above)\n const ids = new Set<string>(\n orgMembers\n .map((m) => m.user_id ?? m.userId)\n .filter((v): v is string => typeof v === 'string' && v.length > 0),\n );\n ids.add(userId);\n grants.org_user_ids = Array.from(ids);\n }\n\n // 6. Permission sets — user-scoped grants (null org = global, else active org).\n // Rows outside their validity window are dropped BEFORE any derivation, so\n // an expired admin_full_access grant cannot yield platform_admin either.\n // (read in Leg 1 above)\n const upsRows = upsRowsAll.filter((r) => isGrantActive(r, nowMs));\n const psIds = new Set<string>(\n upsRows\n .filter((r) => {\n const org = (r.organization_id ?? r.organizationId) ?? null;\n return !(org && tenantId && org !== tenantId);\n })\n .map((r) => r.permission_set_id ?? r.permissionSetId)\n .filter(Boolean),\n );\n // platform_admin (ADR-0068 D2) is DERIVED from an UNSCOPED admin_full_access\n // USER grant — the single source of truth (no trusted stored boolean).\n const unscopedUserPsIds = new Set<string>(\n upsRows\n .filter((r) => ((r.organization_id ?? r.organizationId) ?? null) === null)\n .map((r) => r.permission_set_id ?? r.permissionSetId)\n .filter(Boolean),\n );\n let hasPlatformAdminGrant = false;\n\n // 5b. [ADR-0090 D5] Audience anchor: every AUTHENTICATED member implicitly\n // holds the built-in `everyone` position, so sets bound to it resolve\n // below exactly like any other position-bound grant — ADDITIVE, with no\n // \"only when the user has nothing else\" cliff.\n if (!grants.positions.includes('everyone')) grants.positions.push('everyone');\n\n // 6a. Position-bound permission sets (sys_position_permission_set): a position\n // carries its permission sets.\n //\n // [ADR-0049] A DEACTIVATED position grants nothing — the `deactivate_position`\n // dialog's promise (\"users keep their assignment but the position stops\n // granting permissions\"), enforced at the ONE place it is enforceable.\n // Downstream in plugin-security the position→set linkage is already\n // collapsed into a flat `permissions` list, so a set held via a\n // deactivated position is indistinguishable there from one granted\n // directly and filtering there would over-revoke.\n //\n // The name is dropped from `positions` too, not merely from the junction\n // read: `resolvePermissionSetsForContext` requests `positions` as\n // permission-set NAMES (position names are commonly reused as set names),\n // so a name left standing would resolve the same grant one layer down.\n // Only a name whose row is explicitly deactivated is dropped — a name\n // with no `sys_position` row at all (`org_owner`, a membership-derived\n // role) has no flag to read and is untouched.\n if (grants.positions.length > 0) {\n // [#10103] Scoped to the CALLER's organization. `sys_position` spells\n // its name index `unique: 'organization'` and its rows are materialized\n // per organization, so several organizations hold a row named\n // `everyone` (and one named after every declared position). Swept by\n // name alone, this read returned EVERY organization's rows, and the\n // junction read below then collected another organization's bindings —\n // a cross-organization grant bleed, measured reachable from one tenant's\n // resolution to another tenant's `everyone` binding. It also made the\n // sweep O(organizations) on a table that is read on every request.\n //\n // Scoped by threading the organization into the context rather than by\n // adding an `organization_id` predicate here: the driver's\n // `applyTenantScope` is the one governed spelling of this wall, and a\n // bare equality written at this call site would be a second, ungoverned\n // implementation of it — the exact shape that produced the defect this\n // card repairs. Per-request cost stays O(the caller's own organization's\n // catalog).\n //\n // Limit raised with it: the cap has to admit this organization's rows\n // alongside any organization-less ones the driver's compatibility arm\n // still returns, or a caller silently loses positions. Those\n // organization-less rows stay REACHABLE on purpose — they are not\n // reaped, and grants point at them by row id, so dropping them here\n // would revoke standing access silently.\n const positionRows = await tryFind(ql, 'sys_position', { name: { $in: grants.positions } }, 200, tenantId);\n const deactivatedNames = new Set<string>(\n positionRows.filter((r) => !isRowActive(r)).map((r) => r.name).filter(Boolean),\n );\n if (deactivatedNames.size > 0) {\n grants.positions = grants.positions.filter((n) => !deactivatedNames.has(n));\n }\n const positionIds = positionRows.filter((r) => isRowActive(r)).map((r) => r.id).filter(Boolean);\n if (positionIds.length > 0) {\n const rpsRows = await tryFind(ql, 'sys_position_permission_set', { position_id: { $in: positionIds } }, 500);\n for (const r of rpsRows) {\n const id = r.permission_set_id ?? r.permissionSetId;\n if (id) psIds.add(id);\n }\n }\n }\n\n // 6b. Resolve permission-set details (names → grants.permissions; system_permissions;\n // tab_permissions merged by highest visibility).\n if (psIds.size > 0) {\n const psRowsAll = await tryFind(ql, 'sys_permission_set', { id: { $in: Array.from(psIds) } }, 500);\n // [ADR-0049] A DEACTIVATED permission set grants nothing — the\n // `deactivate_permission_set` dialog's promise (\"existing assignments stay\n // in place but stop granting access\"). Dropped BEFORE any derivation, the\n // same discipline the validity window gets at §6, so `hasPlatformAdminGrant`\n // cannot be derived from a set that no longer grants either: a deactivated\n // `admin_full_access` must not keep conferring PLATFORM_ADMIN.\n const psRows = psRowsAll.filter((r) => isRowActive(r));\n const tabRank: Record<string, number> = { hidden: 0, default_off: 1, default_on: 2, visible: 3 };\n const mergedTabs: Record<string, 'visible' | 'hidden' | 'default_on' | 'default_off'> = {};\n for (const ps of psRows) {\n if (ps.name && !grants.permissions.includes(ps.name)) grants.permissions.push(ps.name);\n if (ps.name === ADMIN_FULL_ACCESS && unscopedUserPsIds.has(ps.id)) hasPlatformAdminGrant = true;\n const sysPerms = typeof ps.system_permissions === 'string'\n ? safeJsonParse(ps.system_permissions, [])\n : (ps.system_permissions ?? ps.systemPermissions);\n if (Array.isArray(sysPerms)) {\n for (const p of sysPerms) {\n if (typeof p === 'string' && !grants.systemPermissions.includes(p)) grants.systemPermissions.push(p);\n }\n }\n const tabs = typeof ps.tab_permissions === 'string'\n ? safeJsonParse(ps.tab_permissions, {})\n : (ps.tab_permissions ?? ps.tabPermissions);\n if (tabs && typeof tabs === 'object') {\n for (const [app, val] of Object.entries(tabs as Record<string, unknown>)) {\n if (typeof val !== 'string' || !(val in tabRank)) continue;\n const cur = mergedTabs[app];\n if (!cur || tabRank[val] > tabRank[cur]) {\n mergedTabs[app] = val as 'visible' | 'hidden' | 'default_on' | 'default_off';\n }\n }\n }\n }\n if (Object.keys(mergedTabs).length > 0) grants.tabPermissions = mergedTabs;\n }\n\n // 6c. Project the derived platform_admin built-in role (leads the list).\n if (hasPlatformAdminGrant && !grants.positions.includes(BUILTIN_IDENTITY_PLATFORM_ADMIN)) {\n grants.positions.unshift(BUILTIN_IDENTITY_PLATFORM_ADMIN);\n }\n\n // 6d. [ADR-0095 D2/D3] Resolve the posture rung ONCE, from held CAPABILITY\n // grants — never from a better-auth role. `PLATFORM_ADMIN` from the\n // unscoped `admin_full_access` grant (the same `viewAllRecords`/\n // `modifyAllRecords` evidence the superuser bypass trusts); `TENANT_ADMIN`\n // from the `organization_admin` grant (auto-provisioned from the better-\n // auth owner/admin role at §3 above — a provisioning source, not an\n // enforcement input, closing the #2836 dual-track class). Enforcement\n // behavior is unchanged: the per-object Layer 0 exemption + per-side\n // superuser bypass still gate access; posture is the carried, explainable\n // tier. `EXTERNAL` is never derived (no external principal type yet).\n grants.posture = derivePosture({\n isPlatformAdmin: hasPlatformAdminGrant,\n // [ADR-0105 D4] Either org-admin capability set resolves the rung — the\n // wall-less variant differs only by withholding the superuser bits.\n isTenantAdmin: ORGANIZATION_ADMIN_GRANTS.some((n: string) => grants.permissions.includes(n)),\n });\n\n // 7. [ADR-0024] Env-side AI seat: synthesize the `ai_seat` capability from the\n // boolean sys_user.ai_access (sqlite returns 1/0; memory returns boolean).\n if (!grants.permissions.includes('ai_seat')) {\n const aiAccess = ((await getUserRow()) as { ai_access?: unknown } | undefined)?.ai_access;\n if (aiAccess === true || aiAccess === 1 || aiAccess === '1') grants.permissions.push('ai_seat');\n }\n\n return grants;\n}\n\n// ── Localization (ADR-0053 Phase 2) ─────────────────────────────────────────\n\nfunction isValidTimeZone(tz: string): boolean {\n try { new Intl.DateTimeFormat('en-US', { timeZone: tz }); return true; } catch { return false; }\n}\nfunction coerceTimeZone(value: unknown): string | undefined {\n const s = typeof value === 'string' ? value.trim() : value != null ? String(value).trim() : '';\n return s && isValidTimeZone(s) ? s : undefined;\n}\nfunction coerceLocale(value: unknown): string | undefined {\n const s = typeof value === 'string' ? value.trim() : value != null ? String(value).trim() : '';\n return s || undefined;\n}\nfunction coerceCurrency(value: unknown): string | undefined {\n const s = typeof value === 'string' ? value.trim().toUpperCase() : '';\n return /^[A-Z]{3}$/.test(s) ? s : undefined;\n}\n\nexport interface ResolveLocalizationInput {\n ql: any;\n /** Settings service exposing `get(namespace, key, { tenantId, userId })`. */\n settings?: any;\n tenantId?: string;\n userId?: string;\n}\n\ntype LocalizationResult = { timezone: string; locale: string; currency?: string };\n\n/**\n * Process-local TTL cache for the FAILED-READ outcome of\n * {@link resolveLocalizationContext} only (#10221; narrowed after a patch\n * round — see below).\n *\n * `resolveAuthzContext`'s #2409 de-dup already collapsed the THREE per-key\n * reads a request used to issue into one batched `sys_setting` query; what it\n * did not address is the SAME query repeating on EVERY request. On a fresh\n * environment (`sys_setting` not yet migrated / never written) that one query\n * fails every time, and `packages/drivers/driver-sql`'s\n * `backendStatementFault` — deliberately generic; see its doc — logs a\n * `[sql-driver] DATABASE_ERROR` line for EVERY failed read, so the identical\n * \"no such table: sys_setting\" warning repeats once per request and buries\n * real errors in between.\n *\n * `tryFind`-style catches already guaranteed the FUNCTIONAL fallback (catch →\n * `[]` → built-in `UTC` / `en-US` defaults) — this cache only stops the\n * FAILING query (and therefore the log line) from re-running every request.\n *\n * ## Why only the failure, not every result (patch round, CI red)\n *\n * The first version of this cache memoized every outcome — including a\n * SUCCESSFUL read — for 30s, mirroring `packages/plugins/plugin-audit/src/audit-writers.ts`\n * (`resolveWriteLocale`)'s existing TTL cache of this same read. That is safe\n * for audit-writer's use: audit trail enrichment is explicitly best-effort,\n * so a stale locale in a log line for up to 30s costs nothing observable.\n * It is NOT safe for `@objectstack/rest`'s use of this same function: analytics\n * date-bucketing reads the org timezone on every query, and\n * `packages/qa/dogfood/test/analytics-timezone.dogfood.test.ts` (the golden\n * regression for #1982/#2018) writes a NEW org timezone via the real settings\n * route and asserts the VERY NEXT analytics read buckets under it — a 30s-old\n * cached value broke that test (CI: `expected undefined to be 3`, the bucket\n * stayed on the previous timezone). Analytics bucketing is declared, tested\n * behavior, not best-effort enrichment — it cannot tolerate the same\n * staleness plugin-audit's cache is allowed to.\n *\n * So this cache is narrowed to memoize ONLY the case the underlying read\n * itself THREW (a backend fault — e.g. \"no such table\" — not a legitimate\n * empty/no-rows-yet result, which is a normal, cheap, un-failing read and\n * stays uncached so a fresh write is visible on the next request same as\n * before). That fully addresses #10221 — the log spam only exists on an env\n * where the read is actively failing — while never caching a value a caller\n * could observe going stale.\n *\n * Keyed on the `ql` instance (one entry-set per environment engine, so two\n * environments/tenants sharing a process never see each other's cached\n * outcome) and then `tenantId|userId` beneath it, matching the audit writer's\n * key shape. A `ql` that isn't cacheable (missing/non-object) skips the cache\n * entirely — there is no query to dedupe in that case.\n */\nconst LOCALIZATION_FAILURE_CACHE_TTL_MS = 30_000;\nconst localizationFailureCache = new WeakMap<object, Map<string, { value: LocalizationResult; expiresAt: number }>>();\n\n/**\n * Resolve workspace localization defaults (reference `timezone` / `locale` /\n * `currency`). Canonical path is the `localization` SettingsManifest (cascade:\n * platform default → global → tenant); falls back to direct tenant-scoped\n * `sys_setting` rows, then the built-ins `UTC` / `en-US`. Never throws.\n *\n * A read that fails outright (backend fault — table missing, connection\n * refused, etc.) is memoized for {@link LOCALIZATION_FAILURE_CACHE_TTL_MS}\n * per `(ql, tenantId, userId)` so the failing query — and the driver's log\n * line for it — does not repeat every request (#10221). A successful read,\n * including a legitimate \"no settings configured yet\" empty result, is NEVER\n * cached: the next call always re-reads, so a settings write takes effect\n * immediately (see the cache doc above for why — the dogfood analytics\n * bucketing test pins this).\n */\nexport async function resolveLocalizationContext(input: ResolveLocalizationInput): Promise<LocalizationResult> {\n const { ql, tenantId, userId } = input;\n const cacheKey = `${tenantId ?? ''}|${userId ?? ''}`;\n if (ql && typeof ql === 'object') {\n const hit = localizationFailureCache.get(ql)?.get(cacheKey);\n if (hit && hit.expiresAt > Date.now()) return hit.value;\n }\n\n const { value, failed } = await resolveLocalizationContextUncached(input);\n if (failed && ql && typeof ql === 'object') {\n const bucket = localizationFailureCache.get(ql) ?? new Map<string, { value: LocalizationResult; expiresAt: number }>();\n bucket.set(cacheKey, { value, expiresAt: Date.now() + LOCALIZATION_FAILURE_CACHE_TTL_MS });\n localizationFailureCache.set(ql, bucket);\n }\n return value;\n}\n\nasync function resolveLocalizationContextUncached(\n input: ResolveLocalizationInput,\n): Promise<{ value: LocalizationResult; failed: boolean }> {\n const { ql, settings, tenantId, userId } = input;\n let failed = false;\n try {\n if (settings && typeof settings.get === 'function') {\n const sctx = { tenantId, userId } as any;\n // [#10826] ONE grouped namespace read instead of three: `getMany`\n // resolves all three keys over at most two `loadRows` calls (queries\n // 16–18 of 24 on the measured rig collapse to one). Same per-key\n // answers by the service's own equivalence contract. Feature-detected:\n // an older service without `getMany` keeps the three parallel `get`s\n // (still 1 leg — this is a query-count fix, per the card's calibration).\n // A thrown `getMany` lands in the same place a thrown `get` did —\n // `failed = true` and the direct `$in` fallback below, which reads the\n // exact same three keys.\n let tzRes: any; let localeRes: any; let currencyRes: any;\n if (typeof settings.getMany === 'function') {\n try {\n const many = await settings.getMany('localization', ['timezone', 'locale', 'currency'], sctx);\n tzRes = many.timezone;\n localeRes = many.locale;\n currencyRes = many.currency;\n } catch {\n failed = true;\n }\n } else {\n [tzRes, localeRes, currencyRes] = await Promise.all([\n settings.get('localization', 'timezone', sctx).catch(() => {\n failed = true;\n return undefined;\n }),\n settings.get('localization', 'locale', sctx).catch(() => {\n failed = true;\n return undefined;\n }),\n settings.get('localization', 'currency', sctx).catch(() => {\n failed = true;\n return undefined;\n }),\n ]);\n }\n const tz = coerceTimeZone(tzRes?.value);\n const locale = coerceLocale(localeRes?.value);\n const currency = coerceCurrency(currencyRes?.value);\n if (tz || locale || currency) {\n return { value: { timezone: tz ?? 'UTC', locale: locale ?? 'en-US', currency }, failed: false };\n }\n }\n } catch {\n // settings service unavailable → direct read\n failed = true;\n }\n // One read for all three keys instead of a query per key (`$in` on `key`).\n // Inlined (rather than the shared `tryFind`) so a genuine backend fault —\n // as opposed to a legitimate empty result — is visible to the caller above,\n // which is the signal the failure-only cache keys off. `ql` is already\n // typed `any` (its shape varies by caller — REST's engine, a test double,\n // …), so the options literal below needs no `as any` of its own (#4918\n // query-options-erasure guard: that cast is a distinct, counted erasure\n // site, not implied by an already-`any` receiver).\n let rows: any[] = [];\n if (ql && typeof ql.find === 'function') {\n try {\n let result = await ql.find('sys_setting', {\n where: { namespace: 'localization', key: { $in: ['timezone', 'locale', 'currency'] }, scope: 'tenant' },\n limit: 10,\n context: { isSystem: true },\n });\n if (result && (result as any).value) result = (result as any).value;\n rows = Array.isArray(result) ? result : [];\n } catch {\n failed = true;\n }\n }\n const valueOf = (k: string) => rows.find((r) => r.key === k)?.value;\n return {\n value: {\n timezone: coerceTimeZone(valueOf('timezone')) ?? 'UTC',\n locale: coerceLocale(valueOf('locale')) ?? 'en-US',\n currency: coerceCurrency(valueOf('currency')),\n },\n failed,\n };\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Grant validity windows (ADR-0091 D1/D2).\n *\n * `sys_user_position` and `sys_user_permission_set` rows carry optional\n * `valid_from` / `valid_until` columns. A row outside its window MUST NOT\n * resolve — anywhere, symmetrically: `resolveAuthzContext`, the explain\n * engine's `buildContextForUser`, plugin-sharing's `expandPositionUsers`,\n * and (transitively) the delegated-admin gate's held-scope resolution.\n *\n * Correctness lives HERE, at resolution time — never in a cleanup job\n * (ADR-0049: no unenforced security properties). The window is half-open\n * `[from, until)` in UTC: a grant is inactive before `valid_from` and\n * inactive AT and AFTER `valid_until`. Null/absent bounds mean unbounded,\n * so pre-ADR-0091 rows behave exactly as before.\n *\n * Fail-closed: a bound that is PRESENT but unparseable disables the grant\n * (unlike API-key `isExpired`, which tolerates garbage — an API key is a\n * single credential, a grant row is standing authority).\n */\n\n/**\n * Coerce a stored timestamp to epoch milliseconds.\n * Returns `undefined` for absent (null/undefined/'') values — \"no bound\" —\n * and `NaN` for present-but-unparseable values, which callers treat as\n * out-of-window (fail closed).\n */\nfunction toEpochMs(value: unknown): number | undefined {\n if (value == null || value === '') return undefined;\n if (typeof value === 'number') {\n // Heuristic: seconds vs milliseconds epoch (same rule as api-key.ts).\n return value < 1e12 ? value * 1000 : value;\n }\n if (value instanceof Date) return value.getTime();\n if (typeof value === 'string') return Date.parse(value);\n return Number.NaN;\n}\n\n/** The validity-window shape shared by both user-grant tables (ADR-0091 D1). */\nexport interface GrantValidityWindow {\n valid_from?: unknown;\n valid_until?: unknown;\n}\n\n/**\n * True when a grant row is inside its validity window at `nowMs`.\n * The single predicate every resolver uses (ADR-0091 D2):\n * `(valid_from is null or valid_from <= now) and (valid_until is null or valid_until > now)`.\n */\nexport function isGrantActive(row: GrantValidityWindow | null | undefined, nowMs: number): boolean {\n if (!row) return false;\n const from = toEpochMs((row as any).valid_from ?? (row as any).validFrom);\n // NaN comparisons are always false, so an unparseable bound fails closed.\n if (from !== undefined && !(nowMs >= from)) return false;\n const until = toEpochMs((row as any).valid_until ?? (row as any).validUntil);\n if (until !== undefined && !(nowMs < until)) return false;\n return true;\n}\n\n/**\n * True when a grant row carries a `valid_until` that has already passed —\n * i.e. it WAS active and expired (not merely not-yet-active). The explain\n * engine uses this to report the dedicated \"held until … — expired\"\n * contributor state (ADR-0091 D2).\n */\nexport function isGrantExpired(row: GrantValidityWindow | null | undefined, nowMs: number): boolean {\n if (!row) return false;\n const until = toEpochMs((row as any).valid_until ?? (row as any).validUntil);\n if (until === undefined) return false;\n return !(nowMs < until);\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * ── The monotonic posture ladder (ADR-0095 D2/D3) ───────────────────────────\n *\n * The principal-tiering enum resolved ONCE in `resolveAuthzContext`\n * (`PLATFORM_ADMIN > TENANT_ADMIN > MEMBER > EXTERNAL`). This module owns two\n * things and deliberately nothing more:\n *\n * 1. **Derivation (D3).** {@link derivePosture} maps held *capability grants*\n * — never a better-auth role — to a rung. `PLATFORM_ADMIN` derives from the\n * unscoped `admin_full_access` grant (the `viewAllRecords`/`modifyAllRecords`\n * evidence the superuser bypass already trusts); `TENANT_ADMIN` from the\n * `organization_admin` grant. The better-auth `role='admin'` is upstream a\n * *provisioning source* of those grants (`auto-org-admin-grant.ts`), so it\n * never re-enters adjudication here — the #2836 dual-track class is closed\n * by construction.\n *\n * 2. **The rung → injection-rule mapping + its tested invariants (D2).** Each\n * rung maps to EXACTLY ONE row-visibility injection rule\n * ({@link POSTURE_INJECTION_RULE}). {@link postureVisibleRows} is the\n * REFERENCE MODEL of those rules over a synthetic row-set — it locks the two\n * properties the ADR requires as invariants: strict nesting (rung n's\n * visible set ⊇ rung n−1's) and the EXTERNAL deny-by-default semantics\n * (explicit shares only, OWD never widens it).\n *\n * This module is NOT the enforcement path. The effective read/write filter is\n * `Layer0(tenant) AND Layer1(business RLS)`, computed in `@objectstack/plugin-\n * security` (`tenant-layer.ts` + `security-plugin.ts`), and the real behavior\n * guard is the `authz-matrix-gate` unit snapshot + the dogfood conformance\n * matrix. The reference model here exists so the ladder's *mathematical*\n * properties can be asserted at the unit layer without an enforcement boot, and\n * so the EXTERNAL rung — which has no enforcement path yet — cannot be\n * reinvented differently when portal/external membership arrives.\n */\n\nimport type { AuthzPosture } from '@objectstack/spec/security';\n\n/**\n * The rung ordering, high privilege → low, matching the spec enum's numeric\n * values (`PLATFORM_ADMIN=3 … EXTERNAL=0`). Visibility grows monotonically UP\n * this ladder (see {@link postureVisibleRows}).\n */\nexport const POSTURE_LADDER = [\n 'PLATFORM_ADMIN',\n 'TENANT_ADMIN',\n 'MEMBER',\n 'EXTERNAL',\n] as const satisfies readonly AuthzPosture[];\n\n/** Numeric rank per rung (mirrors the spec `AuthzPosture` enum values). */\nexport const POSTURE_RANK: Record<AuthzPosture, number> = {\n PLATFORM_ADMIN: 3,\n TENANT_ADMIN: 2,\n MEMBER: 1,\n EXTERNAL: 0,\n};\n\n/**\n * The ONE row-visibility injection rule each rung maps to (ADR-0095 D2). Prose,\n * because the machine artifacts live in enforcement (Layer 0 + the per-rung\n * Layer 1 rule); this is the enumerable contract the explain track reports and\n * {@link postureVisibleRows} models.\n */\nexport const POSTURE_INJECTION_RULE: Record<AuthzPosture, string> = {\n PLATFORM_ADMIN:\n 'Layer 0 exemption where the object posture permits (private / platform-global / better-auth-managed) — crosses the tenant wall; org-scoped like TENANT_ADMIN on ordinary tenant business objects.',\n TENANT_ADMIN:\n 'All rows within the active organization (organization_id == ctx.tenantId); no ownership / depth / sharing narrowing.',\n MEMBER:\n 'Business RLS within the organization — ownership (owner / unit depth), the OWD baseline, and explicit sharing.',\n EXTERNAL:\n 'Explicitly shared rows ONLY — OWD baselines and sharing rules never apply; a misconfiguration can only shrink visibility, never widen it.',\n};\n\n/** Capability-grant evidence the posture derivation consumes (ADR-0095 D3). */\nexport interface PostureEvidence {\n /**\n * Holds the UNSCOPED platform-admin capability grant (`admin_full_access` →\n * `viewAllRecords`/`modifyAllRecords`) — the same evidence the superuser\n * bypass trusts. NOT a better-auth role.\n */\n isPlatformAdmin: boolean;\n /**\n * Holds the org-admin capability grant (`organization_admin`, tenant-scoped\n * `viewAllRecords`/`modifyAllRecords`). Provisioned from the better-auth\n * owner/admin role upstream, consumed here only as a held capability.\n */\n isTenantAdmin: boolean;\n}\n\n/**\n * Resolve the principal's posture rung from held capability grants (ADR-0095 D3).\n *\n * Returns `PLATFORM_ADMIN` | `TENANT_ADMIN` | `MEMBER`. It NEVER returns\n * `EXTERNAL`: no external principal type exists yet (the sharing chain has no\n * portal/guest-share concept — ADR-0095 W4). The `EXTERNAL` rung, its injection\n * rule, and its semantics are defined and test-locked ({@link postureVisibleRows},\n * {@link POSTURE_INJECTION_RULE}) so that when portal/external membership lands\n * (ADR-0093) the derivation gains an EXTERNAL branch HERE without the rung being\n * reinvented. `MEMBER` is the authenticated-principal floor.\n */\nexport function derivePosture(evidence: PostureEvidence): AuthzPosture {\n if (evidence.isPlatformAdmin) return 'PLATFORM_ADMIN';\n if (evidence.isTenantAdmin) return 'TENANT_ADMIN';\n return 'MEMBER';\n}\n\n// ── Reference visibility model (invariant lock, NOT enforcement) ─────────────\n\n/** A synthetic record for the ladder reference model. */\nexport interface LadderRow {\n id: string;\n /** The row's tenant. `undefined` = a non-tenant (platform-global) row. */\n organization_id?: string;\n /** The row's owner (drives the MEMBER ownership disjunct). */\n owner_id?: string;\n /**\n * Whether an OWD-derived source would admit this row for a member (public\n * baseline / criteria sharing). EXTERNAL deliberately ignores this field.\n */\n owdVisible?: boolean;\n /** User ids this row is EXPLICITLY shared to (the only EXTERNAL source). */\n sharedTo?: readonly string[];\n}\n\n/** The principal the reference model evaluates a rung for. */\nexport interface LadderPrincipal {\n userId: string;\n /** The principal's active organization (undefined for an unscoped principal). */\n organizationId?: string;\n}\n\nfunction isSharedTo(row: LadderRow, userId: string): boolean {\n return (row.sharedTo ?? []).includes(userId);\n}\n\n/** EXTERNAL rung: explicitly shared rows ONLY — never OWD, never org-wide. */\nfunction externalVisible(rows: readonly LadderRow[], p: LadderPrincipal): LadderRow[] {\n return rows.filter((r) => isSharedTo(r, p.userId));\n}\n\n/**\n * MEMBER rung: business RLS within the org — ownership OR OWD baseline OR\n * explicit sharing. Composed as `EXTERNAL ∪ (in-org ownership/OWD)` so the\n * EXTERNAL ⊆ MEMBER leg of the nesting invariant holds by construction.\n */\nfunction memberVisible(rows: readonly LadderRow[], p: LadderPrincipal): LadderRow[] {\n const shared = new Set(externalVisible(rows, p));\n return rows.filter(\n (r) =>\n shared.has(r) ||\n (r.organization_id === p.organizationId && (r.owner_id === p.userId || r.owdVisible === true)),\n );\n}\n\n/**\n * TENANT_ADMIN rung: all rows in the active organization. Composed as\n * `MEMBER ∪ (all in-org)` so MEMBER ⊆ TENANT_ADMIN holds by construction.\n */\nfunction tenantAdminVisible(rows: readonly LadderRow[], p: LadderPrincipal): LadderRow[] {\n const member = new Set(memberVisible(rows, p));\n return rows.filter((r) => member.has(r) || r.organization_id === p.organizationId);\n}\n\n/** PLATFORM_ADMIN rung: crosses the tenant wall — every row (⊇ TENANT_ADMIN trivially). */\nfunction platformAdminVisible(rows: readonly LadderRow[]): LadderRow[] {\n return [...rows];\n}\n\n/**\n * Reference model of the per-rung injection rule: the visible-row set a rung\n * would resolve to over `rows` for `principal`. Used to lock the ADR-0095 D2\n * invariants (strict nesting + EXTERNAL deny-by-default). NOT an enforcement\n * path — see the module header.\n */\nexport function postureVisibleRows(\n posture: AuthzPosture,\n rows: readonly LadderRow[],\n principal: LadderPrincipal,\n): LadderRow[] {\n switch (posture) {\n case 'PLATFORM_ADMIN':\n return platformAdminVisible(rows);\n case 'TENANT_ADMIN':\n return tenantAdminVisible(rows, principal);\n case 'MEMBER':\n return memberVisible(rows, principal);\n case 'EXTERNAL':\n return externalVisible(rows, principal);\n }\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * The `active` flag on the RBAC grant catalogues — `sys_permission_set` and\n * `sys_position` (ADR-0049 enforce-or-remove).\n *\n * Both objects ship a Deactivate action whose confirmation dialog promises, in\n * four locales, that access stops:\n *\n * > Deactivate this permission set? Existing assignments stay in place but stop\n * > granting access until re-activated.\n * > Deactivate this position? Users keep their assignment but the position stops\n * > granting permissions until re-activated.\n *\n * Nothing read the column, so the promise was false: deactivation changed a\n * badge in Setup and the assignments kept granting. Correctness lives at\n * RESOLUTION time — the same placement ADR-0091 chose for validity windows,\n * and for the same reason: a catalogue flag that is only honoured by a cleanup\n * job is an unenforced security property.\n *\n * ## Why the predicate is \"explicitly deactivated\", not \"explicitly active\"\n *\n * Absent means ACTIVE. Only a stored value that really reads false takes the\n * grant away. Two measured reasons, and they point the same way:\n *\n * 1. **Deployed data.** `active` carries `defaultValue: true`, but a row that\n * predates the column, arrived through a migration, or was projected with a\n * `fields` list that omitted it carries no value at all. Requiring `true`\n * would revoke every such row's grants the moment this lands — a silent\n * mass revocation nobody asked for, which is the opposite of the one thing\n * the Deactivate dialog promises. `isGrantActive` reads absent bounds as\n * unbounded for exactly this reason (ADR-0091 D2).\n * 2. **Storage shapes.** SQLite stores booleans as 1/0 and presents them back\n * as numbers unless the driver knows the column is boolean; the memory\n * driver round-trips real booleans; a JSON/text column can hand back\n * `'false'`. `row.active === false` alone therefore misses the deactivated\n * row on the primary driver — an enforcement hole shaped exactly like the\n * bug this predicate closes. Pushing `active: true` into a driver `where`\n * has the same problem from the other side AND drops the NULL rows of (1),\n * so the filter runs in memory over rows the resolver already fetched:\n * zero new queries, identical answer on every driver.\n *\n * The false-set below is closed on purpose. An unrecognised value (a stray\n * string, an object) is NOT a deactivation — nothing writes one, and inventing\n * a revocation out of junk data is the failing-open-into-a-lockout direction.\n */\n\n/** The stored spellings of a deactivated row, across every driver in the tree. */\nconst DEACTIVATED_VALUES: readonly unknown[] = [false, 0, '0', 'false'];\n\n/** A catalogue row that may carry the `active` flag (`sys_permission_set`, `sys_position`). */\nexport interface ActivatableRow {\n active?: unknown;\n}\n\n/**\n * True unless the row carries an `active` column that is explicitly OFF.\n *\n * The ONE predicate every reader of `sys_permission_set.active` /\n * `sys_position.active` uses, so the resolver that enforces the flag and the\n * break-glass guard that simulates a write to it can never disagree about what\n * \"deactivated\" means.\n */\nexport function isRowActive(row: ActivatableRow | null | undefined): boolean {\n if (!row) return false;\n const value = (row as { active?: unknown }).active;\n if (value === undefined || value === null) return true;\n return !DEACTIVATED_VALUES.includes(value);\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * assembleExecutionContext — the SINGLE assembly of an inbound request's\n * {@link ExecutionContext}, shared by every transport entry point.\n *\n * `resolveAuthzContext` (next door) already made AUTHORIZATION resolution\n * single-sourced. The step AFTER it — turning the resolved\n * {@link ResolvedAuthzContext} into the `ExecutionContext` envelope that\n * reaches enforcement — stayed hand-written per transport, and that duplication\n * produced a measured defect family:\n *\n * - **#6071 — field drift.** The REST copy never set `principalKind`, so every\n * enforcement judgment reading it (explain's guest⇒EXTERNAL floor, the\n * security plugin's agent baseline, the perf-disclosure gate) was silently\n * never-true on that face.\n * - **#6206 / #6551 — dropped fields.** The share-link copies omitted\n * `accessible_org_ids`, and the `group` posture's Layer 0 wall reads it\n * directly: real 403s for callers who should have been let through. Both\n * surfaces were since converted to pass the WHOLE envelope through.\n *\n * Both defects are the same shape: a field exists on `ExecutionContext`, one\n * copy carries it, another silently does not. This module makes that shape\n * unrepresentable by CLOSING the field set with a type\n * ({@link ExecutionContextEntryFields}) — every field a transport entry point\n * decides must be decided HERE, explicitly, and a new `ExecutionContext` field\n * fails to compile until it is either assembled or listed as\n * non-entry-resolved.\n *\n * ## Two named entries — the anonymous face is genuinely divergent (#6216)\n *\n * The maintainer ruling of 2026-08-08 on #6216 (Option A) settled the one\n * question that blocked convergence: what an anonymous request yields.\n *\n * - {@link assembleExecutionContext} — the DEFAULT, fail-closed entry. No\n * resolved principal → `undefined`, and the surface answers 401. This is the\n * REST face's contract, unchanged.\n * - {@link assembleExecutionContextOrGuest} — the EXPLICIT guest entry. No\n * resolved principal → a first-class guest envelope\n * (`principalKind: 'guest'`, `positions: ['guest']`), which the runtime /\n * MCP dispatcher has always produced and whose consumers are live\n * (`plugin-security/explain-engine.ts`: guest ⇒ `EXTERNAL` posture). A\n * surface adopts this entry ONLY when its product semantics serve anonymous\n * principals.\n *\n * Neither surface's runtime behaviour changes. What changes is that the\n * divergence is now NAMED API rather than drift — and the same is true of the\n * per-face values below (`accessToken`, `oauth`, `localization`): they are\n * REQUIRED inputs, so a face cannot silently omit one, and what a face chooses\n * to withhold it withholds on the record.\n *\n * Options B (guest everywhere — turns REST's anonymous 401s into authz-shaped\n * denials and makes anonymous-serving the DEFAULT posture of a new surface) and\n * C (no-ctx everywhere — deletes the guest principal, the `guest` position and\n * explain's `EXTERNAL` floor) were both considered and rejected: each breaks a\n * live consumer side.\n */\n\nimport type { ExecutionContext } from '@objectstack/spec/kernel';\n\nimport type { AuthGate } from './auth-gate.js';\nimport type { ResolvedAuthzContext } from './resolve-authz-context.js';\n\n/**\n * `ExecutionContext` fields a transport ENTRY POINT does not resolve — they are\n * per-operation flags, engine internals, or attribution supplied further down\n * the stack. Listing one here is a deliberate, reviewable statement that no\n * request-identity resolution produces it; everything NOT listed is part of the\n * closed entry set below and must be assembled.\n *\n * - `actor` / `attributedUserId` — audit attribution, set by the host or the\n * hook layer (ADR-0014 D2, #4586), never by identity resolution.\n * - `rlsMembership` — engine-side RLS scoping cache.\n * - `transaction` / `traceId` — per-operation handles.\n * - `flowRunId`, `skipTriggers`, `skipAutomations`, `seedReplay`,\n * `skipStateMachine`, `preserveAudit` — per-write behaviour flags,\n * server-constructed at the call site.\n */\ntype NonEntryExecutionContextField =\n | 'actor'\n | 'attributedUserId'\n | 'rlsMembership'\n | 'transaction'\n | 'traceId'\n | 'flowRunId'\n | 'skipTriggers'\n | 'skipAutomations'\n | 'seedReplay'\n | 'skipStateMachine'\n | 'preserveAudit';\n\n/**\n * The CLOSED field set every transport entry point must decide. Derived from\n * `ExecutionContext` itself, so adding a field to `ExecutionContextSchema`\n * widens this union automatically — and the assembly below stops compiling\n * until the new field is either assembled or declared non-entry-resolved.\n */\nexport type EntryExecutionContextField = Exclude<\n keyof ExecutionContext,\n NonEntryExecutionContextField\n>;\n\n/**\n * One value per closed field. Every key is REQUIRED (`-?`) while the VALUE may\n * be `undefined` — the decision may not be omitted, only made explicitly. This\n * is the type that makes the #6071 drift class unrepresentable.\n */\nexport type ExecutionContextEntryFields = {\n [K in EntryExecutionContextField]-?: ExecutionContext[K];\n};\n\n/**\n * Emission order of the assembled envelope, and a second, independent\n * exhaustiveness bite: `satisfies` rejects a stale name, and\n * `_ENTRY_FIELDS_EXHAUSTIVE` below rejects a missing one.\n */\nexport const ENTRY_EXECUTION_CONTEXT_FIELDS = [\n 'positions',\n 'permissions',\n 'systemPermissions',\n 'isSystem',\n 'principalKind',\n 'onBehalfOf',\n 'audience',\n 'userId',\n 'tenantId',\n 'email',\n 'accessToken',\n 'tabPermissions',\n 'posture',\n 'authGate',\n 'org_user_ids',\n 'accessible_org_ids',\n 'oauthScopes',\n 'timezone',\n 'locale',\n 'currency',\n] as const satisfies readonly EntryExecutionContextField[];\n\n/**\n * Compile-time proof that {@link ENTRY_EXECUTION_CONTEXT_FIELDS} covers the\n * whole closed set. A new `ExecutionContext` field that is neither assembled\n * nor listed as non-entry-resolved makes this `never`, and the initializer\n * below fails to compile.\n */\ntype MissingEntryField = Exclude<\n EntryExecutionContextField,\n (typeof ENTRY_EXECUTION_CONTEXT_FIELDS)[number]\n>;\nconst _ENTRY_FIELDS_EXHAUSTIVE: [MissingEntryField] extends [never] ? true : never = true;\nvoid _ENTRY_FIELDS_EXHAUSTIVE;\n\n/**\n * OAuth 2.1 access-token provenance. Reaches the assembler from the `/mcp`\n * dispatch door ALONE (`acceptOAuthAccessToken`) — OAuth bearers carry coarse\n * tool-family scopes enforced at MCP tool dispatch, so honouring them on\n * another surface would bypass that scope model entirely.\n */\nexport interface OAuthTokenProvenance {\n /** The human `sub` the token was issued for. */\n userId: string;\n /** Granted scopes, surfaced on the envelope so MCP can narrow tool families. */\n scopes: string[];\n /**\n * The authorized client (`azp`). Present ⇒ this is an AI AGENT acting on\n * behalf of the human `userId`; absent ⇒ the token names no client and the\n * principal stays human (the scopes are still surfaced).\n */\n clientId?: string;\n /**\n * The agent's OWN permission CEILING, derived from {@link scopes} by the door\n * that speaks the OAuth scope vocabulary\n * (`scopesToAgentPermissionSets`, `@objectstack/spec/ai`).\n *\n * Interpreted THERE and not here on purpose: the scope vocabulary is\n * MCP-domain knowledge and `@objectstack/core` is the microkernel — it should\n * not acquire a dependency on the AI subdomain (concretely, every package\n * whose test config aliases `@objectstack/core` to its source would then have\n * to resolve `@objectstack/spec/ai` too, down to `driver-memory`). What the\n * ceiling REPLACES on the envelope is decided below, once, for every face —\n * and that is the part that drifted.\n */\n scopePermissions: string[];\n /**\n * Whether the token carries the user's consent to let this agent invoke\n * actions on their behalf — the `actions:execute` scope\n * (`MCP_OAUTH_SCOPE_ACTIONS`), evaluated at the same door for the same\n * reason.\n */\n delegatesActions: boolean;\n}\n\n/** Reference localization for an authenticated principal (@see resolveLocalizationContext). */\nexport interface EntryLocalization {\n timezone?: string;\n locale?: string;\n currency?: string;\n}\n\n/**\n * Everything the shared assembly needs. Every key is REQUIRED so a face cannot\n * silently omit one — a face that has no value for an input passes `undefined`\n * on the record, which is what turns the remaining divergences into named API.\n */\nexport interface ExecutionContextAssemblyInput {\n /** The shared authorization envelope (@see resolveAuthzContext). */\n authz: ResolvedAuthzContext;\n /**\n * OAuth access-token provenance, or `undefined` on a face that does not\n * accept one. Only the `/mcp` dispatch door passes a value; REST passes\n * `undefined` — which is why `principalKind: 'agent'`, `onBehalfOf` and\n * `oauthScopes` are not representable there.\n */\n oauth: OAuthTokenProvenance | undefined;\n /**\n * Resolved reference localization, or `undefined` when the face resolved\n * none (anonymous requests have no scope to resolve against).\n */\n localization: EntryLocalization | undefined;\n /**\n * The request's OWN locale preference (`Accept-Language`, `?locale`, …),\n * which wins over the workspace default; `undefined` when the caller\n * expresses none. Each face extracts it its own way — the PRECEDENCE lives\n * here so the two cannot disagree about it (#3957).\n */\n requestLocale: string | undefined;\n /**\n * The session bearer to carry on the envelope, surfaced to hooks as\n * `session.accessToken` (`objectql/engine.ts` `buildSession`,\n * `spec/data/hook.zod.ts`).\n *\n * A NAMED per-face divergence, preserved deliberately (#6216): the runtime /\n * MCP dispatcher passes `authz.accessToken`; the REST face has never carried\n * it and passes `undefined`, because widening a published hook surface to\n * expose the session token on a second transport is a product decision, not a\n * refactor. Being a required input, the choice is on the record at each face\n * instead of being an omission nobody can see.\n */\n accessToken: string | undefined;\n /**\n * [ADR-0069] The AUTHENTICATION-policy gate posture resolved for this\n * request's session (expired password / enforced MFA), or `undefined` when\n * the face resolves none — normalize a session user through\n * `normalizeAuthGate` rather than copying its `authGate` verbatim.\n *\n * A NAMED per-face divergence, on the same footing as {@link accessToken}\n * (#7280):\n *\n * - the **REST** face lifts it onto the envelope, because that is where its\n * consumer reads it (`RestServer.enforceAuth` → `403 { code, message }`);\n * - the **runtime / MCP dispatcher** passes `undefined`, because it enforces\n * the same ADR-0069 gate at its OWN seam (`HttpDispatcher.enforceAuthGate`\n * re-reads the session and calls `evaluateAuthGate` there) and never reads\n * `context.authGate` — carrying it would be a second, unread copy.\n *\n * Until #7280 declared it, this posture reached the envelope through an\n * `as any` spread AFTER assembly, which put it outside this closed set\n * entirely — the blind spot the set exists to remove.\n */\n authGate: AuthGate | undefined;\n}\n\n/** Drop `undefined`-valued keys, emitting in the closed set's declared order. */\nfunction emit(fields: ExecutionContextEntryFields): ExecutionContext {\n const ctx: Record<string, unknown> = {};\n for (const key of ENTRY_EXECUTION_CONTEXT_FIELDS) {\n const value = fields[key];\n if (value !== undefined) ctx[key] = value;\n }\n return ctx as ExecutionContext;\n}\n\n/**\n * Decide every field of the closed set. The one branch is the principal's\n * PROVENANCE (agent / human / guest); everything else is a single expression\n * shared by every face.\n */\nfunction entryFields(\n input: ExecutionContextAssemblyInput,\n anonymous: boolean,\n): ExecutionContextEntryFields {\n const { authz, oauth, localization, requestLocale, accessToken, authGate } = input;\n\n // [ADR-0090 D10 — agent principal] An OAuth access token naming an authorized\n // client (`azp`) is an AI agent acting ON BEHALF OF the human `sub`. The\n // agent's OWN grants are its scope-derived CEILING, NOT the user's — so the\n // user-derived positions/permissions/systemPermissions are REPLACED with that\n // ceiling. The human stays the delegator (`onBehalfOf`), and the security\n // engine intersects the two so the agent can never exceed EITHER its\n // consented scope OR the user's own reach (confused-deputy prevention).\n // `userId` stays the human so owner-stamping and `current_user.*` RLS resolve\n // to them.\n const agent = !anonymous && oauth?.clientId ? oauth : undefined;\n\n return {\n // [ADR-0090 D9/D10] Principal taxonomy at the HTTP entry: a session-backed\n // request is a human principal; a sessionless one is a guest, holding the\n // built-in `guest` position implicitly and exclusively. Internal engine\n // calls that construct bare contexts never pass through here, so the\n // security plugin's empty-context skip path keeps its meaning.\n positions: agent ? [] : anonymous ? ['guest'] : authz.positions,\n permissions: agent ? agent.scopePermissions : authz.permissions,\n // [ADR-0090 D10] System capabilities on the agent principal gate business\n // ACTION invocation (`actionPermissionError` reads `ctx.systemPermissions`)\n // — a door SEPARATE from the object CRUD/FLS/RLS intersection, which is\n // driven by the resolved ceiling SETS (they carry no caps, so cap-gated\n // OBJECT access stays denied to the agent regardless of this line). The\n // `actions:execute` scope IS the user's consent to let this agent invoke\n // actions on their behalf; without it the agent holds none.\n systemPermissions: agent\n ? agent.delegatesActions\n ? (authz.systemPermissions ?? [])\n : []\n : authz.systemPermissions,\n isSystem: false,\n principalKind: agent ? 'agent' : anonymous ? 'guest' : 'human',\n onBehalfOf: agent ? { userId: authz.userId!, principalKind: 'human' } : undefined,\n // [ADR-0090 D10/D11 — P1 shape] No transport resolves an external\n // (portal/partner) audience yet; `undefined` reads as 'internal'. Named\n // here rather than excluded so the gap is visible in the closed set instead\n // of being invisible outside it — when an external principal type lands,\n // this is the line that must change, on every face at once.\n audience: undefined,\n userId: authz.userId,\n tenantId: authz.tenantId,\n email: authz.email,\n accessToken,\n tabPermissions: authz.tabPermissions,\n // [ADR-0095 D2 / #2947] The derived posture rung, carried so every\n // transport presents enforcement the SAME value. Present only for an\n // authenticated principal (guest → absent).\n posture: authz.posture,\n // [ADR-0069 / #7280] The AUTHENTICATION-policy gate, carried for the seam\n // that reads it off the envelope (REST's `enforceAuth`). Anonymous → never:\n // a guest has no authenticated session for a policy gate to attach to, so\n // \"gated guest\" is not a state this entry can emit even if a face passed\n // one.\n authGate: anonymous ? undefined : authGate,\n /** Fellow-org user IDs for RLS scoping of identity tables. */\n org_user_ids: authz.org_user_ids,\n // [ADR-0105 D2] The caller's org access set — the `group` posture's Layer 0\n // wall reads it directly, so every transport must carry it (#6206).\n accessible_org_ids: authz.accessible_org_ids,\n // OAuth provenance: surface the token's granted scopes so the MCP\n // dispatcher can narrow the exposed tool families (undefined for every\n // other provenance = not scope-limited).\n oauthScopes: oauth && authz.userId === oauth.userId ? oauth.scopes : undefined,\n // Anonymous → no localization (no scope to resolve against); the engine\n // default stands. [#3957] The request's OWN language preference wins over\n // the workspace default, so a rejection message is not rendered in English\n // beside the Chinese label of the very field it names.\n timezone: anonymous ? undefined : localization?.timezone,\n locale: anonymous ? undefined : (requestLocale ?? localization?.locale),\n currency: anonymous ? undefined : localization?.currency,\n };\n}\n\n/**\n * The DEFAULT, fail-closed entry (#6216 Option A). An unauthenticated request\n * yields NO context — the surface answers 401. Every surface uses this one\n * unless serving anonymous principals is part of its product semantics.\n */\nexport function assembleExecutionContext(\n input: ExecutionContextAssemblyInput,\n): ExecutionContext | undefined {\n if (!input.authz.userId) return undefined;\n return emit(entryFields(input, false));\n}\n\n/**\n * The EXPLICIT guest entry (#6216 Option A). An unauthenticated request becomes\n * a first-class guest principal — `principalKind: 'guest'`, `positions:\n * ['guest']` — which enforcement consumers read today\n * (`plugin-security/explain-engine.ts`: guest ⇒ `EXTERNAL` posture).\n *\n * Adopt this ONLY on a surface that genuinely serves anonymous principals: the\n * built-in `guest` position is the declared vocabulary for \"what anonymous may\n * do\", and handing a guest envelope to a surface that previously answered 401\n * converts an authentication failure into an authorization evaluation.\n */\nexport function assembleExecutionContextOrGuest(\n input: ExecutionContextAssemblyInput,\n): ExecutionContext {\n return emit(entryFields(input, !input.authz.userId));\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * ADR-0069 — authentication-policy session gate.\n *\n * Some auth policies (password expiry, enforced MFA) must block an\n * authenticated user from PROTECTED RESOURCES until they remediate, while\n * still letting them reach the auth endpoints (change-password, two-factor\n * enrollment, sign-out) and a few UI-bootstrap reads.\n *\n * The posture is computed ONCE, in the auth `customSession` enrichment, and\n * attached to the session user as `user.authGate = { code, message }`. The\n * transport seams (REST middleware, dispatcher) then call\n * {@link evaluateAuthGate} to decide whether THIS request is blocked. Keeping\n * the allow-list + decision in one pure function means the seams can never\n * drift on what is blocked.\n */\n\nimport type { ExecutionContext } from '@objectstack/spec/kernel';\n\n/**\n * The gate posture, DERIVED from its declaration on `ExecutionContextSchema`\n * (`packages/spec/src/kernel/execution-context.zod.ts`) rather than restated\n * here (#7280).\n *\n * It was a hand-written interface while the envelope field was undeclared, so\n * the two could have drifted with nothing to catch it — the exact class of\n * defect the closed entry field set (#6216) exists to make unrepresentable.\n * One declaration, one type.\n */\nexport type AuthGate = NonNullable<ExecutionContext['authGate']>;\n\n/** Message used when a session's gate names a `code` but no usable `message`. */\nconst DEFAULT_AUTH_GATE_MESSAGE = 'Access is blocked by an authentication policy.';\n\n/**\n * Normalize the `authGate` a better-auth session user carries into the shape\n * `ExecutionContextSchema` declares — or `null` when there is no gate.\n *\n * The session user crosses an external boundary as `any`, so this is where the\n * declared contract is actually met: a gate naming no string `code` is not a\n * gate, and a missing/blank `message` is filled with the default rather than\n * riding onto the envelope (and into a `403` body) as `undefined`. Both\n * consumers normalize HERE, at the one producer, instead of tolerating a loose\n * shape downstream: {@link evaluateAuthGate} for the seams that decide per\n * path, and REST's `computeExecCtx` for the seam that lifts the posture onto\n * the execution context.\n */\nexport function normalizeAuthGate(sessionUser: any): AuthGate | null {\n const gate = sessionUser?.authGate;\n if (!gate || typeof gate.code !== 'string') return null;\n return {\n code: gate.code,\n message:\n typeof gate.message === 'string' && gate.message ? gate.message : DEFAULT_AUTH_GATE_MESSAGE,\n };\n}\n\n// Endpoints a gated user MUST still reach to remediate or bootstrap the\n// remediation UI. Matched against the request path (query stripped). Covers\n// both REST (`/api/v1/auth/…`) and dispatcher (`/auth/…`) path shapes.\nconst ALLOW_PREFIXES = ['/api/v1/auth/', '/api/auth/', '/auth/'];\nconst ALLOW_SUFFIXES = ['/health', '/ready', '/discovery', '/me/apps', '/me/localization'];\n\n/** True when `path` is exempt from the auth gate (auth + remediation + health). */\nexport function isAuthGateAllowlisted(rawPath: string | undefined | null): boolean {\n if (!rawPath) return true;\n // Strip query + trailing slashes WITHOUT a regex (avoids ReDoS on a\n // path of many '/'). char 47 = '/'.\n let path = rawPath.split('?')[0] || '/';\n let end = path.length;\n while (end > 1 && path.charCodeAt(end - 1) === 47) end--;\n path = path.slice(0, end) || '/';\n // Any path with an `/auth/` segment is an auth endpoint (covers project-\n // scoped mounts like `/api/v1/environments/:env/auth/...`).\n if (path.includes('/auth/')) return true;\n for (const p of ALLOW_PREFIXES) {\n if (path.startsWith(p) || path === p.replace(/\\/$/, '')) return true;\n }\n for (const s of ALLOW_SUFFIXES) {\n if (path.endsWith(s)) return true;\n }\n return false;\n}\n\n/**\n * Returns the active gate when `sessionUser` carries an `authGate` AND `path`\n * is not allow-listed; otherwise null. Anonymous users (no `authGate`) and\n * allow-listed paths always pass.\n */\nexport function evaluateAuthGate(sessionUser: any, path: string): AuthGate | null {\n const gate = normalizeAuthGate(sessionUser);\n if (!gate) return null;\n if (isAuthGateAllowlisted(path)) return null;\n return gate;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * #2567 — the single anonymous-deny decision, shared by every HTTP seam.\n *\n * ADR-0056 D2 made the platform deny anonymous callers by default. Phase 1 gated\n * each surface separately, with every seam hand-rolling the same\n * `!userId && !isSystem → 401` check. This centralises that DECISION into one\n * pure, tested function — the exact pattern {@link ./auth-gate.ts} established\n * for the ADR-0069 auth-policy gate: keeping the decision in one function means\n * the seams can never drift on who is denied.\n *\n * ## Do NOT keep a route list here (#10835)\n *\n * This paragraph used to name the Phase-1 surfaces inline — \"REST `/data`,\n * dispatcher `/graphql` + `/meta`, raw-hono `/data`\" — and TWO of those three\n * outlived the routes they named:\n *\n * - **`/graphql` is gone.** Removed with the GraphQL surface itself\n * (`packages/runtime/src/http-dispatcher.ts`: \"/graphql removed — GraphQL is\n * not in the product plan\", #2462 follow-on). No `handleGraphQL` survives\n * anywhere in `packages/runtime`, and `/graphql` is now ordinary catch-all\n * traffic.\n * - **raw-hono `/data` is gone.** Those routes were deleted as a duplicate\n * surface in v17 (#4073); `packages/adapters/hono` mounts only `/discovery`,\n * `/auth/*` and the terminal `${prefix}/*` catch-all.\n *\n * A stale list here is worse than no list, because the seams it names are the\n * thing a reader opens this file to learn: a dead route reads as a live surface.\n * That is not hypothetical — the `handleGraphQL` test doubles removed alongside\n * this edit had become the only non-CHANGELOG hits in `packages/**`, and so read\n * to the next author grepping the name as evidence the runtime still had the\n * method.\n *\n * The live enumeration is mechanical, so defer to it rather than restating it:\n * `packages/qa/dogfood/test/authz-conformance.matrix.ts` and its companion\n * `authz-conformance.test.ts` ratchet a CURATED table of HTTP/transport entry\n * points out of source — deleting a `shouldDenyAnonymous` call makes its row\n * STALE, a new ungated route in one of those files is UNCLASSIFIED, and either\n * breaks CI. Read the ratchet, or read the call sites.\n *\n * ## The `requireAuth` opt-out is gone (#3963)\n *\n * This used to take a `requireAuth` posture and no-op when it was falsy, so a\n * deployment could open its ENTIRE data plane with one config key. That key is\n * retired: auth is a kernel concern, and every surface that legitimately serves\n * a caller with no session derives its own narrow authorization from a\n * DECLARATION instead of from the deployment posture —\n *\n * - control plane (`/auth/*`, `/health`, `/ready`, `/discovery`, the ADR-0069\n * remediation paths) → the {@link isAuthGateAllowlisted} allowlist, below;\n * - public form submission → `publicFormGrant` (ADR-0056 Option A), derived\n * from the form view's own declaration;\n * - share links → the capability token, validated then read as SYSTEM;\n * - a `book.audience: 'public'` read → the ADR-0046 §6.7 audience gate (#3963);\n * - MCP → an OAuth token or API key, never anonymous.\n *\n * Those run UPSTREAM of this function and set the execution context (a `userId`,\n * or `isSystem`) or bypass the seam entirely, so this only ever inspects the\n * already-resolved context. Nothing else gets in.\n */\n\nimport { isAuthGateAllowlisted } from './auth-gate.js';\n\n/** HTTP status every seam returns for an anonymous-denied request. */\nexport const ANONYMOUS_DENY_STATUS = 401 as const;\n/** Stable machine code (mirrors the REST `enforceAuth` seam). ADR-0112: SCREAMING, a `StandardErrorCode` member. */\nexport const ANONYMOUS_DENY_CODE = 'UNAUTHENTICATED' as const;\n/** Human-facing message. */\nexport const ANONYMOUS_DENY_MESSAGE = 'Authentication is required to access this endpoint.';\n/**\n * The **REST seam's** 401 body — flat `{ error, code, message }`. NOT the\n * platform's only one; see the two-envelope table below before you reuse this\n * shape.\n *\n * Two consumers write it verbatim, both flat-family seams: `@objectstack/rest`'s\n * `enforceAuth` (`rest-server.ts` —\n * `res.status(ANONYMOUS_DENY_STATUS).json(ANONYMOUS_DENY_BODY)`), which owns the\n * `/data/*` and `/meta` surfaces, and `@objectstack/runtime`'s\n * `mountRouteOnServer` (`dispatcher-plugin.ts` — the endpoint-route 401 arm,\n * #9823), which answers declared routes mounted on the HTTP server.\n *\n * ## Two live envelopes, one denial (#5632)\n *\n * Every HTTP seam shares the DECISION ({@link shouldDenyAnonymous}) and the\n * semantics ({@link ANONYMOUS_DENY_STATUS} / {@link ANONYMOUS_DENY_CODE} /\n * {@link ANONYMOUS_DENY_MESSAGE}). What differs is the **wrapper**:\n *\n * - **REST seam** — `@objectstack/rest` `enforceAuth`, this constant, verbatim:\n * `{ error: 'UNAUTHENTICATED', code: 'UNAUTHENTICATED', message: '…' }`.\n * The machine code lives in the top-level `code` key — the same documented\n * key every other REST error family answers (#9487, maintainer-ruled\n * ADDITIVE: `error` keeps carrying the code value it always has, so no\n * existing reader breaks). There is no `success` key and no nesting.\n * - **Dispatcher seams** — the five runtime domains `domains/ai.ts`,\n * `domains/meta.ts`, `domains/security.ts`, `domains/actions.ts` and\n * `domains/automation.ts` do NOT use this constant. Each calls\n * `deps.error(ANONYMOUS_DENY_MESSAGE, ANONYMOUS_DENY_STATUS, { code: ANONYMOUS_DENY_CODE })`,\n * so the wire body is the dispatcher's standard wrapper:\n * `{ success: false, error: { code, message, httpStatus } }`.\n *\n * Both shapes are **live and sanctioned** — ADR-0112's 2026-07-30 amendment\n * (#4007) records the flat and wrapped envelopes as the two live ones, and\n * assigns retiring one of them to the envelope-convergence line (#3843 family).\n * Converging them is a breaking wire change; it is not this module's to make,\n * and this constant must not be read as if it had already happened. The #9487\n * `code` key does NOT settle that question either way (ADR-0112 D5 stays\n * open): it aligns the flat family to the `{ error, code }` shape the other\n * flat REST error families already answer, without moving or removing a key.\n *\n * ## Reading this from a consumer (human or AI author)\n *\n * Read the envelope the seam you called DECLARES — flat from `/data` + `/meta`,\n * wrapped from a dispatcher-mounted surface. Do **not** write a tolerant\n * `body.error?.code ?? body.error` chain that swallows both: that fallback is\n * precisely where an envelope regression hides, and this docstring claiming to\n * be \"the single shape every seam returns\" is what used to invite it (#5632).\n *\n * Both shapes are pinned against a real booted showcase by\n * `packages/qa/dogfood/test/showcase-anonymous-deny-surfaces.dogfood.test.ts`,\n * which classifies every anonymous 401 into exactly one of the two families and\n * fails on a third dialect or on a seam that changes family.\n */\nexport const ANONYMOUS_DENY_BODY = {\n error: ANONYMOUS_DENY_CODE,\n code: ANONYMOUS_DENY_CODE,\n message: ANONYMOUS_DENY_MESSAGE,\n} as const;\n\nexport interface AnonymousDenyInput {\n /** Resolved caller id, if any. */\n userId?: string | null;\n /** Internal system context (never set on inbound HTTP; cannot be forged). */\n isSystem?: boolean;\n /** HTTP method — `OPTIONS` (CORS preflight) always passes. */\n method?: string | null;\n /**\n * OPTIONAL request path. When a NON-EMPTY string, a control-plane path\n * (auth / health / ready / discovery — see {@link isAuthGateAllowlisted}) is\n * exempt. Body-routed seams (GraphQL) have no meaningful path and pass\n * `undefined`; see the guard below for why that is load-bearing.\n */\n path?: string | null;\n}\n\n/**\n * True when the request MUST be rejected with 401. The one decision every HTTP\n * seam shares.\n */\nexport function shouldDenyAnonymous(input: AnonymousDenyInput): boolean {\n if (typeof input.method === 'string' && input.method.toUpperCase() === 'OPTIONS') {\n return false; // CORS preflight\n }\n if (input.userId || input.isSystem) return false; // authenticated / system\n // Control-plane exemption — ONLY for a real, non-empty path.\n //\n // ⚠️ `isAuthGateAllowlisted(undefined)` returns `true` (it treats \"no path\"\n // as allow-listed for the auth-gate's purposes). A body-routed seam such as\n // GraphQL has no meaningful request path; if it passed `undefined` straight\n // through, the allowlist would exempt EVERY anonymous query and silently\n // reopen exactly the hole #2567 closes. The non-empty guard is mandatory.\n if (typeof input.path === 'string' && input.path.length > 0 && isAuthGateAllowlisted(input.path)) {\n return false;\n }\n return true;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * ADMIN_STANDING_SURFACE — what `resolveAuthzContext` READS when it decides\n * who is an administrator, declared beside the resolver that reads it.\n *\n * ## Why this file exists (#8734)\n *\n * `plugin-auth`'s break-glass guard (`last-admin-guard.ts`, ADR-0024 D5.2)\n * decides whether a pending write can empty the administrator population by\n * testing the payload against three standing-key lists — `MEMBER_STANDING_KEYS`,\n * `GRANT_STANDING_KEYS`, `PERMISSION_SET_STANDING_KEYS`. Those lists are not an\n * independent design artifact: they are a CACHE of the columns this resolver\n * consumes. A payload touching none of them is skipped without any reads, so a\n * column this resolver starts reading and the guard's list omits is a write\n * class the guard silently stops judging — the one write class that can lock an\n * installation out of its own administration, with no in-product recovery.\n *\n * Nothing bound the two together. The correspondence was carried by a comment,\n * and it had already gone false once: #6084 wrote, beside the list, that\n * everything a permission-set write touches other than `name` — naming `active`\n * explicitly — is invisible to \"who is an administrator\". That was true when\n * written. #8613 made `active` a resolution-time predicate (a DEACTIVATED\n * `admin_full_access` set confers nothing, §6b below), and the sentence became\n * false. It was caught by one agent reading the comment closely enough to\n * notice it contradicted the code being written. Nothing mechanical would have\n * caught it: the guard's own tests stay green, because the guard is simply never\n * consulted for that write.\n *\n * ## What this file is, and what it is NOT\n *\n * It is a MEASUREMENT, not a wish. Its column lists are asserted equal to what\n * the resolver actually reads at runtime, by\n * `admin-standing-surface.test.ts`, which drives the real\n * `resolveAuthzContext` over a recording engine and collects every property\n * access and every `where` key per table. That is deliberate: a hand-written\n * list of \"columns the derivation reads\" is the same artifact as the comment\n * that went stale, one indirection along. Observation is also the only reading\n * that survives the derivation moving INTO a helper — `active` is read by\n * `isRowActive(ps)` and the window bounds by `isGrantActive(row, now)`, neither\n * of which names a column at the resolver's own call site.\n *\n * It is NOT a projection the resolver consumes. `ql.find` here returns whole\n * rows and the reads are ordinary property accesses on untyped rows, so nothing\n * in this file can FORCE the resolver to read only what it declares. The force\n * comes from the observation test: add a read, and this declaration is red\n * until it is updated; update this declaration, and `plugin-auth`'s\n * correspondence test is red until every new column is either in a standing-key\n * list or explicitly excluded with a reason.\n *\n * ## Reading the entries\n *\n * Every table this resolution path reads is listed — including the ones that\n * CANNOT confer administrator standing, each with the reason it cannot. That is\n * the table-level half of the same guarantee: a resolver that starts deriving\n * administrator standing from a new table would otherwise be invisible to a\n * column-set comparison, because the new table appears in neither side's list.\n */\n\n/** How a table this resolver reads relates to \"who is an administrator\". */\nexport interface AdminStandingTable {\n /**\n * `derives` — a write to this table can change the administrator population,\n * so `last-admin-guard.ts` must carry a standing-key list for it.\n * `reads-only` — this resolver reads the table for something else entirely.\n */\n readonly role: 'derives' | 'reads-only';\n /** Why the row above is the right classification. Prose, but pinned to a measured table. */\n readonly reason: string;\n /**\n * Every column this resolver reads on the table — property accesses and\n * `where` keys alike, in every spelling it actually touches. Declared for\n * `derives` tables only; asserted equal to the observed set.\n */\n readonly columns?: readonly string[];\n}\n\n/**\n * The measured read surface of the administrator derivation.\n *\n * Scope, stated so the gate cannot be read as claiming more than it measures:\n * this is the SESSION/user-id resolution path — `resolveAuthzContext` with a\n * principal, and therefore all of `resolveUserAuthzGrants`. The API-key\n * ADMISSION path (`resolveApiKeyAdmission`) is outside it on purpose: it\n * authenticates a principal and seeds `permissions` with the key's scopes, and\n * confers no administrator standing of its own — `hasPlatformAdminGrant` (§6b)\n * is set only from a `sys_permission_set` row reached through an UNSCOPED\n * `sys_user_permission_set` grant, never from a scope string.\n */\nexport const ADMIN_STANDING_SURFACE: Readonly<Record<string, AdminStandingTable>> = {\n sys_permission_set: {\n role: 'derives',\n reason:\n 'The row `platform_admin` is resolved BY NAME from (§6b). Renaming it, deleting it or '\n + 'switching it off (ADR-0049 `active`, read here since #8613) un-makes every platform '\n + 'admin at once, with no identity table touched.',\n columns: [\n 'id',\n 'name',\n 'active',\n 'system_permissions',\n 'systemPermissions',\n 'tab_permissions',\n 'tabPermissions',\n ],\n },\n\n sys_user_permission_set: {\n role: 'derives',\n reason:\n 'The grant that makes a user a platform admin: an UNSCOPED, in-window (ADR-0091) grant of '\n + '`admin_full_access` (§6). Re-pointing it, scoping it to an organization or moving it out '\n + 'of its window revokes the standing while leaving the row in place.',\n columns: [\n 'user_id',\n 'permission_set_id',\n 'permissionSetId',\n 'organization_id',\n 'organizationId',\n 'valid_from',\n 'validFrom',\n 'valid_until',\n 'validUntil',\n ],\n },\n\n sys_member: {\n role: 'derives',\n reason:\n 'Organization owner/admin standing (§3). The graded `role` is projected into `positions` '\n + 'here and separately drives the `organization_admin` capability grant, which is what the '\n + 'posture ladder reads; the break-glass guard counts the same rows one step earlier, by '\n + 'grade (ADR-0108). Either way a downgrade of the last graded membership is a write that '\n + 'can empty the administrator population.',\n columns: [\n 'user_id',\n 'userId',\n 'organization_id',\n 'organizationId',\n 'role',\n 'valid_from',\n 'validFrom',\n 'valid_until',\n 'validUntil',\n ],\n },\n\n sys_user: {\n role: 'reads-only',\n reason:\n 'Read for the `current_user.email` RLS fallback and the ADR-0024 `ai_seat` synthesis (§7). '\n + 'Neither confers administrator standing. The guard does watch this table, but for the '\n + 'ban/delete WRITE SHAPES — `banned` is never read here, so it is not a derivation column '\n + 'and carries no standing-key list.',\n },\n\n sys_user_position: {\n role: 'reads-only',\n reason:\n 'ADR-0057 D4 platform-RBAC position assignments (§4). A position can carry permission sets '\n + '(see `sys_position_permission_set`) but never platform-admin standing — §6b requires the '\n + 'set to be reached through an unscoped USER grant (`unscopedUserPsIds`), so a '\n + 'position-bound `admin_full_access` resolves the set name into `permissions` and leaves '\n + '`hasPlatformAdminGrant` false.',\n },\n\n sys_position: {\n role: 'reads-only',\n reason:\n 'Read to drop DEACTIVATED positions (ADR-0049, §6a). Same reason as `sys_user_position`: '\n + 'the position path cannot reach `hasPlatformAdminGrant`.',\n },\n\n sys_position_permission_set: {\n role: 'reads-only',\n reason:\n 'Position-bound permission sets (§6a). Contributes ids to `psIds` — and therefore names to '\n + '`permissions` — but not to `unscopedUserPsIds`, which is the set §6b tests for '\n + 'platform-admin standing.',\n },\n};\n\n/** The tables a write to which can change who is an administrator. */\nexport function adminStandingTables(): string[] {\n return Object.entries(ADMIN_STANDING_SURFACE)\n .filter(([, t]) => t.role === 'derives')\n .map(([name]) => name)\n .sort();\n}\n\n/**\n * The columns this resolver reads on `table`, or `undefined` when the table is\n * not part of the administrator derivation.\n */\nexport function adminStandingColumns(table: string): readonly string[] | undefined {\n const entry = ADMIN_STANDING_SURFACE[table];\n return entry?.role === 'derives' ? entry.columns : undefined;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * [#7678] The `?status=` vocabulary of the audience-binding suggestion list\n * (ADR-0090 D5/D9) — ONE owner for a rule that had exactly one implementation\n * and two seams needing it.\n *\n * The predicate was written for the runtime dispatcher's `/security` domain and\n * lived there, private. The **live** REST route\n * (`rest-server.ts` → `registerSecurityEndpoints`) is a second seam onto the\n * same service call and never had it, so `?status=garbage` reached the service,\n * matched no row, and answered **200 with an empty list** — which reads as\n * \"there are no suggestions\", a plausible and actionable-looking answer, rather\n * than \"your filter was not a status\". That silent arm is the defect; the two\n * seams disagreeing about one contract is the cause.\n *\n * So this module is the convergence, not a copy: `domains/security.ts` and\n * `rest-server.ts` both import from here, and the vocabulary — including the\n * refusal wording — exists once.\n *\n * The record is keyed BY the contract type on purpose (carried over from the\n * original): adding a status to `AudienceBindingSuggestionFilter` leaves a key\n * missing here and renaming one leaves a key excess, and either way this fails\n * to compile. A plain `['pending', …]` array would silently drift.\n */\n\nimport type { AudienceBindingSuggestionFilter } from '@objectstack/spec/contracts';\n\n/** The `status` arm of {@link AudienceBindingSuggestionFilter}, named. */\nexport type AudienceBindingSuggestionStatus = NonNullable<AudienceBindingSuggestionFilter['status']>;\n\n/** The accepted `?status=` values, keyed by the contract type (see module note). */\nexport const AUDIENCE_BINDING_SUGGESTION_STATUSES: Record<AudienceBindingSuggestionStatus, true> = {\n pending: true,\n confirmed: true,\n dismissed: true,\n};\n\n/**\n * The same vocabulary as a list — for refusal messages, and for tests that must\n * enumerate every valid value FROM the type rather than hand-picking one.\n */\nexport const AUDIENCE_BINDING_SUGGESTION_STATUS_VALUES = Object.keys(\n AUDIENCE_BINDING_SUGGESTION_STATUSES,\n) as readonly AudienceBindingSuggestionStatus[];\n\n/**\n * Is `value` one of the three statuses the contract declares? Case-sensitive on\n * purpose — the contract's values are lowercase, so `PENDING` is not a status\n * and gets the same refusal as `garbage`.\n */\nexport const isAudienceBindingSuggestionStatus = (\n value: string,\n): value is AudienceBindingSuggestionStatus =>\n Object.prototype.hasOwnProperty.call(AUDIENCE_BINDING_SUGGESTION_STATUSES, value);\n\n/** The refusal wording, shared so both seams answer an unknown status identically. */\nexport const unknownAudienceBindingSuggestionStatusMessage = (value: string): string =>\n `Unknown status filter '${value}' — expected one of: ${AUDIENCE_BINDING_SUGGESTION_STATUS_VALUES.join(', ')}`;\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * [#7284] The `__` operation-private-key convention — one owner, on the\n * CONSUMER side.\n *\n * `assemble-execution-context.ts` next door is the single place an\n * `ExecutionContext` is BUILT at a transport entry point (#6216). This file is\n * its counterpart at the other end: the single place one is stripped back down\n * before being forwarded to a question it was not resolved for.\n *\n * ## What a `__` key is\n *\n * plugin-security's middleware STAMPS keys onto the operation context, resolved\n * for the object of the operation IN FLIGHT. They are middleware-private\n * vocabulary, not fields of `ExecutionContext`, and every one of them is read as\n * a WIDENING input by whoever consumes it:\n *\n * - the ADR-0057 D1 access DEPTH the sharing owner-match expands to —\n * `__readScope` / `__writeScope`, plus the ADR-0090 D10 delegator halves\n * `__delegatorReadScope` / `__delegatorWriteScope`, stamped in place by\n * `security-plugin.ts` (`sc.__readScope = …`);\n * - the engine's internal privilege markers on the same channel —\n * `__expandRead` marks a read as a lookup EXPANSION sub-read (it no longer\n * relaxes any gate — #7626 removed that waiver — but it still travels with\n * one operation and must not be inherited by another), `__referentialFieldClear`\n * authorizes the referential-clear write.\n *\n * plugin-security is the PRODUCER of that vocabulary and would be the most\n * honest owner of the rule for consuming it, but none of the three consumers\n * depends on it and a string-prefix filter does not justify three new dependency\n * edges onto a plugin (the trade the filing card priced, #7284). `@objectstack/\n * spec` is fenced off by Prime Directive #2. `@objectstack/core` is the only\n * candidate every consumer already depends on, so the rule lives here and the\n * producer stays free of reverse edges.\n *\n * ## Why a consumer must drop them\n *\n * A caller's envelope carries a depth resolved for the object the middleware\n * last saw. A consumer that forwards that envelope to ask about a DIFFERENT\n * object applies one object's widening to another object's question — the exact\n * stale-scope leak `resolveWriteScopeForSharing` was extracted to prevent (\"a\n * stale value can never leak in through a spread\", `security-plugin.ts`).\n *\n * The leak is not hypothetical and does not require the consumer to be careless:\n * plugin-security only OVERWRITES `__readScope` when it actually resolves\n * permission sets for the new object (`if (permissionSets.length > 0)`), so a\n * stale depth SURVIVES into a question it was never resolved for whenever that\n * branch does not fire. A REST request that touched another object before\n * reaching, say, `/reports/:id/run` hands over an envelope the middleware has\n * already written into.\n *\n * Dropping is safe in the one direction that matters: the middleware re-stamps\n * the depth for THIS object when it resolves any set, so the only thing dropping\n * can do is leave the sharing owner-match at its narrowest (`own`) — the safe\n * direction.\n *\n * ## Why by PREFIX and never by a name list\n *\n * The `__` convention is what marks a key as belonging to the operation in\n * flight. A hand-maintained list of the six names above would go stale the day\n * the middleware stamps a seventh, and it would go stale SILENTLY — a forwarded\n * key nobody remembered to add reads exactly like a key that was meant to be\n * forwarded. The prefix is the contract; the names are its current membership.\n *\n * ⛔ The corollary, for whoever changes the middleware: a key that is\n * operation-private MUST carry the `__` prefix. Stamping one without it makes it\n * invisible to every consumer at once, and there is no compiler error.\n *\n * ## Known consumers\n *\n * `plugin-audit` (`comment-access-hooks.ts`, #7141), `service-storage`\n * (`attachment-access-hooks.ts`, #7145) and `plugin-reports`\n * (`report-service.ts`, #7204) — each forwarding a caller envelope to a gate or\n * a read that asks about a parent/target object rather than about the object the\n * middleware resolved for. Each of the three grew its own byte-equivalent copy\n * of this file by hand before #7284 gave the rule a home; `operation-private-\n * keys.pin.test.ts` is what now catches a fourth.\n */\n\nimport type { ExecutionContext } from '@objectstack/spec/kernel';\n\n/**\n * The prefix marking a key as private to the operation plugin-security has in\n * flight. See this module's header for why the convention is a prefix and not a\n * list of names.\n */\nexport const OPERATION_PRIVATE_KEY_PREFIX = '__';\n\n/**\n * The caller's execution envelope, minus the operation-private keys.\n *\n * A FRESH object every time, and that is load-bearing in BOTH directions:\n *\n * - outbound — a callee that stamps its own `__writeScope` onto what it\n * receives (which is exactly what plugin-security does before it calls the\n * sharing service) can never write back into the operation context the caller\n * was handed;\n * - inbound — the engine's middleware stamps a depth for the object it is about\n * to read onto whatever it is handed, so forwarding a caller's envelope BY\n * REFERENCE would write that depth back into the request context the route\n * goes on using.\n *\n * ⛔ Never `return exec;` on the \"nothing to strip\" path. The copy is the point,\n * not an optimisation to skip when the envelope happens to be clean — the two\n * hazards above are about the callee's future writes, not about the current\n * contents.\n *\n * Note what this deliberately does NOT do: it strips, it never SYNTHESISES a\n * depth for the new object. Absent depth leaves the sharing owner-match at its\n * narrowest (`own`), which is the safe direction and byte-for-byte what the\n * five-field projections these call sites replaced produced.\n *\n * @param exec the caller's envelope, as a bare record\n * @returns a new envelope carrying only the non-operation-private keys\n */\nexport function withoutOperationPrivateKeys(exec: Record<string, unknown>): ExecutionContext {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(exec)) {\n if (key.startsWith(OPERATION_PRIVATE_KEY_PREFIX)) continue;\n out[key] = value;\n }\n return out as ExecutionContext;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Timezone-aware calendar utilities (ADR-0053 Phase 2).\n *\n * The one primitive everything else builds on is {@link calendarPartsInTz}:\n * the year/month/day an instant falls on *as seen in a reference timezone*.\n * It uses `Intl.DateTimeFormat().formatToParts()` so DST transitions are\n * handled by the platform's tz database — never hand-rolled offset math, which\n * is the classic source of off-by-one-hour bucket errors.\n *\n * This lives in `@objectstack/core` (not `@objectstack/formula`) because both\n * the ObjectQL aggregation engine and the analytics service need it and both\n * already depend on core, whereas neither depends on formula's public surface.\n * (`@objectstack/formula` keeps its own private copy for `today()`/`daysFromNow`\n * to avoid a layering dependency on core.)\n */\n\n/** Calendar-day parts in a reference timezone. `month` is 1-12. */\nexport interface CalendarParts {\n year: number;\n month: number;\n day: number;\n}\n\n/**\n * A wall clock as a human writes it — calendar day plus an optional\n * time-of-day, with **no zone attached**. `2026-08-01 06:00:00` is this shape:\n * it names a reading on a clock, and only a reference timezone turns it into an\n * instant. Omitted time components default to 0, so {@link CalendarParts} alone\n * is midnight.\n */\nexport interface WallClockParts extends CalendarParts {\n /** 0-23. */\n hour?: number;\n minute?: number;\n second?: number;\n millisecond?: number;\n}\n\n/**\n * The year/month/day an instant falls on in `tz`. Throws if `tz` is not a\n * valid IANA zone (callers treat that as a fall-through to UTC).\n */\nexport function calendarPartsInTz(d: Date, tz: string): CalendarParts {\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone: tz,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n }).formatToParts(d);\n const get = (t: string) => Number(parts.find((p) => p.type === t)?.value);\n return { year: get('year'), month: get('month'), day: get('day') };\n}\n\n/**\n * The calendar-day parts of an instant, in `tz` when it's a real non-UTC zone,\n * otherwise in UTC. Never throws: an unset, `'UTC'`, or invalid zone falls back\n * to the UTC calendar day. This is the safe entry point for bucketing code that\n * must degrade to the historical UTC behavior rather than error.\n */\nexport function calendarPartsInTzOrUtc(d: Date, tz?: string): CalendarParts {\n if (tz && tz !== 'UTC') {\n try {\n return calendarPartsInTz(d, tz);\n } catch {\n // unknown zone → fall through to UTC\n }\n }\n return {\n year: d.getUTCFullYear(),\n month: d.getUTCMonth() + 1,\n day: d.getUTCDate(),\n };\n}\n\n/**\n * The UTC instant (epoch ms) at which calendar day `ymd` (`YYYY-MM-DD`) *begins*\n * in reference timezone `tz` — i.e. local **midnight** of that day rendered as a\n * UTC instant. The inverse direction of {@link calendarPartsInTz}.\n *\n * DST-safe: the zone offset is read from the platform tz database via\n * `Intl.DateTimeFormat` (never hand-computed), and a two-pass resolution settles\n * the rare case where the offset differs side-to-side of the target instant. An\n * unset, `'UTC'`, invalid, or unparseable input returns plain UTC midnight.\n *\n * Used by date-bucket drill ranges (#1752): a `datetime` field buckets on the\n * reference-tz calendar, so its bucket boundary is that tz's midnight instant.\n *\n * Date-only by contract: a `YYYY-MM-DD HH:mm:ss` argument is still `NaN` here.\n * Callers holding a wall clock with a time-of-day want\n * {@link zonedWallClockToUtcMs}, which this delegates its zone arithmetic to.\n */\nexport function zonedDateStartToUtcMs(ymd: string, tz?: string): number {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(ymd);\n if (!m) return NaN;\n return zonedWallClockToUtcMs(\n { year: Number(m[1]), month: Number(m[2]), day: Number(m[3]) },\n tz,\n );\n}\n\n/**\n * The UTC instant (epoch ms) at which a **wall clock** reading happens in\n * reference timezone `tz` — the general inverse of {@link calendarPartsInTz},\n * of which {@link zonedDateStartToUtcMs} is the midnight special case.\n *\n * `2026-08-01 06:00:00` in `Asia/Shanghai` is `2026-07-31T22:00:00Z`: a\n * different day, month and quarter. That gap is why this direction exists as a\n * shared primitive at all — bulk import (#8485) reads offset-free spreadsheet\n * cells, which are wall clocks and nothing more, and `new Date(cell)` resolves\n * them against the **process** `TZ`, i.e. a host setting rather than the\n * tenant's configured zone.\n *\n * DST-safe: the zone offset is read from the platform tz database via\n * `Intl.DateTimeFormat` (never hand-computed), and a two-pass resolution settles\n * the case where the offset differs side-to-side of the target instant. Two\n * wall clocks are not a bijection with instants, and this function resolves\n * both degenerate cases to the **earlier candidate instant** — in both, the\n * final pass reads the offset on the DST side of the transition (measured, not\n * merely intended — `datetime.test.ts` pins both):\n * - a clock reading the zone **skips** (spring forward: `02:30` on a US\n * spring-forward day) settles on the *post*-transition offset (EDT, −04),\n * which places the instant just **before** the gap: it reads `01:30` EST\n * locally, not `03:30` EDT. Note this is the opposite of Temporal's\n * `'compatible'` disambiguation, which pushes a gap reading forward;\n * - a clock reading that happens **twice** (fall back: `01:30` on a US\n * fall-back day) resolves to its first occurrence, the one still on the\n * pre-transition DST offset (EDT, −04).\n *\n * A spreadsheet cell naming a wall clock that its zone never had is ambiguous\n * by construction; what matters for an import is that the answer is\n * deterministic and host-independent, which both branches above are.\n *\n * FALLBACK — an unset, `'UTC'`, or invalid `tz` reads the wall clock **as UTC**,\n * never as the process-local clock. Every caller of this family already degrades\n * that way ({@link zonedDateStartToUtcMs}, and the export renderer's cell path),\n * and a host `TZ` fallback would reintroduce exactly the deployment-dependent\n * instant this primitive exists to remove. A parts object that produces an\n * invalid date (`NaN` components) returns `NaN`, as `Date.UTC` does.\n */\nexport function zonedWallClockToUtcMs(parts: WallClockParts, tz?: string): number {\n const wallAsUtc = Date.UTC(\n parts.year,\n parts.month - 1,\n parts.day,\n parts.hour ?? 0,\n parts.minute ?? 0,\n parts.second ?? 0,\n parts.millisecond ?? 0,\n );\n if (!tz || tz === 'UTC' || Number.isNaN(wallAsUtc)) return wallAsUtc;\n try {\n // The tz offset (local − UTC, in ms) at instant `t`: read t's wall clock in\n // `tz`, re-interpret those parts as UTC, and subtract t. `formatToParts`\n // resolves no finer than a second, so `t` is truncated to a whole second\n // first — otherwise a sub-second wall clock leaks its milliseconds into the\n // \"offset\" and shifts the answer by them (a real defect while generalising\n // this from the date-only form, where ms was always 0).\n const offsetAt = (t: number): number => {\n const whole = Math.floor(t / 1000) * 1000;\n const p = new Intl.DateTimeFormat('en-US', {\n timeZone: tz,\n hourCycle: 'h23',\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n }).formatToParts(new Date(whole));\n const g = (k: string) => Number(p.find((x) => x.type === k)?.value);\n return (\n Date.UTC(g('year'), g('month') - 1, g('day'), g('hour'), g('minute'), g('second')) - whole\n );\n };\n // Want U such that localParts(U) == the wall clock, i.e. U = wallAsUtc − offset(U).\n // Iterate from the zero-offset guess; converges in ≤2 steps off a DST edge.\n const off1 = offsetAt(wallAsUtc - offsetAt(wallAsUtc));\n return wallAsUtc - off1;\n } catch {\n return wallAsUtc; // unknown zone → the wall clock read as UTC\n }\n}\n\n/**\n * Calendar-day bound semantics (ADR-0053 D-D) now live in `@objectstack/spec`,\n * beside the date-macro vocabulary they give meaning to — the fifth consumer\n * (`@objectstack/formula`'s RLS write-side `check` evaluator) cannot depend on\n * this package, and a second copy of the rule is exactly the divergence #3777\n * catalogued.\n *\n * Re-exported here so the published `@objectstack/core` surface is unchanged\n * for the drivers and analytics strategies that already import it from here.\n */\nexport { nextUtcCalendarDay, utcInstantMs } from '@objectstack/spec/data';\n\n/**\n * Granularity of a canonical date-bucket key. Mirrors `@objectstack/spec`'s\n * `DateGranularity` enum but kept as a local literal union so this low-level\n * package needs no dependency on spec.\n */\nexport type BucketGranularity = 'day' | 'week' | 'month' | 'quarter' | 'year';\n\n/**\n * ISO-8601 week label (Mon-start weeks, week 1 = the week of the first\n * Thursday) of a UTC calendar day. The forward-direction companion used to\n * *validate* a reconstructed week boundary; it mirrors the week branch of\n * `@objectstack/objectql`'s `bucketDateValue` (kept in lockstep by the\n * round-trip parity test in objectql).\n */\nfunction isoWeekLabelUtc(d: Date): string {\n const target = new Date(d.getTime());\n const dayNum = (target.getUTCDay() + 6) % 7; // Mon=0..Sun=6\n target.setUTCDate(target.getUTCDate() - dayNum + 3); // shift to that week's Thursday\n const firstThursday = new Date(Date.UTC(target.getUTCFullYear(), 0, 4));\n const weekNo =\n 1 +\n Math.round(\n ((target.getTime() - firstThursday.getTime()) / 86400000 -\n 3 +\n ((firstThursday.getUTCDay() + 6) % 7)) /\n 7,\n );\n return `${target.getUTCFullYear()}-W${String(weekNo).padStart(2, '0')}`;\n}\n\n/**\n * The half-open calendar span `[start, end)` of a canonical date-bucket KEY,\n * as `YYYY-MM-DD` strings (`start` inclusive, `end` exclusive — the next\n * bucket's first day).\n *\n * The input MUST be the canonical key produced by `bucketDateValue` /\n * `buildDateBucketExpr` (`2026`, `2026-Q2`, `2026-06`, `2026-06-15`,\n * `2026-W23`) — NEVER a localized / humanized display label. The span is pure,\n * timezone-naive calendar arithmetic; a caller that needs instant bounds for a\n * `datetime` field in a reference timezone layers that on top (and, per\n * ADR-0053, a `date` field compares against these `YYYY-MM-DD` bounds directly).\n *\n * Returns `null` for the empty bucket, an unparseable key, or a key that is\n * shape-valid but out of range (e.g. `2026-13`, a `-W53` in a 52-week year,\n * `2026-02-30`). Callers drop the range and fall back to an unscoped (superset)\n * drill rather than emit a wrong bound.\n *\n * `key` admits `null` because that IS the empty bucket's key on both aggregation\n * paths (#3839); callers pass a grouped row's dimension value straight through\n * rather than casting a lie.\n */\nexport function bucketKeyToCalendarRange(\n key: string | null | undefined,\n granularity: BucketGranularity,\n): { start: string; end: string } | null {\n if (typeof key !== 'string' || key.length === 0) return null;\n const fmt = (dt: Date) =>\n `${String(dt.getUTCFullYear()).padStart(4, '0')}-${String(dt.getUTCMonth() + 1).padStart(\n 2,\n '0',\n )}-${String(dt.getUTCDate()).padStart(2, '0')}`;\n\n switch (granularity) {\n case 'year': {\n const m = /^(\\d{4})$/.exec(key);\n if (!m) return null;\n const y = Number(m[1]);\n return { start: fmt(new Date(Date.UTC(y, 0, 1))), end: fmt(new Date(Date.UTC(y + 1, 0, 1))) };\n }\n case 'quarter': {\n const m = /^(\\d{4})-Q([1-4])$/.exec(key);\n if (!m) return null;\n const y = Number(m[1]);\n const startMonth = (Number(m[2]) - 1) * 3; // Q1→0, Q2→3, Q3→6, Q4→9\n return {\n start: fmt(new Date(Date.UTC(y, startMonth, 1))),\n end: fmt(new Date(Date.UTC(y, startMonth + 3, 1))), // Date.UTC rolls Q4 into next year\n };\n }\n case 'month': {\n const m = /^(\\d{4})-(\\d{2})$/.exec(key);\n if (!m) return null;\n const mo = Number(m[2]);\n if (mo < 1 || mo > 12) return null;\n const y = Number(m[1]);\n return {\n start: fmt(new Date(Date.UTC(y, mo - 1, 1))),\n end: fmt(new Date(Date.UTC(y, mo, 1))),\n };\n }\n case 'day': {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(key);\n if (!m) return null;\n const y = Number(m[1]);\n const mo = Number(m[2]);\n const d = Number(m[3]);\n const start = new Date(Date.UTC(y, mo - 1, d));\n if (fmt(start) !== key) return null; // reject an impossible day that rolled over\n return { start: key, end: fmt(new Date(Date.UTC(y, mo - 1, d + 1))) };\n }\n case 'week': {\n const m = /^(\\d{4})-W(\\d{2})$/.exec(key);\n if (!m) return null;\n const isoYear = Number(m[1]);\n const week = Number(m[2]);\n if (week < 1 || week > 53) return null;\n // Monday of ISO week 1 is the Monday on/before Jan 4; add (week-1) weeks.\n const jan4 = new Date(Date.UTC(isoYear, 0, 4));\n const jan4Dow = (jan4.getUTCDay() + 6) % 7; // Mon=0..Sun=6\n const start = new Date(jan4.getTime());\n start.setUTCDate(jan4.getUTCDate() - jan4Dow + (week - 1) * 7);\n if (isoWeekLabelUtc(start) !== key) return null; // reject -W53 overflow etc.\n const end = new Date(start.getTime());\n end.setUTCDate(start.getUTCDate() + 7);\n return { start: fmt(start), end: fmt(end) };\n }\n default:\n return null;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * `bulkWrite` — the shared batched-write helper used by BOTH the seed loader\n * (`@objectstack/metadata-protocol`) and the data-import runner\n * (`@objectstack/rest`), so neither reimplements batching, transient-error\n * retry, or per-row degradation. See framework#2678.\n *\n * ObjectQL's engine already does the efficient thing when handed an ARRAY —\n * one `driver.bulkCreate` round-trip plus parent-deduplicated summary\n * recompute (`engine.insert(object, rows[])`) — but seed/import fed it one\n * record at a time, so neither got the benefit. This module re-chunks rows\n * into batches and drives them through a caller-supplied batch-write\n * function, adding:\n *\n * - transient-error retry (network blip / timeout) with exponential\n * backoff, so a dropped connection doesn't silently drop the row (the\n * 2026-07-06 HotCRM incident: a turso `fetch failed` mid-seed dropped rows\n * silently because nothing retried);\n * - per-row degradation when a batch fails for a non-transient (logical /\n * validation) reason, so one bad row can't fail the other N-1 — needed\n * because `driver.bulkCreate` is a single multi-row statement/`Promise.all`\n * on every driver in this repo (sql, memory, mongodb): one bad row fails\n * the whole call;\n * - a stable per-row result keyed by the row's original index, so callers\n * can reassemble output in input order even though rows are processed in\n * batches (and a batch's flush may be interleaved with other, immediate,\n * per-row work such as updates).\n *\n * Delivery semantics: **at-least-once**. Transient retry and per-row\n * degradation both RE-RUN a write whose outcome was unknown — e.g. a turso\n * `fetch failed` that arrived *after* the row was already committed\n * (framework#3149), or a result-count mismatch that voids the batch\n * (framework#3151). A caller that needs exactly-once must make its\n * `writeBatch`/`writeOne` idempotent; both receive an `attempt` counter for\n * exactly this — see the natural-key recheck the seed loader and import\n * runner perform on `attempt > 1`. `writeBatch` MUST also resolve exactly one\n * record per input row, in input order: a short / long / non-array return is\n * rejected as a failed batch (framework#3151), never silently backfilled.\n */\n\nexport interface BulkWriteRowResult<TRecord = any> {\n /** Index into the original `rows` array passed to {@link bulkWrite}. */\n index: number;\n ok: boolean;\n record?: TRecord;\n error?: unknown;\n}\n\nexport interface RetryOptions {\n /** Max attempts for one write (batch or single-row), including the first. Default 3. */\n maxRetries?: number;\n /** Base backoff in ms; doubled each retry, plus jitter. Default 200. */\n backoffBaseMs?: number;\n /** Classifies an error as transient (worth retrying) vs logical (the row/batch is just bad). */\n isTransientError?: (err: unknown) => boolean;\n /** Injectable sleep, for deterministic tests. */\n sleep?: (ms: number) => Promise<void>;\n}\n\nexport interface BulkWriteOptions<TRow, TRecord = any> extends RetryOptions {\n /** Rows per batch. Default 200 (framework#2678 suggests 100-500). */\n batchSize?: number;\n /**\n * Write one batch. MUST resolve to one record per input row, in the SAME\n * order as `batch` — {@link bulkWrite} correlates `records[i]` back to\n * `batch[i]` positionally (this is how every `bulkCreate` implementation in\n * this repo already behaves: sql's single `INSERT ... VALUES (...), (...)\n * RETURNING *`, memory's `Promise.all`, mongodb's ordered `insertMany`).\n *\n * `ctx.attempt` is the 1-based attempt number. `attempt > 1` means a prior\n * attempt's outcome is UNKNOWN (a transient blip that may have landed after\n * commit) — an exactly-once caller should recheck by natural key and skip\n * rows already present before re-writing (framework#3149).\n */\n writeBatch: (batch: TRow[], ctx: { attempt: number }) => Promise<TRecord[]>;\n /**\n * Write a single row — used only to degrade a failed batch. `ctx.attempt`\n * carries the same recheck signal as {@link writeBatch}.\n */\n writeOne: (row: TRow, ctx: { attempt: number }) => Promise<TRecord>;\n /**\n * Partial-success batch write (framework#3172). When provided it is used\n * INSTEAD of {@link writeBatch}: it must resolve one outcome per input row,\n * in input order — `{ ok: true, record }` for written rows, `{ ok: false,\n * error }` for rows that failed individually (e.g. validation). Per-row\n * failures are final verdicts: bulkWrite records them as-is and does NOT\n * degrade to `writeOne` for them — that is the whole point (a degradation\n * re-run would re-fire beforeInsert hooks on the good rows). Only a THROWN\n * error (a transient infra failure, a result-count mismatch) falls back to\n * the per-row `writeOne` degradation, exactly like `writeBatch`.\n */\n writeBatchPartial?: (\n batch: TRow[],\n ctx: { attempt: number },\n ) => Promise<Array<{ ok: boolean; record?: TRecord; error?: unknown }>>;\n}\n\nconst DEFAULT_BATCH_SIZE = 200;\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_BACKOFF_BASE_MS = 200;\n\n/**\n * Transient-error signatures shared by common HTTP/TCP-backed drivers\n * (turso/libsql's fetch-based transport included). Deliberately excludes\n * anything that looks like a validation/constraint error — those must NOT be\n * retried, only degraded to per-row.\n */\nconst TRANSIENT_PATTERNS: RegExp[] = [\n /fetch failed/i,\n /network/i,\n /timed?\\s*out/i,\n /timeout/i,\n /socket hang ?up/i,\n /connection.*(closed|reset|refused|terminated|aborted)/i,\n /\\b(502|503|504)\\b/,\n /server.*unavailable/i,\n /too many connections/i,\n];\n\nconst TRANSIENT_CODES = /^(ECONNRESET|ECONNREFUSED|ECONNABORTED|EPIPE|EAI_AGAIN|ETIMEDOUT|EHOSTUNREACH|ENETUNREACH|ENOTFOUND)$/i;\n\n/**\n * Validation / constraint / schema signatures that are DEFINITIVELY logical,\n * never worth retrying. Checked before {@link TRANSIENT_PATTERNS} so a message\n * that happens to mention both (e.g. `CHECK constraint failed: network_zone`,\n * `column network_id is not allowed`) is classified as logical rather than\n * burning retries on a row that will fail identically every time (framework\n * #3150).\n */\nconst NON_TRANSIENT_PATTERNS: RegExp[] = [\n /validation/i,\n /constraint/i,\n /\\brequired\\b/i,\n /\\bunique\\b/i,\n /duplicate/i,\n /not[\\s_-]*null/i,\n /invalid/i,\n /not allowed/i,\n /out of range/i,\n];\n\nexport function defaultIsTransientError(err: unknown): boolean {\n const message = (err as { message?: unknown } | null)?.message;\n const text = typeof message === 'string' ? message : String(err ?? '');\n // A definitive logical signature wins even if a transient word also appears.\n if (NON_TRANSIENT_PATTERNS.some((re) => re.test(text))) return false;\n const code = (err as { code?: unknown } | null)?.code;\n if (typeof code === 'string' && TRANSIENT_CODES.test(code)) return true;\n return TRANSIENT_PATTERNS.some((re) => re.test(text));\n}\n\nconst defaultSleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\ninterface ResolvedRetryOptions {\n maxRetries: number;\n backoffBaseMs: number;\n isTransientError: (err: unknown) => boolean;\n sleep: (ms: number) => Promise<void>;\n}\n\nasync function withRetry<T>(fn: (attempt: number) => Promise<T>, opts: ResolvedRetryOptions): Promise<T> {\n let lastError: unknown;\n for (let attempt = 1; attempt <= opts.maxRetries; attempt++) {\n try {\n return await fn(attempt);\n } catch (err) {\n lastError = err;\n if (attempt >= opts.maxRetries || !opts.isTransientError(err)) throw err;\n const jitter = Math.floor(Math.random() * 50);\n await opts.sleep(opts.backoffBaseMs * 2 ** (attempt - 1) + jitter);\n }\n }\n // Unreachable — the loop above always returns or throws — but keeps TS's\n // control-flow analysis happy about a guaranteed return type.\n throw lastError;\n}\n\n/**\n * Retry a single write (e.g. an `engine.update()` call the seed loader or\n * import runner makes outside the batched-insert path) with the same\n * transient-error backoff {@link bulkWrite} applies to batches — so a\n * network blip doesn't drop an update the way it used to drop an insert.\n */\nexport async function withTransientRetry<T>(fn: (attempt: number) => Promise<T>, opts: RetryOptions = {}): Promise<T> {\n return withRetry(fn, {\n maxRetries: Math.max(1, opts.maxRetries ?? DEFAULT_MAX_RETRIES),\n backoffBaseMs: opts.backoffBaseMs ?? DEFAULT_BACKOFF_BASE_MS,\n isTransientError: opts.isTransientError ?? defaultIsTransientError,\n sleep: opts.sleep ?? defaultSleep,\n });\n}\n\n/**\n * Write `rows` through `opts.writeBatch` in chunks of `opts.batchSize`,\n * retrying a whole-batch transient failure with backoff, and degrading to\n * per-row `opts.writeOne` calls (each itself retried) when a batch fails for\n * a non-transient reason — so one bad row can't drop the rest of the batch.\n *\n * Returns one {@link BulkWriteRowResult} per input row, indexed to match\n * `rows`' original order.\n */\nexport async function bulkWrite<TRow, TRecord = any>(\n rows: TRow[],\n opts: BulkWriteOptions<TRow, TRecord>,\n): Promise<BulkWriteRowResult<TRecord>[]> {\n const batchSize = Math.max(1, opts.batchSize ?? DEFAULT_BATCH_SIZE);\n const retryOpts: ResolvedRetryOptions = {\n maxRetries: Math.max(1, opts.maxRetries ?? DEFAULT_MAX_RETRIES),\n backoffBaseMs: opts.backoffBaseMs ?? DEFAULT_BACKOFF_BASE_MS,\n isTransientError: opts.isTransientError ?? defaultIsTransientError,\n sleep: opts.sleep ?? defaultSleep,\n };\n\n const results: BulkWriteRowResult<TRecord>[] = new Array(rows.length);\n\n for (let start = 0; start < rows.length; start += batchSize) {\n const batch = rows.slice(start, start + batchSize);\n try {\n // Partial-success path (framework#3172): one call yields a final per-row\n // verdict, so a row that fails validation never triggers the whole-batch\n // degradation that re-runs beforeInsert hooks on its siblings.\n if (opts.writeBatchPartial) {\n const outcomes = await withRetry((attempt) => opts.writeBatchPartial!(batch, { attempt }), retryOpts);\n if (!Array.isArray(outcomes) || outcomes.length !== batch.length) {\n throw Object.assign(\n new Error(\n `bulkWrite: writeBatchPartial returned ${\n Array.isArray(outcomes) ? `${outcomes.length} outcome(s)` : String(typeof outcomes)\n } for a ${batch.length}-row batch — treating batch as failed`,\n ),\n { code: 'ERR_BULK_RESULT_MISMATCH' },\n );\n }\n for (let i = 0; i < batch.length; i++) {\n const o = outcomes[i];\n results[start + i] = o.ok\n ? { index: start + i, ok: true, record: o.record }\n : { index: start + i, ok: false, error: o.error };\n }\n continue;\n }\n const records = await withRetry((attempt) => opts.writeBatch(batch, { attempt }), retryOpts);\n // Contract guard (framework#3151): `writeBatch` must resolve one record\n // per input row. A short / long / non-array return breaks the positional\n // correlation below, so backfilling it would report phantom successes\n // (`record: undefined`) or drop records. Treat the whole batch as failed\n // and fall through to per-row degradation (each row re-attempted via\n // `writeOne`, which under an idempotent caller rechecks before writing).\n // The message deliberately avoids any transient signature so this never\n // reads as a retryable blip — and it is thrown *outside* `withRetry`, so\n // the batch is not retried on it.\n if (!Array.isArray(records) || records.length !== batch.length) {\n throw Object.assign(\n new Error(\n `bulkWrite: writeBatch returned ${\n Array.isArray(records) ? `${records.length} record(s)` : String(typeof records)\n } for a ${batch.length}-row batch — treating batch as failed`,\n ),\n { code: 'ERR_BULK_RESULT_MISMATCH' },\n );\n }\n for (let i = 0; i < batch.length; i++) {\n results[start + i] = { index: start + i, ok: true, record: records[i] };\n }\n } catch (batchErr) {\n // A single-row \"batch\" already IS the per-row attempt — its failure\n // (after transient retry) is the row's final outcome; calling\n // `writeOne` again would just repeat the identical work.\n if (batch.length === 1) {\n results[start] = { index: start, ok: false, error: batchErr };\n continue;\n }\n // The batch failed even after transient retry, or failed for a logical\n // reason retry wouldn't fix. Degrade to per-row so one bad row can't\n // fail the other rows in this batch. Each row still gets its own\n // transient retry — the batch-level failure doesn't tell us which row\n // (if any) was actually the transient one.\n for (let i = 0; i < batch.length; i++) {\n const idx = start + i;\n try {\n const record = await withRetry((attempt) => opts.writeOne(batch[i], { attempt }), retryOpts);\n results[idx] = { index: idx, ok: true, record };\n } catch (err) {\n results[idx] = { index: idx, ok: false, error: err };\n }\n }\n }\n }\n\n return results;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * [#7823 / #8497] The write-response half of the `internal: true` guarantee —\n * THE single helper every write mouth that returns a body to an external\n * caller passes its record(s) through.\n *\n * ## The contract\n *\n * A field declared `internal: true` is *never returned on the generic data\n * path* (#7728). The READ half lives in the engine (`omitInternalFields` runs\n * on every find/findOne result). The WRITE-RESPONSE half lives HERE.\n *\n * ## Why the ingress and not the engine (the measured history, #7823)\n *\n * The first shape stripped `internal` fields inside the engine's insert and\n * by-id-update paths. That conflated two different guarantees:\n *\n * - \"never returned on the generic data path\" — the flag's sentence, about\n * what an EXTERNAL caller receives; and\n * - \"never returned to the engine-level caller that performed the write\" —\n * which no ruling ever asked for, and which is FALSE for credential mint:\n * better-auth's `createWithHooks` reads the minted `sys_session` row back\n * off the insert result, so the engine-side strip broke `signIn`/`signUp`\n * outright (measured: `verify signIn: no token in response`).\n *\n * Plain removal of the engine limbs was ALSO measured wrong: the by-id-update\n * strip was the sole closure of #7728's fourth surface — with it neutralised,\n * `PATCH /data/sys_api_key/{id}` answered 200 with the stored 64-hex `key`\n * hash in the body. Both measurements are satisfiable at exactly one boundary:\n * the mouth that builds the external 201/200 body. Engine write results keep\n * the stored row whole (mint works); every external write response is stripped\n * through this helper (the hash never leaves); the read path is untouched.\n *\n * ## Why this module sits in `@objectstack/core` (#8497)\n *\n * It shipped inside `@objectstack/metadata-protocol`, next to the protocol\n * class that was then its only caller. That placement encoded an assumption\n * the surface does not honour: **the generic write mouths are not all on the\n * protocol class.** Two transports reach the engine directly —\n *\n * - `@objectstack/rest` (`rest-server.ts`, the cross-object `POST /batch`\n * update arm's direct `ql.update`), and\n * - `@objectstack/mcp` (`stdio-data-bridge.ts`, whose `create` handed the\n * engine's insert result straight back to the MCP client — a MEASURED leak\n * of the flagged column, found by widening this guard's scope in #8497),\n *\n * — and neither package depends on `@objectstack/metadata-protocol`. The old\n * home therefore forced each new mouth to choose between a duck-typed reach\n * through a protocol instance (what `rest` does) and a private restatement of\n * the rule (a third copy of a security-relevant predicate). `@objectstack/core`\n * is the floor all three already depend on, and it already hosts exactly this\n * class of shared write-path helper (`bulk-write.ts`, used by both\n * `metadata-protocol` and `rest` so neither reimplements batching). One helper,\n * reachable from every mouth, is the whole point of the flag being structural.\n *\n * `@objectstack/metadata-protocol` re-exports both functions unchanged, so its\n * public API is byte-identical across the move.\n *\n * ## The residual risk, and what gates it\n *\n * Response-body policy at the mouth means a FUTURE write mouth that forgets\n * this helper leaks silently. Three tripwires hold the property, each an\n * enumeration no author can dodge by adding code without touching it:\n *\n * - `protocol.write-response-internal-fields.tripwire.test.ts`\n * (`metadata-protocol`) walks the protocol class's prototype for `*Data`\n * faces;\n * - `rest-write-response-internal-fields.tripwire.test.ts` (`rest`) walks\n * `RestServer.getRoutes()` for HTTP write routes;\n * - `mcp-write-response-internal-fields.tripwire.test.ts` (`mcp`) walks the\n * `McpDataBridge` write faces.\n *\n * Together they assert the PROPERTY — \"no response body an external caller\n * receives from a write carries an `internal: true` value\" — rather than the\n * shape of any one class. Adding a write mouth? Route its response records\n * through this helper and register it with the tripwire that enumerates its\n * surface.\n *\n * ## Semantics\n *\n * Mirrors the engine's `collectInternalReadFields` rule exactly — a field\n * participates iff its declaration carries `internal === true` (strict\n * boolean; truthy strings and numbers do not count, same as the engine).\n * `@objectstack/core` cannot import that collector (`@objectstack/objectql`\n * sits above this package), so the rule is restated here in full;\n * `internal-fields.test.ts` in objectql and the tripwires above pin the same\n * spelling from both sides. OMIT, not mask, for the #7728 reasons: the flag's\n * columns are `required`, so a mask carries zero bits while still shipping a\n * value under a field whose description promises none.\n *\n * Deletion is IN PLACE and idempotent: records that already lack the field\n * (a re-stripped read result, a fake engine that never returned it) pass\n * through unchanged, and non-record values (`null`, an affected-row count, a\n * driver's boolean delete verdict) are skipped rather than judged.\n */\n\n/** Minimal view of an object schema this module reads — the field map only. */\ninterface SchemaWithFields {\n fields?: Record<string, { internal?: unknown } | undefined> | undefined;\n}\n\n/**\n * Collect the names of fields declared `internal: true` on `schema`.\n *\n * Same verdicts as objectql's `collectInternalReadFields` (see the module\n * header for why it is restated rather than imported): strict `=== true`,\n * empty result for a missing/field-less schema.\n */\nexport function collectInternalWriteResponseFields(schema: unknown): string[] {\n const fields = (schema as SchemaWithFields | null | undefined)?.fields;\n if (!fields || typeof fields !== 'object') return [];\n const out: string[] = [];\n for (const [name, def] of Object.entries(fields)) {\n if (def && def.internal === true) out.push(name);\n }\n return out;\n}\n\n/**\n * Drop every `internal: true` field from a write response's record(s), in\n * place. THE single helper every external write mouth goes through — see the\n * module header; the three tripwires enforce the \"every\".\n *\n * @param schema The registered object schema (`engine.registry.getObject(...)`\n * / the protocol's own registry view / `metadataService\n * .getObject(...)`). An unknown object (no schema) strips\n * nothing — the write itself would have been refused upstream\n * by the object-existence gate.\n * @param records A single record, an array of records, or anything a write\n * mouth hands back where a record could sit (`null`, a count, a\n * boolean): non-objects are skipped, arrays are walked.\n */\nexport function omitInternalFieldsFromWriteResponse(schema: unknown, records: unknown): void {\n if (!records) return;\n const internalFields = collectInternalWriteResponseFields(schema);\n if (internalFields.length === 0) return;\n const list = Array.isArray(records) ? records : [records];\n for (const row of list) {\n if (!row || typeof row !== 'object') continue;\n for (const field of internalFields) delete (row as Record<string, unknown>)[field];\n }\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * `runMigrationJournal` — the framework-owned runner for data migrations that\n * are too big, too long, or too multi-step to live in one transaction\n * (ADR-0119 D2, #4617).\n *\n * ## Why this is framework-owned rather than four hand-rolled copies\n *\n * Four migration-class consumers independently converged on the same four\n * moves — dry-run preflight, an undo journal, LIFO compensation, re-entrant\n * forward recovery: ADR-0105 D13 promotion, ADR-0117 D8's ownership backfill,\n * the org lifecycle transitions, and ADR-0119 D10's master-data distribution\n * (#4585). One copy is engineering. Four is platform debt, and the fourth\n * author would have had to rediscover the `chunk_done`-inside-the-transaction\n * subtlety below from scratch — or, far more likely, not rediscover it.\n *\n * ## Why a journal at all, given ADR-0034 gave us transactions\n *\n * ADR-0119 D1 made `engine.transaction()` reachable through the contract, but\n * a transaction cannot be the whole answer here:\n *\n * - a million-row backfill cannot hold one write-lock for its duration;\n * - `driver-memory`'s `beginTransaction` deep-clones the entire database, so\n * \"just wrap the whole thing\" is O(db) per begin;\n * - `ObjectQL.transaction()` binds the DEFAULT driver only, so a migration\n * spanning datasources silently commits part of its work outside it;\n * - a process KILLED — as distinct from a thrown error — defeats in-process\n * rollback entirely, and that is the case operators actually hit.\n *\n * So the unit of atomicity is the CHUNK, and durability across chunks is the\n * journal. Everything else in this file follows from that one sentence.\n *\n * ## The invariant that carries the whole design\n *\n * `chunk_done(i)` is written INSIDE the chunk's own transaction, so\n * `done ⇔ committed` holds by construction rather than by luck.\n * `chunk_started(i)` is written autonomously BEFORE it. A reader who \"tidies\"\n * that asymmetry destroys recovery: it is what gives `started ∧ ¬done` exactly\n * one meaning — **the outcome is unknown** — which is the only state a crash\n * can leave and the only state recovery has to reason about.\n *\n * ## Delivery semantics: at-least-once, idempotency is the caller's job\n *\n * Inherited verbatim from `./bulk-write.ts` rather than re-derived, because a\n * second delivery-semantics story in the same codebase is a second thing to\n * get subtly wrong. Forward and compensate callbacks receive an `attempt`\n * counter; `attempt > 1` means the previous outcome is UNKNOWN — the write may\n * or may not have committed — and the callback must recheck by natural key\n * before re-writing. That is the same contract the seed loader and import\n * runner already honour on `attempt > 1`.\n */\n\nimport { createHash, randomUUID } from 'node:crypto';\nimport type { IObjectQLEngine } from '@objectstack/spec/contracts';\nimport {\n MIGRATION_JOURNAL_OBJECT,\n type MigrationJournalEvent,\n type MigrationJournalKind,\n type MigrationOnCrashPolicy,\n} from '@objectstack/spec/system';\n\n/** Journal writes and recovery reads run as the platform, never as a user. */\nconst SYSTEM_CTX = { isSystem: true } as const;\n\n/** Rows per chunk when a plan does not choose. Matches `bulk-write.ts`. */\nconst DEFAULT_CHUNK_SIZE = 200;\n\n/**\n * Can this runtime actually roll back? — the ADR-0119 D4 gate, shared.\n *\n * Exported from `@objectstack/core` and consumed by\n * `@objectstack/metadata-protocol`'s `batchData` (which depends on core, so\n * the direction is legal) so the two cannot drift. They were the same two-line\n * condition written twice, which is precisely the shape that drifts by one\n * clause and leaves one caller believing it has atomicity it does not have.\n *\n * TWO levels, both necessary. `engine.transaction()` exists but runs the\n * callback with NO transaction and NO rollback when the default driver lacks\n * `beginTransaction` — a declared caveat of the contract member (ADR-0119 D1),\n * and one that turns \"atomic\" back into a lie precisely where it matters. So\n * where the driver registry is inspectable the driver is checked too; where it\n * is not (test doubles), the engine-level probe is all there is.\n *\n * A type predicate, not a bare boolean: every caller's next move is to CALL\n * `transaction`, and on the host surfaces that declare it optionally\n * (`MetadataHostEngine`) a boolean would leave each one re-narrowing by hand —\n * which is the same restatement this helper exists to remove.\n */\nexport function engineCanRollBack<T>(engine: T): engine is T & EngineWithTransaction {\n const e = engine as {\n transaction?: unknown;\n getDefaultDriverName?: () => string | undefined;\n getDriverByName?: (name: string) => unknown;\n } | null | undefined;\n if (typeof e?.transaction !== 'function') return false;\n const defaultDriverName = e.getDefaultDriverName?.();\n const defaultDriver = defaultDriverName ? e.getDriverByName?.(defaultDriverName) : undefined;\n return !defaultDriver || typeof (defaultDriver as { beginTransaction?: unknown }).beginTransaction === 'function';\n}\n\n/**\n * What {@link engineCanRollBack} proves is present.\n *\n * Typed FROM the contract rather than transcribed from it (#5696): a hand-copy\n * mirrors the signature only until the contract moves, and this one had already\n * started to — it predates `opts.require` and the callback's `owned` argument.\n * ADR-0119 D1 blessed exactly this shape for the narrow host surfaces\n * (`transaction?: IObjectQLEngine['transaction']`); a *narrow* surface may stay\n * narrow, but it may not drift from the real signature.\n */\nexport interface EngineWithTransaction {\n transaction: IObjectQLEngine['transaction'];\n}\n\n/** What a forward/compensate callback is told about the chunk it is running. */\nexport interface MigrationChunkContext {\n readonly runId: string;\n /** Run-global chunk index — the LIFO ordering key, stable across a resume. */\n readonly chunkIndex: number;\n /**\n * 1 on the first try. `> 1` means a previous attempt's outcome is UNKNOWN:\n * recheck by natural key before re-writing (see this file's header).\n */\n readonly attempt: number;\n /**\n * The transaction-bound execution context. Thread it to every engine call\n * this callback makes — `engine.insert(obj, row, { context })` — so the\n * write joins the chunk's transaction instead of committing beside it.\n */\n readonly context: unknown;\n}\n\n/** One step of a plan. Steps run in declaration order; each is chunked. */\nexport interface MigrationPlanStep<TRow = unknown> {\n readonly name: string;\n /**\n * Read-only preflight. Throw to refuse the run. Runs for EVERY step before\n * any step writes — a plan that would fail at step 3 must not have written\n * step 1 (ADR-0117 D8's fail-closed enable gate, generalized).\n */\n preflight?(engine: IObjectQLEngine): Promise<void>;\n /** The rows this step processes. Called once, before chunking. */\n load(engine: IObjectQLEngine): Promise<TRow[]>;\n /** Forward work for one chunk. Runs INSIDE the chunk's transaction. */\n forward(rows: TRow[], ctx: MigrationChunkContext, engine: IObjectQLEngine): Promise<void>;\n /**\n * Undo one previously-committed chunk. Runs in its OWN transaction.\n * A step without one makes the plan non-compensable — which the runner\n * refuses up front rather than discovering at the worst possible moment\n * (see {@link runMigrationJournal}'s preflight).\n */\n compensate?(rows: TRow[], ctx: MigrationChunkContext, engine: IObjectQLEngine): Promise<void>;\n}\n\nexport interface MigrationPlan {\n /** Stable plan id. Part of the plan hash; identifies the plan across runs. */\n readonly id: string;\n /** Optional join to `sys_migration.id` when this plan implements a named migration. */\n readonly migrationId?: string;\n readonly steps: ReadonlyArray<MigrationPlanStep<any>>;\n readonly chunkSize?: number;\n /**\n * What a REDISCOVERED (crashed) run should do. Note this governs restart\n * only — an in-run failure always compensates, because the runner is still\n * alive to do it and a half-applied plan is nobody's intent.\n */\n readonly onCrash?: MigrationOnCrashPolicy;\n}\n\n/** One chunk in the run-global chunk plan. */\nexport interface MigrationChunk {\n /** Run-global index, 0-based, stable for a given plan hash. */\n readonly index: number;\n readonly stepIndex: number;\n readonly stepName: string;\n readonly offset: number;\n readonly length: number;\n}\n\nexport interface MigrationRunResult {\n readonly runId: string;\n /**\n * `completed` — every chunk committed.\n * `compensated` — a chunk failed and every committed chunk was undone.\n * `failed` — a chunk failed AND compensation could not finish. The database\n * is in a partial state that needs a human; the journal says exactly where.\n */\n readonly status: 'completed' | 'compensated' | 'failed';\n readonly chunksTotal: number;\n readonly chunksCommitted: number;\n readonly chunksCompensated: number;\n readonly planHash: string;\n /** The failure that ended a non-`completed` run. */\n readonly error?: unknown;\n}\n\n/**\n * Where a resume finds the plan it has to re-run (#4617).\n *\n * A journal cannot hold a plan. `forward` and `compensate` are FUNCTIONS, and\n * the rows a chunk covers are produced by `load()` against the live database —\n * none of it survives a process boundary, which is why the journal records the\n * plan HASH rather than the plan. So recovery needs the plan handed back to it\n * by whoever owns the code, and that is what this registry is: the seam between\n * \"the journal knows a run stopped at chunk 7\" and \"something in this process\n * knows what chunk 7 was supposed to do\".\n *\n * Registered as the `migration-plans` kernel service. An interrupted run whose\n * plan no loaded plugin registers is REPORTED, never silently skipped — the\n * operator is told which plan id is missing, because \"nothing to resume\" and\n * \"the code that owns this run is not loaded\" are different facts and only one\n * of them is safe to ignore.\n */\nexport interface MigrationPlanProvider {\n register(plan: MigrationPlan): void;\n get(planId: string): MigrationPlan | undefined;\n list(): MigrationPlan[];\n}\n\n/** The default {@link MigrationPlanProvider}. Last registration for an id wins. */\nexport class MigrationPlanRegistry implements MigrationPlanProvider {\n private readonly plans = new Map<string, MigrationPlan>();\n\n register(plan: MigrationPlan): void {\n this.plans.set(plan.id, plan);\n }\n\n get(planId: string): MigrationPlan | undefined {\n return this.plans.get(planId);\n }\n\n list(): MigrationPlan[] {\n return [...this.plans.values()];\n }\n}\n\n/** A run found by {@link findInterruptedRuns} — started, never concluded. */\nexport interface InterruptedRun {\n readonly runId: string;\n readonly planId: string;\n readonly planHash: string;\n readonly migrationId?: string;\n readonly startedAt?: string;\n /** Chunks whose `chunk_done` is present — known committed. */\n readonly committedChunks: number[];\n /** Chunks with `chunk_started` and no `chunk_done` — outcome UNKNOWN. */\n readonly unknownChunks: number[];\n readonly compensatedChunks: number[];\n}\n\n/** Raised when the runner refuses to start or to resume. Never a partial run. */\nexport class MigrationJournalRefusal extends Error {\n readonly code: string;\n constructor(code: string, message: string) {\n super(message);\n this.name = 'MigrationJournalRefusal';\n this.code = code;\n }\n}\n\n// ── plan shape ────────────────────────────────────────────────────────────\n\n/** Flatten steps × rows into the run-global chunk list. */\nexport function planChunks(\n plan: MigrationPlan,\n rowCounts: readonly number[],\n chunkSize = plan.chunkSize ?? DEFAULT_CHUNK_SIZE,\n): MigrationChunk[] {\n const size = Math.max(1, chunkSize);\n const chunks: MigrationChunk[] = [];\n plan.steps.forEach((step, stepIndex) => {\n const total = rowCounts[stepIndex] ?? 0;\n for (let offset = 0; offset < total; offset += size) {\n chunks.push({\n index: chunks.length,\n stepIndex,\n stepName: step.name,\n offset,\n length: Math.min(size, total - offset),\n });\n }\n });\n return chunks;\n}\n\n/**\n * Hash the plan SHAPE — id, step names, and the chunk boundaries.\n *\n * Resuming a changed plan against an old journal would apply chunk boundaries\n * the journal never described: \"chunk 7 done\" would name a different range of\n * different rows, and the resume would skip work it never did. So the hash\n * covers exactly what a chunk index means, and a mismatch REFUSES.\n */\nexport function hashMigrationPlan(plan: MigrationPlan, chunks: readonly MigrationChunk[]): string {\n const shape = JSON.stringify({\n id: plan.id,\n steps: plan.steps.map((s) => s.name),\n chunks: chunks.map((c) => [c.stepIndex, c.offset, c.length]),\n });\n return createHash('sha256').update(shape, 'utf8').digest('hex').slice(0, 32);\n}\n\n// ── journal I/O ───────────────────────────────────────────────────────────\n\n/**\n * Append one event.\n *\n * `execContext` is the transaction-bound context when the event must share a\n * chunk's fate (`chunk_done`, `compensated`) and undefined when it must NOT\n * (`chunk_started`, and every run-level event). Passing the wrong one is the\n * single most consequential mistake available in this file — see the header.\n */\nasync function appendEvent(\n engine: IObjectQLEngine,\n event: MigrationJournalEvent,\n execContext?: unknown,\n): Promise<void> {\n await engine.insert(\n MIGRATION_JOURNAL_OBJECT,\n { ...event, created_at: event.created_at ?? new Date().toISOString() },\n { context: execContext ?? { ...SYSTEM_CTX } },\n );\n}\n\n/**\n * Every event for a run, ordered by `seq`.\n *\n * Sorted in memory, deliberately. `seq` is the ordering authority (wall-clock\n * stamps tie at coarse resolution and skew), and a run's journal is bounded by\n * its chunk count, so this costs nothing and removes recovery's dependence on\n * driver-side sort behaviour — which is not something a recovery path should\n * be discovering the edges of.\n */\nexport async function readRunJournal(\n engine: IObjectQLEngine,\n runId: string,\n): Promise<MigrationJournalEvent[]> {\n const rows = (await engine.find(\n MIGRATION_JOURNAL_OBJECT,\n { where: { run_id: runId } },\n { context: { ...SYSTEM_CTX } },\n )) as MigrationJournalEvent[];\n return [...(rows ?? [])].sort((a, b) => Number(a.seq) - Number(b.seq));\n}\n\n/** Chunk indices carrying `kind`, as a set. */\nfunction chunkSetOf(events: readonly MigrationJournalEvent[], kind: MigrationJournalKind): Set<number> {\n const out = new Set<number>();\n for (const e of events) {\n if (e.kind === kind && typeof e.chunk_index === 'number') out.add(e.chunk_index);\n }\n return out;\n}\n\n/**\n * Runs that started and never concluded — the boot scanner's input.\n *\n * \"Concluded\" means `run_done` (finished forward) or `run_failed` with every\n * committed chunk compensated (finished backward). Anything else is a run that\n * stopped mid-flight and still owes the operator an answer.\n */\nexport async function findInterruptedRuns(engine: IObjectQLEngine): Promise<InterruptedRun[]> {\n const started = (await engine.find(\n MIGRATION_JOURNAL_OBJECT,\n { where: { kind: 'run_started' } },\n { context: { ...SYSTEM_CTX } },\n )) as MigrationJournalEvent[];\n\n const out: InterruptedRun[] = [];\n for (const start of started ?? []) {\n const events = await readRunJournal(engine, start.run_id);\n if (events.some((e) => e.kind === 'run_done')) continue;\n\n const committed = chunkSetOf(events, 'chunk_done');\n const compensated = chunkSetOf(events, 'compensated');\n const outstanding = [...committed].filter((i) => !compensated.has(i));\n // A failed run whose committed chunks were all undone is settled: it ended\n // backward, on purpose, and its rows prove it.\n if (events.some((e) => e.kind === 'run_failed') && outstanding.length === 0) continue;\n\n const unknown = [...chunkSetOf(events, 'chunk_started')].filter((i) => !committed.has(i));\n let planId = start.run_id;\n try {\n planId = start.detail ? (JSON.parse(start.detail).planId ?? start.run_id) : start.run_id;\n } catch {\n // A malformed detail payload must not hide an interrupted run — the run\n // is still reported, just without its friendly plan id.\n }\n out.push({\n runId: start.run_id,\n planId,\n planHash: start.plan_hash ?? '',\n migrationId: start.migration_id,\n startedAt: start.created_at,\n committedChunks: [...committed].sort((a, b) => a - b),\n unknownChunks: unknown.sort((a, b) => a - b),\n compensatedChunks: [...compensated].sort((a, b) => a - b),\n });\n }\n return out;\n}\n\n// ── the runner ────────────────────────────────────────────────────────────\n\nexport interface RunMigrationJournalOptions {\n /** Supply to resume an existing run; omit to start a new one. */\n readonly runId?: string;\n readonly chunkSize?: number;\n /** Injectable for deterministic tests. */\n readonly now?: () => string;\n}\n\ninterface LoadedPlan {\n readonly chunks: MigrationChunk[];\n readonly planHash: string;\n readonly rowsByStep: unknown[][];\n}\n\n/** Load every step's rows, derive the chunk plan, hash it. */\nasync function loadPlan(\n engine: IObjectQLEngine,\n plan: MigrationPlan,\n chunkSize?: number,\n): Promise<LoadedPlan> {\n const rowsByStep: unknown[][] = [];\n for (const step of plan.steps) rowsByStep.push((await step.load(engine)) ?? []);\n const chunks = planChunks(plan, rowsByStep.map((r) => r.length), chunkSize ?? plan.chunkSize);\n return { chunks, planHash: hashMigrationPlan(plan, chunks), rowsByStep };\n}\n\n/**\n * Run `plan` under the journal, or resume a run left behind by a crash.\n *\n * Refuses (never partially runs) when: the runtime cannot roll back; any\n * step's preflight fails; the plan declares `onCrash: 'compensate'` but some\n * step cannot compensate; or a resume's plan hash disagrees with the journal.\n */\nexport async function runMigrationJournal(\n engine: IObjectQLEngine,\n plan: MigrationPlan,\n options: RunMigrationJournalOptions = {},\n): Promise<MigrationRunResult> {\n const now = options.now ?? (() => new Date().toISOString());\n\n // ── capability gate ───────────────────────────────────────────────────\n // Refuse rather than degrade. A runner whose chunks are not actually\n // atomic writes `chunk_done` rows that mean nothing, and a journal that\n // cannot be trusted is worse than no journal — it will be believed.\n if (!engineCanRollBack(engine)) {\n throw new MigrationJournalRefusal(\n 'NOT_IMPLEMENTED',\n `Migration plan '${plan.id}' requires engine transaction support; this runtime cannot roll back. ` +\n `The journal's chunk_done markers would not mean \"committed\", so the run is refused rather than started.`,\n );\n }\n\n const { chunks, planHash, rowsByStep } = await loadPlan(engine, plan, options.chunkSize);\n\n // ── resume bookkeeping ────────────────────────────────────────────────\n const resuming = Boolean(options.runId);\n const runId = options.runId ?? randomUUID();\n let events: MigrationJournalEvent[] = [];\n let seq = 0;\n let committed = new Set<number>();\n let compensated = new Set<number>();\n const attemptsByChunk = new Map<number, number>();\n\n if (resuming) {\n events = await readRunJournal(engine, runId);\n if (events.length === 0) {\n throw new MigrationJournalRefusal('NO_SUCH_RUN', `No journal rows for run '${runId}'.`);\n }\n const start = events.find((e) => e.kind === 'run_started');\n if (start?.plan_hash && start.plan_hash !== planHash) {\n // The plan changed under a journal that describes the old one. Chunk 7\n // in the journal and chunk 7 in this plan are different rows; resuming\n // would skip work that was never done.\n throw new MigrationJournalRefusal(\n 'PLAN_CHANGED',\n `Refusing to resume run '${runId}': plan hash ${planHash} does not match the journal's ${start.plan_hash}. ` +\n `The chunk boundaries recorded in the journal describe a different plan.`,\n );\n }\n if (events.some((e) => e.kind === 'run_done')) {\n return {\n runId, status: 'completed', chunksTotal: chunks.length,\n chunksCommitted: chunkSetOf(events, 'chunk_done').size,\n chunksCompensated: chunkSetOf(events, 'compensated').size, planHash,\n };\n }\n seq = events.reduce((m, e) => Math.max(m, Number(e.seq) + 1), 0);\n committed = chunkSetOf(events, 'chunk_done');\n compensated = chunkSetOf(events, 'compensated');\n for (const e of events) {\n if (e.kind === 'chunk_started' && typeof e.chunk_index === 'number') {\n attemptsByChunk.set(e.chunk_index, (attemptsByChunk.get(e.chunk_index) ?? 0) + 1);\n }\n }\n }\n\n // ── preflight ─────────────────────────────────────────────────────────\n // Every validator runs before any write, so a plan that would fail at step 3\n // has not written step 1. On a resume this re-runs too: the world moved\n // while the process was dead, and the reason to refuse may have appeared\n // since.\n for (const step of plan.steps) {\n if (!step.preflight) continue;\n try {\n await step.preflight(engine);\n } catch (err) {\n throw new MigrationJournalRefusal(\n 'PREFLIGHT_FAILED',\n `Migration plan '${plan.id}' refused: preflight for step '${step.name}' failed: ${errText(err)}`,\n );\n }\n }\n\n // A plan that says \"undo me on crash\" must be able to. Discovering that it\n // cannot at compensation time means discovering it with rows already\n // written and no way back.\n if (plan.onCrash === 'compensate') {\n const missing = plan.steps.filter((s) => !s.compensate).map((s) => s.name);\n if (missing.length > 0) {\n throw new MigrationJournalRefusal(\n 'NOT_COMPENSABLE',\n `Migration plan '${plan.id}' declares onCrash: 'compensate' but step(s) ${missing.join(', ')} declare no compensate().`,\n );\n }\n }\n\n const rowsOf = (c: MigrationChunk): unknown[] => rowsByStep[c.stepIndex].slice(c.offset, c.offset + c.length);\n const next = (): number => seq++;\n\n if (!resuming) {\n await appendEvent(engine, {\n run_id: runId, seq: next(), kind: 'run_started', plan_hash: planHash,\n migration_id: plan.migrationId, created_at: now(),\n detail: JSON.stringify({\n planId: plan.id,\n onCrash: plan.onCrash ?? 'resume',\n chunks: chunks.map((c) => ({ i: c.index, step: c.stepName, offset: c.offset, length: c.length })),\n }),\n });\n }\n\n // A rediscovered run whose policy is 'compensate' does not go forward at\n // all — it unwinds what it already did and stops.\n if (resuming && plan.onCrash === 'compensate') {\n return await unwind(engine, plan, {\n runId, planHash, chunks, rowsOf, next, now,\n committed, compensated, chunksTotal: chunks.length,\n cause: new Error(`run '${runId}' rediscovered after interruption; plan policy is compensate`),\n });\n }\n\n // ── forward ───────────────────────────────────────────────────────────\n for (const chunk of chunks) {\n if (committed.has(chunk.index)) continue; // already durable — skip, do not redo\n const attempt = (attemptsByChunk.get(chunk.index) ?? 0) + 1;\n attemptsByChunk.set(chunk.index, attempt);\n const step = plan.steps[chunk.stepIndex];\n const rows = rowsOf(chunk);\n\n // Autonomous, BEFORE the transaction: this is what makes an interrupted\n // chunk visible as \"started, outcome unknown\" rather than invisible.\n await appendEvent(engine, {\n run_id: runId, seq: next(), kind: 'chunk_started',\n chunk_index: chunk.index, attempt, migration_id: plan.migrationId, created_at: now(),\n });\n\n try {\n await engine.transaction(async (trxCtx: unknown) => {\n await step.forward(rows, { runId, chunkIndex: chunk.index, attempt, context: trxCtx }, engine);\n // INSIDE the transaction — `done ⇔ committed`, not a race.\n await appendEvent(\n engine,\n {\n run_id: runId, seq: next(), kind: 'chunk_done',\n chunk_index: chunk.index, attempt, migration_id: plan.migrationId, created_at: now(),\n },\n trxCtx,\n );\n }, { ...SYSTEM_CTX });\n committed.add(chunk.index);\n } catch (err) {\n // The chunk rolled back, so nothing of it is on disk — including its\n // `chunk_done`. Unwind what earlier chunks committed.\n return await unwind(engine, plan, {\n runId, planHash, chunks, rowsOf, next, now,\n committed, compensated, chunksTotal: chunks.length, cause: err,\n });\n }\n }\n\n await appendEvent(engine, {\n run_id: runId, seq: next(), kind: 'run_done', migration_id: plan.migrationId, created_at: now(),\n });\n return {\n runId, status: 'completed', chunksTotal: chunks.length,\n chunksCommitted: committed.size, chunksCompensated: compensated.size, planHash,\n };\n}\n\ninterface UnwindArgs {\n runId: string;\n planHash: string;\n chunks: readonly MigrationChunk[];\n rowsOf: (c: MigrationChunk) => unknown[];\n next: () => number;\n now: () => string;\n committed: Set<number>;\n compensated: Set<number>;\n chunksTotal: number;\n cause: unknown;\n}\n\n/**\n * LIFO compensation over committed chunks.\n *\n * Newest-first because later chunks may depend on earlier ones; undoing in\n * commit order can hit a state the compensator was never written for.\n *\n * A compensation failure HALTS and is journalled — never swallowed, never\n * \"best effort, carry on\". Continuing past it would produce a database whose\n * state no journal describes, which is the one outcome this whole file exists\n * to prevent. The run ends `failed`, and the rows say exactly which chunk\n * resisted.\n */\nasync function unwind(\n engine: IObjectQLEngine,\n plan: MigrationPlan,\n a: UnwindArgs,\n): Promise<MigrationRunResult> {\n const order = [...a.committed].sort((x, y) => y - x); // newest-first\n for (const index of order) {\n if (a.compensated.has(index)) continue;\n const chunk = a.chunks[index];\n const step = plan.steps[chunk.stepIndex];\n\n if (!step.compensate) {\n // Nothing to undo this with. Say so loudly and stop — a silent skip\n // would leave the row written and the journal claiming a clean unwind.\n await appendEvent(engine, {\n run_id: a.runId, seq: a.next(), kind: 'run_failed',\n chunk_index: index, migration_id: plan.migrationId, created_at: a.now(),\n detail: JSON.stringify({\n phase: 'compensate', reason: 'step declares no compensate()',\n step: step.name, cause: errText(a.cause),\n }),\n });\n return {\n runId: a.runId, status: 'failed', chunksTotal: a.chunksTotal,\n chunksCommitted: a.committed.size, chunksCompensated: a.compensated.size,\n planHash: a.planHash, error: a.cause,\n };\n }\n\n const attempt = 1;\n try {\n await engine.transaction(async (trxCtx: unknown) => {\n await step.compensate!(a.rowsOf(chunk), { runId: a.runId, chunkIndex: index, attempt, context: trxCtx }, engine);\n await appendEvent(\n engine,\n {\n run_id: a.runId, seq: a.next(), kind: 'compensated',\n chunk_index: index, attempt, migration_id: plan.migrationId, created_at: a.now(),\n },\n trxCtx,\n );\n }, { ...SYSTEM_CTX });\n a.compensated.add(index);\n } catch (err) {\n await appendEvent(engine, {\n run_id: a.runId, seq: a.next(), kind: 'run_failed',\n chunk_index: index, migration_id: plan.migrationId, created_at: a.now(),\n detail: JSON.stringify({\n phase: 'compensate', step: step.name,\n error: errText(err), cause: errText(a.cause),\n }),\n });\n return {\n runId: a.runId, status: 'failed', chunksTotal: a.chunksTotal,\n chunksCommitted: a.committed.size, chunksCompensated: a.compensated.size,\n planHash: a.planHash, error: err,\n };\n }\n }\n\n await appendEvent(engine, {\n run_id: a.runId, seq: a.next(), kind: 'run_failed',\n migration_id: plan.migrationId, created_at: a.now(),\n detail: JSON.stringify({ phase: 'forward', error: errText(a.cause), compensated: [...a.compensated].sort((x, y) => x - y) }),\n });\n return {\n runId: a.runId, status: 'compensated', chunksTotal: a.chunksTotal,\n chunksCommitted: a.committed.size, chunksCompensated: a.compensated.size,\n planHash: a.planHash, error: a.cause,\n };\n}\n\n/** Resume a run the journal says was interrupted. Thin alias for intent at call sites. */\nexport async function resumeMigrationJournal(\n engine: IObjectQLEngine,\n plan: MigrationPlan,\n runId: string,\n options: Omit<RunMigrationJournalOptions, 'runId'> = {},\n): Promise<MigrationRunResult> {\n return runMigrationJournal(engine, plan, { ...options, runId });\n}\n\nfunction errText(err: unknown): string {\n if (err instanceof Error) return err.message;\n try {\n return String(err);\n } catch {\n return '<unprintable error>';\n }\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Runtime resolution of filter placeholders — the server-side half of the\n * `{token}` contract that `@objectstack/spec` declares (framework#3582).\n *\n * `date-macros.zod.ts` and `context-tokens.zod.ts` freeze the *vocabulary*;\n * `@objectstack/lint`'s `validate-filter-tokens` rejects a token outside it at\n * authoring time. Neither one ever substituted a value: every server-side\n * consumer handed the literal `'{current_year_start}'` to the database, where\n * it compared as a string and matched nothing. The failure was invisible —\n * an empty widget, an unfiltered list — so apps worked around it by computing\n * dates at module load, freezing \"this year\" into the built artifact.\n *\n * This module is the missing evaluator. It walks a filter tree and replaces\n * every fully-wrapped placeholder with a concrete value:\n *\n * { close_date: { $gte: '{current_year_start}' } }\n * → { close_date: { $gte: '2026-01-01' } }\n * { owner: '{current_user_id}' }\n * → { owner: 'usr_7f3a…' }\n *\n * # Output form: ISO strings, not driver-native values\n *\n * Date tokens resolve to `YYYY-MM-DD` (or a full ISO timestamp for the\n * sub-day tokens `{now}` / `{N_hours_ago}` / `{N_minutes_ago}`), exactly the\n * form the spec's module doc promises the data engine sees. Translating that\n * to a column's on-disk form is the DRIVER's job and already exists —\n * `SqlDriver.coerceFilterValue` / `temporalFilterValue` turn an ISO comparand\n * into SQLite epoch-ms or leave it alone on native-timestamp dialects. Emitting\n * a driver-native value here would fork that convention into a second source of\n * truth and break the moment a query crosses datasources.\n *\n * # Period `_end` is the last calendar DAY, not the last instant\n *\n * `{current_year_end}` is `2026-12-31`, per the spec's own\n * `DATE_MACRO_DESCRIPTIONS` (\"Dec 31 of this year\"). On a `datetime` column\n * that means `<= {current_year_end}` excludes everything after midnight on the\n * 31st — the classic half-open-range trap. Authors filtering a timestamp want\n * `< {next_year_start}`. This is a documented property of the vocabulary, not\n * something the resolver may quietly \"fix\": silently widening a bound would\n * make the same token mean different things on different column types.\n *\n * # An unknown token throws\n *\n * A value that is entirely `{something}` is a placeholder by construction — no\n * author means the literal six characters `{foo}`. Passing an unrecognised one\n * through is precisely the silent-zero bug this module exists to end, so it is\n * a hard error carrying the near-miss suggestion (`{current_user}` →\n * `{current_user_id}`). Values that merely CONTAIN braces are left untouched.\n *\n * \"Entirely `{something}`\" means ANY character between the braces (#5586).\n * Until then the recognition grammar was the token-NAME grammar\n * (`[a-zA-Z0-9_]+`), so a placeholder carrying a non-word character —\n * `{TODAY()}`, `{current-user-id}`, `{30 days ago}`, `{user.id}` — was not\n * recognised as a token at all and fell straight through to the literal\n * comparison this module exists to abolish. The failure was inverted against\n * the author: `{TODAY}` threw (diagnostic working), `{TODAY()}` returned rows\n * (diagnostic bypassed) — and the parenthesised, kebab-case and\n * natural-language spellings are exactly what an author migrating from another\n * system's macro syntax reaches for first. See `FILTER_TOKEN_WRAPPED_RE` in\n * `@objectstack/spec`.\n */\n\nimport {\n classifyFilterToken,\n parseDateMacroParam,\n type DateMacroUnit,\n} from '@objectstack/spec/data';\nimport { calendarPartsInTzOrUtc } from './datetime.js';\n\n/**\n * The slice of an execution context the resolver reads. Structural on purpose —\n * see {@link filterTokenContextFrom}.\n */\nexport interface ExecutionContextLike {\n readonly userId?: string;\n readonly tenantId?: string;\n readonly timezone?: string;\n}\n\n/**\n * The request-scoped values a placeholder can resolve against.\n *\n * `now` is captured ONCE per resolve call so every token in one filter shares\n * an instant — otherwise a `$gte {current_month_start}` / `$lt\n * {next_month_start}` pair evaluated microseconds apart could straddle a\n * month boundary and silently drop a row.\n */\nexport interface FilterTokenResolutionContext {\n /** Reference instant. Defaults to `new Date()` at call time. */\n now?: Date;\n /** IANA reference timezone for calendar boundaries. Defaults to UTC. */\n timezone?: string;\n /** Resolves `{current_user_id}`. */\n userId?: string;\n /** Resolves `{current_org_id}`. */\n orgId?: string;\n}\n\n/**\n * Raised when a filter carries a placeholder outside the vocabulary.\n *\n * Carries `status`/`code` so the REST layer's generic 4xx passthrough maps it\n * to a **400 with a fixable message** rather than a 500: the caller's filter is\n * malformed, the server is fine. (Same convention plugin-sharing uses for its\n * record-scope denial — no runtime dependency in either direction.)\n */\nexport class UnknownFilterTokenError extends Error {\n readonly token: string;\n readonly suggestion?: string;\n readonly status = 400;\n readonly code = 'FILTER_TOKEN_UNKNOWN';\n\n constructor(token: string, suggestion?: string) {\n super(\n `Unresolvable filter placeholder \"{${token}}\". ` +\n (suggestion\n ? `Did you mean \"{${suggestion}}\"? `\n : 'Resolvable placeholders are the context tokens ({current_user_id}, ' +\n '{current_org_id}) and the date macros ({today}, {current_quarter_start}, ' +\n '{30_days_ago}, …). ') +\n 'Sending it to the data engine verbatim would compare it as a literal ' +\n 'string and match nothing, which is indistinguishable from an empty result.',\n );\n this.name = 'UnknownFilterTokenError';\n this.token = token;\n this.suggestion = suggestion;\n }\n}\n\n/**\n * Raised when a token IS in the vocabulary but the request carries no value\n * for it — an unauthenticated caller filtering on `{current_user_id}`.\n *\n * Distinct from {@link UnknownFilterTokenError} because the fix is different:\n * the metadata is correct, the context is not. Never silently resolves to\n * `null`/`undefined`, which on most drivers degrades to `IS NULL` and would\n * quietly hand back rows the filter was written to exclude.\n */\nexport class UnresolvedFilterTokenError extends Error {\n readonly token: string;\n /** 400, not 500 — see {@link UnknownFilterTokenError}. */\n readonly status = 400;\n readonly code = 'FILTER_TOKEN_UNRESOLVED';\n\n constructor(token: string, detail: string) {\n super(`Filter placeholder \"{${token}}\" cannot be resolved: ${detail}`);\n this.name = 'UnresolvedFilterTokenError';\n this.token = token;\n }\n}\n\n/** `YYYY-MM-DD` for a calendar day, zero-padded. */\nfunction ymd(year: number, month: number, day: number): string {\n const p = (n: number) => String(n).padStart(2, '0');\n return `${year}-${p(month)}-${p(day)}`;\n}\n\n/**\n * Calendar arithmetic is done on a UTC \"proxy\" date built from the reference\n * timezone's calendar parts. Working in UTC keeps the math free of DST jumps\n * (a local-midnight `Date` can shift by an hour when `setMonth` crosses a\n * transition); the zone only decides WHICH calendar day \"now\" is, which\n * {@link calendarPartsInTzOrUtc} answers from the platform tz database.\n */\nfunction proxyDay(now: Date, timezone?: string): Date {\n const { year, month, day } = calendarPartsInTzOrUtc(now, timezone);\n return new Date(Date.UTC(year, month - 1, day));\n}\n\nconst asYmd = (d: Date): string => ymd(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());\n\ntype PeriodKind = 'week' | 'month' | 'quarter' | 'year';\n\n/** Monday-based week start — matches the spec's \"Monday 00:00 of this week\". */\nfunction startOfPeriod(kind: PeriodKind, d: Date): Date {\n const r = new Date(d.getTime());\n switch (kind) {\n case 'week': {\n const dow = (r.getUTCDay() + 6) % 7; // 0 = Monday\n r.setUTCDate(r.getUTCDate() - dow);\n return r;\n }\n case 'month':\n return new Date(Date.UTC(r.getUTCFullYear(), r.getUTCMonth(), 1));\n case 'quarter':\n return new Date(Date.UTC(r.getUTCFullYear(), Math.floor(r.getUTCMonth() / 3) * 3, 1));\n case 'year':\n return new Date(Date.UTC(r.getUTCFullYear(), 0, 1));\n }\n}\n\n/** Days in the given (0-based) month of `year`. */\nfunction daysInMonth(year: number, month: number): number {\n return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();\n}\n\n/**\n * Shift `d` by `n` months, CLAMPING the day to the target month's length.\n *\n * Bare `setUTCMonth(m - 1)` on the 31st rolls FORWARD into the following month\n * (Mar 31 minus one month = \"Feb 31\" = Mar 3), which would make\n * `{1_month_ago}` land after `{today}` on five days of the year. Clamping to\n * Feb 28 is what every calendar library does and the only answer an author\n * would call correct.\n */\nfunction addMonthsClamped(d: Date, n: number): Date {\n const year = d.getUTCFullYear();\n const month = d.getUTCMonth() + n;\n const targetYear = year + Math.floor(month / 12);\n const targetMonth = ((month % 12) + 12) % 12;\n const day = Math.min(d.getUTCDate(), daysInMonth(targetYear, targetMonth));\n return new Date(Date.UTC(\n targetYear, targetMonth, day,\n d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds(),\n ));\n}\n\n/** Shift `d` by `n` whole periods of `kind` (negative shifts backwards). */\nfunction addPeriods(kind: PeriodKind, d: Date, n: number): Date {\n switch (kind) {\n case 'week': {\n const r = new Date(d.getTime());\n r.setUTCDate(r.getUTCDate() + n * 7);\n return r;\n }\n case 'month': return addMonthsClamped(d, n);\n case 'quarter': return addMonthsClamped(d, n * 3);\n case 'year': return addMonthsClamped(d, n * 12);\n }\n}\n\n/** Shift `d` by `n` units of the parameterised grammar. */\nfunction addUnits(unit: DateMacroUnit, d: Date, n: number): Date {\n const r = new Date(d.getTime());\n switch (unit) {\n case 'minute': r.setUTCMinutes(r.getUTCMinutes() + n); return r;\n case 'hour': r.setUTCHours(r.getUTCHours() + n); return r;\n case 'day': r.setUTCDate(r.getUTCDate() + n); return r;\n case 'week': r.setUTCDate(r.getUTCDate() + n * 7); return r;\n // Month/year steps clamp rather than overflow — see addMonthsClamped.\n case 'month': return addMonthsClamped(d, n);\n case 'year': return addMonthsClamped(d, n * 12);\n }\n}\n\n/**\n * `current|last|next` × `week|month|quarter|year` × `start|end`, plus the bare\n * `week_start`-style aliases (which mean `current_`). Returns `undefined` when\n * the token is not a period token.\n */\nconst PERIOD_RE = /^(?:(current|last|next)_)?(week|month|quarter|year)_(start|end)$/;\n\nfunction resolvePeriodToken(token: string, today: Date): string | undefined {\n const m = PERIOD_RE.exec(token);\n if (!m) return undefined;\n const rel = (m[1] ?? 'current') as 'current' | 'last' | 'next';\n const kind = m[2] as PeriodKind;\n const bound = m[3] as 'start' | 'end';\n const offset = rel === 'last' ? -1 : rel === 'next' ? 1 : 0;\n\n // Normalize to the period's own start BEFORE stepping. Day 1 of a\n // month/quarter/year (and a Monday) shifts exactly — no month-length clamping\n // is involved at all — so the answer never depends on the clamp policy, and\n // the arithmetic reads the same for every `kind`.\n const periodStart = startOfPeriod(kind, addPeriods(kind, startOfPeriod(kind, today), offset));\n if (bound === 'start') return asYmd(periodStart);\n // `_end` = the last calendar DAY of the period: the day before the next\n // period begins. See the module doc on half-open ranges.\n const next = addPeriods(kind, periodStart, 1);\n next.setUTCDate(next.getUTCDate() - 1);\n return asYmd(next);\n}\n\n/**\n * Resolve one token NAME (the bit inside the braces) to its concrete value.\n * Throws {@link UnresolvedFilterTokenError} for a vocabulary token the request\n * carries no value for. Returns `undefined` only when the token is outside the\n * vocabulary — callers turn that into {@link UnknownFilterTokenError}.\n */\nexport function resolveFilterToken(\n token: string,\n ctx: FilterTokenResolutionContext = {},\n): unknown {\n const now = ctx.now ?? new Date();\n\n // ── Context tokens ────────────────────────────────────────────────────\n if (token === 'current_user_id') {\n if (!ctx.userId) {\n throw new UnresolvedFilterTokenError(\n token,\n 'the request has no authenticated user. A filter scoped to the signed-in ' +\n 'user cannot run for an anonymous or system caller — gate the surface on ' +\n 'authentication, or drop the token from the filter.',\n );\n }\n return ctx.userId;\n }\n if (token === 'current_org_id') {\n if (!ctx.orgId) {\n throw new UnresolvedFilterTokenError(\n token,\n 'the request carries no active organization (ExecutionContext.tenantId is ' +\n 'unset). Set the active org on the request, or drop the token from the filter.',\n );\n }\n return ctx.orgId;\n }\n\n // ── Date macros ───────────────────────────────────────────────────────\n const today = proxyDay(now, ctx.timezone);\n\n switch (token) {\n case 'now': return now.toISOString();\n case 'today': return asYmd(today);\n case 'yesterday': return asYmd(addUnits('day', today, -1));\n case 'tomorrow': return asYmd(addUnits('day', today, 1));\n }\n\n const period = resolvePeriodToken(token, today);\n if (period !== undefined) return period;\n\n const param = parseDateMacroParam(token);\n if (param) {\n const sign = param.direction === 'ago' ? -1 : 1;\n // Sub-day units are instants — they must keep their time-of-day, so they\n // shift `now` and render as a full ISO timestamp. Day-and-coarser units are\n // calendar quantities and render as `YYYY-MM-DD` off the reference day.\n if (param.unit === 'minute' || param.unit === 'hour') {\n return addUnits(param.unit, now, sign * param.n).toISOString();\n }\n return asYmd(addUnits(param.unit, today, sign * param.n));\n }\n\n return undefined;\n}\n\n/**\n * Does this tree contain any fully-wrapped placeholder at all?\n *\n * A read-only pre-pass so the overwhelmingly common case — an internal query\n * whose filter is entirely literal — costs one allocation-free walk instead of\n * a full structural copy. This runs on every server-side read, so \"no\n * placeholders\" must be close to free.\n */\nfunction hasFilterToken(node: unknown): boolean {\n if (typeof node === 'string') return classifyFilterToken(node) !== null;\n if (Array.isArray(node)) return node.some(hasFilterToken);\n if (node && typeof node === 'object' && !(node instanceof Date)) {\n return Object.values(node as Record<string, unknown>).some(hasFilterToken);\n }\n return false;\n}\n\n/**\n * Deep-replace every fully-wrapped placeholder in `filter` with its resolved\n * value, returning a NEW tree (the caller's metadata is never mutated — a view\n * or dataset definition is shared across requests, so resolving in place would\n * bake one request's user id, and one day's dates, into every later render).\n *\n * Returns the input unchanged, by reference, when it holds no placeholders.\n */\nexport function resolveFilterTokens<T>(\n filter: T,\n ctx: FilterTokenResolutionContext = {},\n): T {\n if (filter == null) return filter;\n if (!hasFilterToken(filter)) return filter;\n\n // One instant for the whole tree (see FilterTokenResolutionContext.now).\n const pinned: FilterTokenResolutionContext = { ...ctx, now: ctx.now ?? new Date() };\n\n const walk = (node: unknown): unknown => {\n if (typeof node === 'string') {\n const cls = classifyFilterToken(node);\n if (!cls) return node;\n if (cls.kind === 'unknown') throw new UnknownFilterTokenError(cls.token, cls.suggestion);\n const resolved = resolveFilterToken(cls.token, pinned);\n // `classifyFilterToken` already vouched for the token, so `undefined`\n // here would mean the spec vocabulary and this resolver have drifted\n // apart — surface it loudly rather than silently emitting `undefined`.\n if (resolved === undefined) throw new UnknownFilterTokenError(cls.token);\n return resolved;\n }\n if (Array.isArray(node)) return node.map(walk);\n if (node && typeof node === 'object') {\n // Dates and other class instances are comparands, not filter structure.\n if (node instanceof Date) return node;\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(node as Record<string, unknown>)) out[k] = walk(v);\n return out;\n }\n return node;\n };\n\n return walk(filter) as T;\n}\n\n/**\n * Convenience bridge from an execution context to the resolver's inputs.\n * `{current_org_id}` reads `tenantId` — the active organization IS the tenant\n * on the read path (same value the RLS compiler binds to\n * `current_user.organization_id`).\n *\n * Typed structurally, not as `ExecutionContext`, so both the parsed context\n * (`ExecutionContextParsed`, defaults applied) and the pre-parse\n * `ExecutionContext` a caller holds mid-pipeline satisfy it. The three fields\n * read here are optional in both.\n */\nexport function filterTokenContextFrom(\n execCtx: ExecutionContextLike | undefined,\n now?: Date,\n): FilterTokenResolutionContext {\n return {\n now,\n timezone: execCtx?.timezone,\n userId: execCtx?.userId,\n orgId: execCtx?.tenantId,\n };\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * [#8690] Can a temporal field's storage rule READ this comparand at all?\n *\n * The one predicate behind the engine's temporal-comparand door\n * (`@objectstack/objectql`, `temporal-comparand-door.ts`) and the analytics\n * raw-SQL decline (`@objectstack/service-analytics`,\n * `NativeSQLStrategy.canHandle`). It lives in `core` because those two packages\n * do not depend on each other and a rule that exists twice is a rule that will\n * disagree with itself — the shape the 2026-08-12 ruling named by name.\n *\n * ## The defect it exists to close\n *\n * A `datetime` field filtered with a bare string the API cannot take literally\n * — `last_30_days`, `not-a-date-at-all` — was bound AS-IS all the way to the\n * driver, where the comparison is false for every row. Measured end to end on\n * `InMemoryDriver` with 51 rows seeded / 38 in-window:\n *\n * ```\n * $gte \"last_30_days\" HTTP 200 count=0 <- silent zero (the defect)\n * $gte \"not-a-date-at-all\" HTTP 200 count=0 <- silent zero\n * $gte \"{30_days_ago}\" HTTP 200 count=38 <- the positive control\n * $gte \"{TODAY}\" REFUSED FILTER_TOKEN_UNKNOWN / 400\n * ```\n *\n * A `{placeholder}` the resolver does not know is refused loudly; a bare string\n * that is not a date was not validated at all. An empty chart is the hardest\n * failure to debug — it is indistinguishable from \"there is genuinely no data\".\n *\n * ## Why the KIND is an argument, and not something this file works out\n *\n * \"Uninterpretable comparand\" is a field-TYPED judgement, and `packages/spec`\n * says so outright (`filter.zod.ts`): a filter schema \"is field-AGNOSTIC … it\n * never sees which column the operator is applied to\". This module therefore\n * answers only the VALUE half — given a kind, can that kind's storage rule read\n * this value — and the caller, which owns object metadata, supplies the kind.\n * That split is what lets the rule sit below both consumers without dragging\n * field metadata down with it.\n *\n * ## Interpretability is defined by the DRIVERS' own totals, deliberately\n *\n * Each predicate below mirrors the total function that would receive the value\n * if the door let it through — `storageDatetimeValue` / `storageDateValue` /\n * `storageTimeValue` in `driver-memory`, and their `SqlDriver` twins. Those\n * functions are total on purpose: an input they cannot interpret is returned\n * UNCHANGED rather than becoming an invented instant. So \"the driver would\n * return it unchanged\" IS the definition of uninterpretable, and defining it\n * any other way would refuse comparands that work today.\n *\n * That is why this is not `utcInstantMs` (`@objectstack/spec/data`), which is\n * the stricter canonical reader: it rejects a bare epoch-millisecond string and\n * every non-ISO spelling `Date.parse` accepts, both of which the drivers read\n * correctly today. Refusing those would be a narrowing this card did not ask\n * for and no measurement supports.\n *\n * ## Two things it deliberately does NOT judge\n *\n * - **Non-string comparands.** A number is epoch milliseconds, a `Date` is an\n * instant, `null` is a null test. The refusal scopes to strings by ruling.\n * - **The EMPTY string.** Measured, `$gte \"\"` binds as `''` and every canonical\n * UTC text sorts at or above it, so it returns every non-null row — a third\n * behaviour again, and one the maintainer ruled stays its own card: \"B and C\n * scope to non-empty strings and must not decide it in passing\". A\n * whitespace-only string is the same cell (the drivers `trim()` before they\n * test), so it is left alone too.\n */\n\nimport { classifyFilterToken } from '@objectstack/spec/data';\n\n/** Which temporal storage rule a declared field takes. */\nexport type TemporalComparandKind = 'datetime' | 'date' | 'time';\n\n/**\n * The kind a declared field's `type` takes, or `null` for every non-temporal\n * field.\n *\n * The same three-way split `driver-memory`'s `indexTemporalFields` and\n * `SqlDriver.temporalFieldKind` make, so the door and the drivers cannot\n * disagree about which fields are temporal at all.\n */\nexport function temporalComparandKind(fieldType: unknown): TemporalComparandKind | null {\n if (fieldType === 'datetime') return 'datetime';\n if (fieldType === 'date') return 'date';\n if (fieldType === 'time') return 'time';\n return null;\n}\n\n/**\n * `storageDatetimeValue`'s reading, as a yes/no.\n *\n * Mirrors it step for step: a bare integer in either sign is epoch\n * milliseconds; a bare `YYYY-MM-DD` is midnight UTC; a zone-naive\n * `YYYY-MM-DD[ T]HH:MM[:SS[.fff]]` has its wall clock read AS UTC; anything\n * else is handed to `Date.parse` exactly as the drivers hand it over.\n */\nfunction readsAsInstant(s: string): boolean {\n if (/^-?\\d+$/.test(s)) return Number.isFinite(new Date(Number(s)).getTime());\n const iso = /^\\d{4}-\\d{2}-\\d{2}$/.test(s)\n ? `${s}T00:00:00.000Z`\n : /^\\d{4}-\\d{2}-\\d{2}[ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?$/.test(s)\n ? `${s.replace(' ', 'T')}Z`\n : s;\n return Number.isFinite(Date.parse(iso));\n}\n\n/**\n * `storageDateValue`'s reading of a STRING: its leading `YYYY-MM-DD`, and\n * nothing else.\n *\n * Narrower than {@link readsAsInstant} on purpose, because the rule it mirrors\n * is narrower: `storageDateValue` collapses a leading calendar day and returns\n * every other string untouched. `2026/07/15` is therefore uninterpretable for a\n * `date` column even though `Date.parse` reads it — today it survives to the\n * driver and compares as text against `YYYY-MM-DD` values, which is the silent\n * wrong answer this door exists to stop.\n */\nfunction readsAsCalendarDay(s: string): boolean {\n return /^\\d{4}-\\d{2}-\\d{2}/.test(s);\n}\n\n/**\n * `storageTimeValue`'s reading: a bare wall clock whose components are in\n * range. Out-of-range (`25:00`) is uninterpretable — the rule it mirrors\n * returns such a value untouched rather than wrapping it.\n */\nfunction readsAsWallClock(s: string): boolean {\n const m = /^(\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.(\\d+))?)?$/.exec(s);\n if (!m) return false;\n return Number(m[1]) <= 23 && Number(m[2]) <= 59 && Number(m[3] ?? '0') <= 59;\n}\n\n/**\n * Is `value` a comparand that a `kind` column's storage rule cannot read?\n *\n * `true` ONLY for a non-empty, non-placeholder STRING that the kind's rule\n * would hand back unchanged. Everything else — a number, a `Date`, `null`, a\n * `{ $field }` reference, filter structure, the empty string, a `{token}` —\n * answers `false`, each for a reason recorded in the module note or below.\n *\n * A `{placeholder}` is stepped around rather than judged because it is another\n * layer's vocabulary and that layer already refuses the unknown ones loudly\n * (`FILTER_TOKEN_UNKNOWN` / 400, with the resolvable tokens listed). Both doors\n * that call this run BEFORE token resolution, so judging a placeholder here\n * would refuse `{30_days_ago}` — the platform's own correct spelling, and the\n * positive control this fix is pinned against.\n */\nexport function isUninterpretableTemporalComparand(\n kind: TemporalComparandKind,\n value: unknown,\n): boolean {\n if (typeof value !== 'string') return false;\n const s = value.trim();\n // The empty-string cell is its own card — see the module note.\n if (s === '') return false;\n // Another layer's vocabulary, and it has its own loud refusal.\n if (classifyFilterToken(value) !== null) return false;\n if (kind === 'datetime') return !readsAsInstant(s);\n if (kind === 'date') return !readsAsCalendarDay(s);\n return !(readsAsWallClock(s) || readsAsInstant(s));\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * [#4435] The 404 a single-record operation answers when the id names no row.\n *\n * Extracted so the READ and the two WRITE paths cannot disagree about it. They\n * did: `getData` answered `404 RECORD_NOT_FOUND` while `updateData` returned\n * `200 { record: null }` and `deleteData` returned `200 { success: true }` for\n * any string in the path — so a typo'd id, an already-deleted row and a real\n * deletion were indistinguishable, and a client PATCHing a concurrently deleted\n * record was told its write had landed.\n *\n * That is the same silent-no-op shape the v17 train removed everywhere else\n * this window (#4240/#4303/#4315 refuse missing fields, #4169 refuses unknown\n * params, #4190 stopped dropping filters) — a write that touched zero rows\n * reporting 200 is that shape one level up, on the verb where it costs the\n * most.\n *\n * [#5138] EXPORTED, for the same \"cannot disagree about it\" reason one layer\n * out. `@objectstack/runtime`'s `callData` is protocol-first with an ObjectQL\n * FALLBACK, and the fallback had reinvented this fact three incompatible ways\n * (`get` → `null`, `update` → a bare `Error` with no status ⇒ 500, `delete` →\n * no check at all ⇒ `200 { deleted: true }` for a row that never existed). It\n * now calls THIS function, so the two paths behind one `callData` answer a\n * missing id identically — which is the only reason a caller may stop caring\n * which of them served it. Re-spelling the envelope there would have been a\n * second not-found envelope; `RECORD_NOT_FOUND` (#5088) is the one this repo\n * has.\n *\n * ── [#7867] Why it lives in `@objectstack/core` and not where it was written ──\n *\n * Because the THIRD path that needed it could not reach the second one. An\n * action body's `ctx.api.object(name).update({ id, … })` traverses neither\n * `protocol.updateData` nor `callData`: it reaches `ObjectQL.update()`'s by-id\n * branch directly, which had no existence gate at all, so a ghost id was a\n * silent no-op that then died on whatever the pipeline complained about first\n * (a `HookConditionError` 400 on a hooked object, a required-field\n * `VALIDATION_FAILED` 400 on an unhooked one — the 400 class varied with the\n * object's declarations; the missing 404 was the constant).\n *\n * The gate for that path belongs in the engine, and `packages/objectql` cannot\n * import `@objectstack/metadata-protocol` where this function was written:\n * ADR-0076 D2's boundary ratchet (`core-boundary.ratchet.test.ts`) forbids the\n * whole `@objectstack/objectql/core` closure — `engine.ts` included — from\n * pulling that package in. So the choice was a FOURTH spelling of the envelope\n * or one home both layers already depend on. #5138's own sentence rules the\n * first out, so this is the second: the factory moved down to the lowest\n * package the three producers share, and `@objectstack/metadata-protocol`\n * re-exports it unchanged for every existing importer.\n *\n * This is the same move `engineCanRollBack` made for the same reason — a fact\n * two layers must agree on lives in the layer beneath both, not in a copy each.\n */\nexport function recordNotFoundError(object: string, id: string | number): Error {\n const err = new Error(`Record ${id} not found in ${object}`) as Error & {\n code?: string;\n status?: number;\n object?: string;\n };\n err.code = 'RECORD_NOT_FOUND';\n err.status = 404;\n err.object = object;\n return err;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { \n PluginHealthStatus, \n PluginHealthCheckParsed, \n PluginHealthReport \n} from '@objectstack/spec/kernel';\nimport type { ObjectLogger } from './logger.js';\nimport type { Plugin } from './types.js';\n\n/**\n * Plugin Health Monitor\n * \n * Monitors plugin health status and performs automatic recovery actions.\n * Implements the advanced lifecycle health monitoring protocol.\n */\nexport class PluginHealthMonitor {\n private logger: ObjectLogger;\n private healthChecks = new Map<string, PluginHealthCheckParsed>();\n private healthStatus = new Map<string, PluginHealthStatus>();\n private healthReports = new Map<string, PluginHealthReport>();\n private checkIntervals = new Map<string, NodeJS.Timeout>();\n private failureCounters = new Map<string, number>();\n private successCounters = new Map<string, number>();\n private restartAttempts = new Map<string, number>();\n\n constructor(logger: ObjectLogger) {\n this.logger = logger.child({ component: 'HealthMonitor' });\n }\n\n /**\n * Register a plugin for health monitoring\n */\n registerPlugin(pluginName: string, config: PluginHealthCheckParsed): void {\n this.healthChecks.set(pluginName, config);\n this.healthStatus.set(pluginName, 'unknown');\n this.failureCounters.set(pluginName, 0);\n this.successCounters.set(pluginName, 0);\n this.restartAttempts.set(pluginName, 0);\n\n this.logger.info('Plugin registered for health monitoring', { \n plugin: pluginName,\n interval: config.interval \n });\n }\n\n /**\n * Start monitoring a plugin\n */\n startMonitoring(pluginName: string, plugin: Plugin): void {\n const config = this.healthChecks.get(pluginName);\n if (!config) {\n this.logger.warn('Cannot start monitoring - plugin not registered', { plugin: pluginName });\n return;\n }\n\n // Clear any existing interval\n this.stopMonitoring(pluginName);\n\n // Set up periodic health checks\n const interval = setInterval(() => {\n this.performHealthCheck(pluginName, plugin, config).catch(error => {\n this.logger.error('Health check failed with error', { \n plugin: pluginName, \n error \n });\n });\n }, config.interval);\n\n this.checkIntervals.set(pluginName, interval);\n this.logger.info('Health monitoring started', { plugin: pluginName });\n\n // Perform initial health check\n this.performHealthCheck(pluginName, plugin, config).catch(error => {\n this.logger.error('Initial health check failed', { \n plugin: pluginName, \n error \n });\n });\n }\n\n /**\n * Stop monitoring a plugin\n */\n stopMonitoring(pluginName: string): void {\n const interval = this.checkIntervals.get(pluginName);\n if (interval) {\n clearInterval(interval);\n this.checkIntervals.delete(pluginName);\n this.logger.info('Health monitoring stopped', { plugin: pluginName });\n }\n }\n\n /**\n * Perform a health check on a plugin\n */\n private async performHealthCheck(\n pluginName: string,\n plugin: Plugin,\n config: PluginHealthCheckParsed\n ): Promise<void> {\n const startTime = Date.now();\n let status: PluginHealthStatus = 'healthy';\n let message: string | undefined;\n const checks: Array<{ name: string; status: 'passed' | 'failed' | 'warning'; message?: string }> = [];\n\n try {\n // Check if plugin has a custom health check method\n if (config.checkMethod && typeof (plugin as any)[config.checkMethod] === 'function') {\n const checkResult = await this.raceCheckTimeout(\n (plugin as any)[config.checkMethod](),\n config.timeout,\n `Health check timeout after ${config.timeout}ms`\n );\n\n if (checkResult === false || (checkResult && checkResult.status === 'unhealthy')) {\n status = 'unhealthy';\n message = checkResult?.message || 'Custom health check failed';\n checks.push({ name: config.checkMethod, status: 'failed', message });\n } else {\n checks.push({ name: config.checkMethod, status: 'passed' });\n }\n } else {\n // Default health check - just verify plugin is loaded\n checks.push({ name: 'plugin-loaded', status: 'passed' });\n }\n\n // Update counters based on result\n if (status === 'healthy') {\n this.successCounters.set(pluginName, (this.successCounters.get(pluginName) || 0) + 1);\n this.failureCounters.set(pluginName, 0);\n\n // Recover from unhealthy state if we have enough successes\n const currentStatus = this.healthStatus.get(pluginName);\n if (currentStatus === 'unhealthy' || currentStatus === 'degraded') {\n const successCount = this.successCounters.get(pluginName) || 0;\n if (successCount >= config.successThreshold) {\n this.healthStatus.set(pluginName, 'healthy');\n this.logger.info('Plugin recovered to healthy state', { plugin: pluginName });\n } else {\n this.healthStatus.set(pluginName, 'recovering');\n }\n } else {\n this.healthStatus.set(pluginName, 'healthy');\n }\n } else {\n this.failureCounters.set(pluginName, (this.failureCounters.get(pluginName) || 0) + 1);\n this.successCounters.set(pluginName, 0);\n\n const failureCount = this.failureCounters.get(pluginName) || 0;\n if (failureCount >= config.failureThreshold) {\n this.healthStatus.set(pluginName, 'unhealthy');\n this.logger.warn('Plugin marked as unhealthy', { \n plugin: pluginName, \n failures: failureCount \n });\n\n // Attempt auto-restart if configured\n if (config.autoRestart) {\n await this.attemptRestart(pluginName, plugin, config);\n }\n } else {\n this.healthStatus.set(pluginName, 'degraded');\n }\n }\n } catch (error) {\n status = 'failed';\n message = error instanceof Error ? error.message : 'Unknown error';\n this.failureCounters.set(pluginName, (this.failureCounters.get(pluginName) || 0) + 1);\n this.healthStatus.set(pluginName, 'failed');\n \n checks.push({ \n name: 'health-check', \n status: 'failed', \n message: message \n });\n\n this.logger.error('Health check exception', { \n plugin: pluginName, \n error \n });\n }\n\n // Create health report\n const report: PluginHealthReport = {\n status: this.healthStatus.get(pluginName) || 'unknown',\n timestamp: new Date().toISOString(),\n message,\n metrics: {\n uptime: Date.now() - startTime,\n },\n checks: checks.length > 0 ? checks : undefined,\n };\n\n this.healthReports.set(pluginName, report);\n }\n\n /**\n * Attempt to restart a plugin\n */\n private async attemptRestart(\n pluginName: string,\n plugin: Plugin,\n config: PluginHealthCheckParsed\n ): Promise<void> {\n const attempts = this.restartAttempts.get(pluginName) || 0;\n \n if (attempts >= config.maxRestartAttempts) {\n this.logger.error('Max restart attempts reached, giving up', { \n plugin: pluginName, \n attempts \n });\n this.healthStatus.set(pluginName, 'failed');\n return;\n }\n\n this.restartAttempts.set(pluginName, attempts + 1);\n \n // Calculate backoff delay\n const delay = this.calculateBackoff(attempts, config.restartBackoff);\n \n this.logger.info('Scheduling plugin restart', { \n plugin: pluginName, \n attempt: attempts + 1, \n delay \n });\n\n await new Promise(resolve => setTimeout(resolve, delay));\n\n try {\n // Call destroy and init to restart\n if (plugin.destroy) {\n await plugin.destroy();\n }\n \n // Note: Full restart would require kernel context\n // This is a simplified version - actual implementation would need kernel integration\n this.logger.info('Plugin restarted', { plugin: pluginName });\n \n // Reset counters on successful restart\n this.failureCounters.set(pluginName, 0);\n this.successCounters.set(pluginName, 0);\n this.healthStatus.set(pluginName, 'recovering');\n } catch (error) {\n this.logger.error('Plugin restart failed', { \n plugin: pluginName, \n error \n });\n this.healthStatus.set(pluginName, 'failed');\n }\n }\n\n /**\n * Calculate backoff delay for restarts\n */\n private calculateBackoff(attempt: number, strategy: 'fixed' | 'linear' | 'exponential'): number {\n const baseDelay = 1000; // 1 second base\n\n switch (strategy) {\n case 'fixed':\n return baseDelay;\n case 'linear':\n return baseDelay * (attempt + 1);\n case 'exponential':\n return baseDelay * Math.pow(2, attempt);\n default:\n return baseDelay;\n }\n }\n\n /**\n * Get current health status of a plugin\n */\n getHealthStatus(pluginName: string): PluginHealthStatus | undefined {\n return this.healthStatus.get(pluginName);\n }\n\n /**\n * Get latest health report for a plugin\n */\n getHealthReport(pluginName: string): PluginHealthReport | undefined {\n return this.healthReports.get(pluginName);\n }\n\n /**\n * Get all health statuses\n */\n getAllHealthStatuses(): Map<string, PluginHealthStatus> {\n return new Map(this.healthStatus);\n }\n\n /**\n * Shutdown health monitor\n */\n shutdown(): void {\n // Stop all monitoring intervals\n for (const pluginName of this.checkIntervals.keys()) {\n this.stopMonitoring(pluginName);\n }\n \n this.healthChecks.clear();\n this.healthStatus.clear();\n this.healthReports.clear();\n this.failureCounters.clear();\n this.successCounters.clear();\n this.restartAttempts.clear();\n \n this.logger.info('Health monitor shutdown complete');\n }\n\n /**\n * Race a plugin's custom health check against its timeout guard, and\n * reclaim the guard the moment the race settles (#4875).\n *\n * Same shape, same reasoning as `ObjectKernel.raceStartupTimeout()` (#4813,\n * PR #4874): the guard used to be armed and then abandoned — when the check\n * won the race, its `setTimeout` stayed ref'd in the event loop for the full\n * `config.timeout`. Health checks are *periodic*, so unlike the kernel's\n * one-shot startup guards the orphans here accumulate: one per plugin per\n * round, each pinning the loop for `config.timeout`.\n *\n * Clearing on settle rather than `unref()`-ing at arm time is deliberate.\n * An unref'd guard also stops pinning the loop, but it stops being a guard\n * as well: if the check never settles and nothing else keeps the loop alive,\n * Node exits before the timer can fire and the timeout is never reported.\n * The guard has to stay ref'd exactly as long as the race is undecided,\n * which is what `clearTimeout` in a `finally` expresses.\n *\n * `check` is widened to `T | PromiseLike<T>` because `checkMethod` is called\n * dynamically off the plugin and may be synchronous; such a check wins the\n * race immediately and the guard is reclaimed on the same turn.\n */\n private async raceCheckTimeout<T>(\n check: T | PromiseLike<T>,\n ms: number,\n message: string\n ): Promise<T> {\n let guard: ReturnType<typeof setTimeout> | undefined;\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n guard = setTimeout(() => {\n reject(new Error(message));\n }, ms);\n });\n\n try {\n return await Promise.race([check, timeoutPromise]);\n } finally {\n clearTimeout(guard);\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { createHash } from 'node:crypto';\n\nimport type { \n HotReloadConfigParsed, \n PluginStateSnapshot \n} from '@objectstack/spec/kernel';\nimport type { ObjectLogger } from './logger.js';\nimport type { Plugin } from './types.js';\n\n// Polyfill for UUID generation to support both Node.js and Browser\nconst generateUUID = () => {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Basic UUID v4 fallback\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n};\n\n/**\n * Plugin State Manager\n * \n * Handles state persistence and restoration during hot reloads\n */\nclass PluginStateManager {\n private logger: ObjectLogger;\n private stateSnapshots = new Map<string, PluginStateSnapshot>();\n private memoryStore = new Map<string, any>();\n\n constructor(logger: ObjectLogger) {\n this.logger = logger.child({ component: 'StateManager' });\n }\n\n /**\n * Save plugin state before reload\n */\n async saveState(\n pluginId: string,\n version: string,\n state: Record<string, any>,\n config: HotReloadConfigParsed\n ): Promise<string> {\n const snapshot: PluginStateSnapshot = {\n pluginId,\n version,\n timestamp: new Date().toISOString(),\n state,\n metadata: {\n checksum: this.calculateChecksum(state),\n compressed: false,\n },\n };\n\n const snapshotId = generateUUID();\n\n switch (config.stateStrategy) {\n case 'memory':\n this.memoryStore.set(snapshotId, snapshot);\n this.logger.debug('State saved to memory', { pluginId, snapshotId });\n break;\n\n case 'disk':\n // For disk storage, we would write to file system\n // For now, store in memory as fallback\n this.memoryStore.set(snapshotId, snapshot);\n this.logger.debug('State saved to disk (memory fallback)', { pluginId, snapshotId });\n break;\n\n case 'distributed':\n // For distributed storage, would use Redis/etcd\n // For now, store in memory as fallback\n this.memoryStore.set(snapshotId, snapshot);\n this.logger.debug('State saved to distributed store (memory fallback)', { \n pluginId, \n snapshotId \n });\n break;\n\n case 'none':\n this.logger.debug('State persistence disabled', { pluginId });\n break;\n }\n\n this.stateSnapshots.set(pluginId, snapshot);\n return snapshotId;\n }\n\n /**\n * Restore plugin state after reload\n */\n async restoreState(\n pluginId: string,\n snapshotId?: string\n ): Promise<Record<string, any> | undefined> {\n // Try to get from snapshot ID first, otherwise use latest for plugin\n let snapshot: PluginStateSnapshot | undefined;\n\n if (snapshotId) {\n snapshot = this.memoryStore.get(snapshotId);\n } else {\n snapshot = this.stateSnapshots.get(pluginId);\n }\n\n if (!snapshot) {\n this.logger.warn('No state snapshot found', { pluginId, snapshotId });\n return undefined;\n }\n\n // Verify checksum if available\n if (snapshot.metadata?.checksum) {\n const currentChecksum = this.calculateChecksum(snapshot.state);\n if (currentChecksum !== snapshot.metadata.checksum) {\n this.logger.error('State checksum mismatch - data may be corrupted', { \n pluginId,\n expected: snapshot.metadata.checksum,\n actual: currentChecksum\n });\n return undefined;\n }\n }\n\n this.logger.debug('State restored', { pluginId, version: snapshot.version });\n return snapshot.state;\n }\n\n /**\n * Clear state for a plugin\n */\n clearState(pluginId: string): void {\n this.stateSnapshots.delete(pluginId);\n // Note: We don't clear memory store as it might have multiple snapshots\n this.logger.debug('State cleared', { pluginId });\n }\n\n /**\n * Calculate checksum for state verification using SHA-256.\n */\n private calculateChecksum(state: Record<string, any>): string {\n const stateStr = JSON.stringify(state);\n return createHash('sha256').update(stateStr).digest('hex');\n }\n\n /**\n * Shutdown state manager\n */\n shutdown(): void {\n this.stateSnapshots.clear();\n this.memoryStore.clear();\n this.logger.info('State manager shutdown complete');\n }\n}\n\n/**\n * Hot Reload Manager\n * \n * Manages hot reloading of plugins with state preservation\n */\nexport class HotReloadManager {\n private logger: ObjectLogger;\n private stateManager: PluginStateManager;\n private reloadConfigs = new Map<string, HotReloadConfigParsed>();\n private watchHandles = new Map<string, any>();\n private reloadTimers = new Map<string, NodeJS.Timeout>();\n\n constructor(logger: ObjectLogger) {\n this.logger = logger.child({ component: 'HotReload' });\n this.stateManager = new PluginStateManager(logger);\n }\n\n /**\n * Register a plugin for hot reload\n */\n registerPlugin(pluginName: string, config: HotReloadConfigParsed): void {\n if (!config.enabled) {\n this.logger.debug('Hot reload disabled for plugin', { plugin: pluginName });\n return;\n }\n\n this.reloadConfigs.set(pluginName, config);\n this.logger.info('Plugin registered for hot reload', { \n plugin: pluginName,\n watchPatterns: config.watchPatterns,\n stateStrategy: config.stateStrategy\n });\n }\n\n /**\n * Start watching for changes (requires file system integration)\n */\n startWatching(pluginName: string): void {\n const config = this.reloadConfigs.get(pluginName);\n if (!config || !config.enabled) {\n return;\n }\n\n // Note: Actual file watching would require chokidar or similar\n // This is a placeholder for the integration point\n this.logger.info('File watching started', { \n plugin: pluginName,\n patterns: config.watchPatterns \n });\n }\n\n /**\n * Stop watching for changes\n */\n stopWatching(pluginName: string): void {\n const handle = this.watchHandles.get(pluginName);\n if (handle) {\n // Stop watching (would call chokidar close())\n this.watchHandles.delete(pluginName);\n this.logger.info('File watching stopped', { plugin: pluginName });\n }\n\n // Clear any pending reload timers\n const timer = this.reloadTimers.get(pluginName);\n if (timer) {\n clearTimeout(timer);\n this.reloadTimers.delete(pluginName);\n }\n }\n\n /**\n * Trigger hot reload for a plugin\n */\n async reloadPlugin(\n pluginName: string,\n plugin: Plugin,\n version: string,\n getPluginState: () => Record<string, any>,\n restorePluginState: (state: Record<string, any>) => void\n ): Promise<boolean> {\n const config = this.reloadConfigs.get(pluginName);\n if (!config) {\n this.logger.warn('Cannot reload - plugin not registered', { plugin: pluginName });\n return false;\n }\n\n this.logger.info('Starting hot reload', { plugin: pluginName });\n\n try {\n // Call before reload hooks\n if (config.beforeReload) {\n this.logger.debug('Executing before reload hooks', { \n plugin: pluginName,\n hooks: config.beforeReload \n });\n // Hook execution would be done through kernel's hook system\n }\n\n // Save state if configured\n let snapshotId: string | undefined;\n if (config.preserveState && config.stateStrategy !== 'none') {\n const state = getPluginState();\n snapshotId = await this.stateManager.saveState(\n pluginName,\n version,\n state,\n config\n );\n this.logger.debug('Plugin state saved', { plugin: pluginName, snapshotId });\n }\n\n // Gracefully shutdown the plugin\n if (plugin.destroy) {\n this.logger.debug('Destroying plugin', { plugin: pluginName });\n \n await this.raceShutdownTimeout(\n plugin.destroy(),\n config.shutdownTimeout,\n 'Shutdown timeout'\n );\n this.logger.debug('Plugin destroyed successfully', { plugin: pluginName });\n }\n\n // At this point, the kernel would reload the plugin module\n // This would be handled by the plugin loader\n this.logger.debug('Plugin module would be reloaded here', { plugin: pluginName });\n\n // Restore state if we saved it\n if (snapshotId && config.preserveState) {\n const restoredState = await this.stateManager.restoreState(pluginName, snapshotId);\n if (restoredState) {\n restorePluginState(restoredState);\n this.logger.debug('Plugin state restored', { plugin: pluginName });\n }\n }\n\n // Call after reload hooks\n if (config.afterReload) {\n this.logger.debug('Executing after reload hooks', { \n plugin: pluginName,\n hooks: config.afterReload \n });\n // Hook execution would be done through kernel's hook system\n }\n\n this.logger.info('Hot reload completed successfully', { plugin: pluginName });\n return true;\n } catch (error) {\n this.logger.error('Hot reload failed', { \n plugin: pluginName, \n error \n });\n return false;\n }\n }\n\n /**\n * Race a plugin's `destroy()` against its shutdown-timeout guard, and\n * reclaim the guard the moment the race settles (#4952).\n *\n * The guard used to be armed and then abandoned — byte-for-byte the leak\n * #4813 fixed in the kernel's startup guards (PR #4874) and #4875 fixed in\n * the periodic health checks (PR #4950): when `destroy()` won the race, its\n * `setTimeout` stayed ref'd in the event loop for the full\n * `shutdownTimeout`, so a hot reload that finished in milliseconds still\n * pinned the loop for the whole budget — once per reload, per plugin.\n *\n * Clearing on settle rather than `unref()`-ing at arm time is deliberate.\n * An unref'd guard also stops pinning the loop, but it stops being a guard\n * as well: if `destroy()` never settles and nothing else keeps the loop\n * alive, Node exits before the timer can fire and the timeout is never\n * reported. The guard has to stay ref'd exactly as long as the race is\n * undecided, which is what `clearTimeout` in a `finally` expresses.\n *\n * `shutdown` is widened to `T | PromiseLike<T>` because the Plugin contract\n * permits a synchronous `destroy()` (`Promise<void> | void`); such a hook\n * wins the race immediately and the guard is reclaimed on the same turn.\n */\n private async raceShutdownTimeout<T>(\n shutdown: T | PromiseLike<T>,\n timeout: number,\n message: string\n ): Promise<T> {\n let guard: ReturnType<typeof setTimeout> | undefined;\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n guard = setTimeout(() => {\n reject(new Error(message));\n }, timeout);\n });\n\n try {\n return await Promise.race([shutdown, timeoutPromise]);\n } finally {\n clearTimeout(guard);\n }\n }\n\n /**\n * Schedule a reload with debouncing\n */\n scheduleReload(\n pluginName: string,\n reloadFn: () => Promise<void>\n ): void {\n const config = this.reloadConfigs.get(pluginName);\n if (!config) {\n return;\n }\n\n // Clear existing timer\n const existingTimer = this.reloadTimers.get(pluginName);\n if (existingTimer) {\n clearTimeout(existingTimer);\n }\n\n // Schedule new reload with debounce\n const timer = setTimeout(() => {\n this.logger.debug('Debounce period elapsed, executing reload', { \n plugin: pluginName \n });\n reloadFn().catch(error => {\n this.logger.error('Scheduled reload failed', { \n plugin: pluginName, \n error \n });\n });\n this.reloadTimers.delete(pluginName);\n }, config.debounceDelay);\n\n this.reloadTimers.set(pluginName, timer);\n this.logger.debug('Reload scheduled with debounce', { \n plugin: pluginName,\n delay: config.debounceDelay \n });\n }\n\n /**\n * Get state manager for direct access\n */\n getStateManager(): PluginStateManager {\n return this.stateManager;\n }\n\n /**\n * Shutdown hot reload manager\n */\n shutdown(): void {\n // Stop all watching\n for (const pluginName of this.watchHandles.keys()) {\n this.stopWatching(pluginName);\n }\n\n // Clear all timers\n for (const timer of this.reloadTimers.values()) {\n clearTimeout(timer);\n }\n\n this.reloadConfigs.clear();\n this.watchHandles.clear();\n this.reloadTimers.clear();\n this.stateManager.shutdown();\n \n this.logger.info('Hot reload manager shutdown complete');\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { \n SemanticVersion,\n VersionConstraint,\n CompatibilityLevel,\n DependencyConflict\n} from '@objectstack/spec/kernel';\nimport type { ObjectLogger } from './logger.js';\n\n/**\n * Semantic Version Parser and Comparator\n * \n * Implements semantic versioning comparison and constraint matching\n */\nexport class SemanticVersionManager {\n /**\n * Parse a version string into semantic version components\n */\n static parse(versionStr: string): SemanticVersion {\n // Remove 'v' prefix if present\n const cleanVersion = versionStr.replace(/^v/, '');\n \n // Match semver pattern: major.minor.patch[-prerelease][+build]\n const match = cleanVersion.match(\n /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([a-zA-Z0-9.-]+))?(?:\\+([a-zA-Z0-9.-]+))?$/\n );\n\n if (!match) {\n throw new Error(`Invalid semantic version: ${versionStr}`);\n }\n\n return {\n major: parseInt(match[1], 10),\n minor: parseInt(match[2], 10),\n patch: parseInt(match[3], 10),\n preRelease: match[4],\n build: match[5],\n };\n }\n\n /**\n * Convert semantic version back to string\n */\n static toString(version: SemanticVersion): string {\n let str = `${version.major}.${version.minor}.${version.patch}`;\n if (version.preRelease) {\n str += `-${version.preRelease}`;\n }\n if (version.build) {\n str += `+${version.build}`;\n }\n return str;\n }\n\n /**\n * Compare two semantic versions\n * Returns: -1 if a < b, 0 if a === b, 1 if a > b\n */\n static compare(a: SemanticVersion, b: SemanticVersion): number {\n // Compare major, minor, patch\n if (a.major !== b.major) return a.major - b.major;\n if (a.minor !== b.minor) return a.minor - b.minor;\n if (a.patch !== b.patch) return a.patch - b.patch;\n\n // Pre-release versions have lower precedence\n if (a.preRelease && !b.preRelease) return -1;\n if (!a.preRelease && b.preRelease) return 1;\n \n // Compare pre-release versions\n if (a.preRelease && b.preRelease) {\n return a.preRelease.localeCompare(b.preRelease);\n }\n\n return 0;\n }\n\n /**\n * Check if version satisfies constraint\n */\n static satisfies(version: SemanticVersion, constraint: VersionConstraint): boolean {\n const constraintStr = constraint as string;\n\n // Any version\n if (constraintStr === '*' || constraintStr === 'latest') {\n return true;\n }\n\n // Exact version\n if (/^[\\d.]+$/.test(constraintStr)) {\n const exact = this.parse(constraintStr);\n return this.compare(version, exact) === 0;\n }\n\n // Caret range (^): Compatible with version\n if (constraintStr.startsWith('^')) {\n const base = this.parse(constraintStr.slice(1));\n return (\n version.major === base.major &&\n this.compare(version, base) >= 0\n );\n }\n\n // Tilde range (~): Approximately equivalent\n if (constraintStr.startsWith('~')) {\n const base = this.parse(constraintStr.slice(1));\n return (\n version.major === base.major &&\n version.minor === base.minor &&\n this.compare(version, base) >= 0\n );\n }\n\n // Greater than or equal\n if (constraintStr.startsWith('>=')) {\n const base = this.parse(constraintStr.slice(2));\n return this.compare(version, base) >= 0;\n }\n\n // Greater than\n if (constraintStr.startsWith('>')) {\n const base = this.parse(constraintStr.slice(1));\n return this.compare(version, base) > 0;\n }\n\n // Less than or equal\n if (constraintStr.startsWith('<=')) {\n const base = this.parse(constraintStr.slice(2));\n return this.compare(version, base) <= 0;\n }\n\n // Less than\n if (constraintStr.startsWith('<')) {\n const base = this.parse(constraintStr.slice(1));\n return this.compare(version, base) < 0;\n }\n\n // Range (1.2.3 - 2.3.4)\n const rangeMatch = constraintStr.match(/^([\\d.]+)\\s*-\\s*([\\d.]+)$/);\n if (rangeMatch) {\n const min = this.parse(rangeMatch[1]);\n const max = this.parse(rangeMatch[2]);\n return this.compare(version, min) >= 0 && this.compare(version, max) <= 0;\n }\n\n return false;\n }\n\n /**\n * Determine compatibility level between two versions\n */\n static getCompatibilityLevel(from: SemanticVersion, to: SemanticVersion): CompatibilityLevel {\n const cmp = this.compare(from, to);\n\n // Same version\n if (cmp === 0) {\n return 'fully-compatible';\n }\n\n // Major version changed - breaking changes\n if (from.major !== to.major) {\n return 'breaking-changes';\n }\n\n // Minor version increased - backward compatible\n if (from.minor < to.minor) {\n return 'backward-compatible';\n }\n\n // Patch version increased - fully compatible\n if (from.patch < to.patch) {\n return 'fully-compatible';\n }\n\n // Downgrade - incompatible\n return 'incompatible';\n }\n}\n\n/**\n * Plugin Dependency Resolver\n * \n * Resolves plugin dependencies using topological sorting and conflict detection\n */\nexport class DependencyResolver {\n private logger: ObjectLogger;\n\n constructor(logger: ObjectLogger) {\n this.logger = logger.child({ component: 'DependencyResolver' });\n }\n\n /**\n * Resolve dependencies using topological sort\n */\n resolve(\n plugins: Map<string, { version?: string; dependencies?: string[] }>\n ): string[] {\n const graph = new Map<string, string[]>();\n const inDegree = new Map<string, number>();\n\n // Build dependency graph\n for (const [pluginName, pluginInfo] of plugins) {\n if (!graph.has(pluginName)) {\n graph.set(pluginName, []);\n inDegree.set(pluginName, 0);\n }\n\n const deps = pluginInfo.dependencies || [];\n for (const dep of deps) {\n // Check if dependency exists\n if (!plugins.has(dep)) {\n throw new Error(`Missing dependency: ${pluginName} requires ${dep}`);\n }\n\n // Add edge\n if (!graph.has(dep)) {\n graph.set(dep, []);\n inDegree.set(dep, 0);\n }\n graph.get(dep)!.push(pluginName);\n inDegree.set(pluginName, (inDegree.get(pluginName) || 0) + 1);\n }\n }\n\n // Topological sort using Kahn's algorithm\n const queue: string[] = [];\n const result: string[] = [];\n\n // Add all nodes with no incoming edges\n for (const [node, degree] of inDegree) {\n if (degree === 0) {\n queue.push(node);\n }\n }\n\n while (queue.length > 0) {\n const node = queue.shift()!;\n result.push(node);\n\n // Reduce in-degree for dependent nodes\n const dependents = graph.get(node) || [];\n for (const dependent of dependents) {\n const newDegree = (inDegree.get(dependent) || 0) - 1;\n inDegree.set(dependent, newDegree);\n \n if (newDegree === 0) {\n queue.push(dependent);\n }\n }\n }\n\n // Check for circular dependencies\n if (result.length !== plugins.size) {\n const remaining = Array.from(plugins.keys()).filter(p => !result.includes(p));\n this.logger.error('Circular dependency detected', { remaining });\n throw new Error(`Circular dependency detected among: ${remaining.join(', ')}`);\n }\n\n this.logger.debug('Dependencies resolved', { order: result });\n return result;\n }\n\n /**\n * Detect dependency conflicts\n */\n detectConflicts(\n plugins: Map<string, { version: string; dependencies?: Record<string, VersionConstraint> }>\n ): DependencyConflict[] {\n const conflicts: DependencyConflict[] = [];\n const versionRequirements = new Map<string, Map<string, VersionConstraint>>();\n\n // Collect all version requirements\n for (const [pluginName, pluginInfo] of plugins) {\n if (!pluginInfo.dependencies) continue;\n\n for (const [depName, constraint] of Object.entries(pluginInfo.dependencies)) {\n if (!versionRequirements.has(depName)) {\n versionRequirements.set(depName, new Map());\n }\n versionRequirements.get(depName)!.set(pluginName, constraint);\n }\n }\n\n // Check for version mismatches\n for (const [depName, requirements] of versionRequirements) {\n const depInfo = plugins.get(depName);\n if (!depInfo) continue;\n\n const depVersion = SemanticVersionManager.parse(depInfo.version);\n const unsatisfied: Array<{ pluginId: string; version: string }> = [];\n\n for (const [requiringPlugin, constraint] of requirements) {\n if (!SemanticVersionManager.satisfies(depVersion, constraint)) {\n unsatisfied.push({\n pluginId: requiringPlugin,\n version: constraint as string,\n });\n }\n }\n\n if (unsatisfied.length > 0) {\n conflicts.push({\n type: 'version-mismatch',\n severity: 'error',\n description: `Version mismatch for ${depName}: detected ${unsatisfied.length} unsatisfied requirements`,\n plugins: [\n { pluginId: depName, version: depInfo.version },\n ...unsatisfied,\n ],\n resolutions: [{\n strategy: 'upgrade',\n description: `Upgrade ${depName} to satisfy all constraints`,\n targetPlugins: [depName],\n automatic: false,\n } as any],\n });\n }\n }\n\n // Check for circular dependencies (will be caught by resolve())\n try {\n this.resolve(new Map(\n Array.from(plugins.entries()).map(([name, info]) => [\n name,\n { version: info.version, dependencies: info.dependencies ? Object.keys(info.dependencies) : [] }\n ])\n ));\n } catch (error) {\n if (error instanceof Error && error.message.includes('Circular dependency')) {\n conflicts.push({\n type: 'circular-dependency',\n severity: 'critical',\n description: error.message,\n plugins: [], // Would need to extract from error\n resolutions: [{\n strategy: 'manual',\n description: 'Remove circular dependency by restructuring plugins',\n automatic: false,\n } as any],\n });\n }\n }\n\n return conflicts;\n }\n\n /**\n * Find best version that satisfies all constraints\n */\n findBestVersion(\n availableVersions: string[],\n constraints: VersionConstraint[]\n ): string | undefined {\n // Parse and sort versions (highest first)\n const versions = availableVersions\n .map(v => ({ str: v, parsed: SemanticVersionManager.parse(v) }))\n .sort((a, b) => -SemanticVersionManager.compare(a.parsed, b.parsed));\n\n // Find highest version that satisfies all constraints\n for (const version of versions) {\n const satisfiesAll = constraints.every(constraint =>\n SemanticVersionManager.satisfies(version.parsed, constraint)\n );\n\n if (satisfiesAll) {\n return version.str;\n }\n }\n\n return undefined;\n }\n\n /**\n * Check if dependencies form a valid DAG (no cycles)\n */\n isAcyclic(dependencies: Map<string, string[]>): boolean {\n try {\n const plugins = new Map(\n Array.from(dependencies.entries()).map(([name, deps]) => [\n name,\n { dependencies: deps }\n ])\n );\n this.resolve(plugins);\n return true;\n } catch {\n return false;\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { ObjectLogger } from './logger.js';\n\n/**\n * Namespace entry representing an object/view/flow etc. registered by a package.\n */\nexport interface NamespaceEntry {\n /** The namespace path (e.g. \"objects.project_task\", \"views.task_list\") */\n namespace: string;\n /** The package that owns this namespace */\n packageId: string;\n /** When this entry was registered */\n registeredAt: string;\n}\n\n/**\n * Result of a namespace conflict check.\n */\nexport interface NamespaceConflict {\n /** The conflicting namespace path */\n namespace: string;\n /** The package that currently owns this namespace */\n existingPackageId: string;\n /** The package attempting to register the same namespace */\n incomingPackageId: string;\n /** A suggested alternative name to avoid the conflict */\n suggestion?: string;\n}\n\n/**\n * Result of namespace availability check.\n */\nexport interface NamespaceCheckResult {\n /** Whether all requested namespaces are available */\n available: boolean;\n /** List of conflicts detected */\n conflicts: NamespaceConflict[];\n /** Suggested alternatives for each conflict */\n suggestions: Record<string, string>;\n}\n\n/**\n * Namespace Resolver\n *\n * Manages namespace registration for installed packages and detects collisions\n * during install-time. Each metadata item (object, view, flow, page, etc.)\n * produces a namespace like `objects.<name>` or `views.<name>`.\n *\n * When a new package declares objects, views, or other metadata that would\n * collide with an existing package's metadata, this resolver reports the\n * conflicts and suggests prefixed alternatives.\n */\nexport class NamespaceResolver {\n private logger: ObjectLogger;\n private registry: Map<string, NamespaceEntry> = new Map();\n\n constructor(logger: ObjectLogger) {\n this.logger = logger.child({ component: 'NamespaceResolver' });\n }\n\n /**\n * Register namespaces owned by a package.\n */\n register(packageId: string, namespaces: string[]): void {\n const now = new Date().toISOString();\n for (const ns of namespaces) {\n if (this.registry.has(ns)) {\n const existing = this.registry.get(ns)!;\n if (existing.packageId !== packageId) {\n this.logger.warn('Overwriting namespace entry', { namespace: ns, existing: existing.packageId, incoming: packageId });\n }\n }\n this.registry.set(ns, { namespace: ns, packageId, registeredAt: now });\n this.logger.debug('Namespace registered', { namespace: ns, packageId });\n }\n }\n\n /**\n * Unregister all namespaces belonging to a package.\n */\n unregister(packageId: string): string[] {\n const removed: string[] = [];\n for (const [ns, entry] of this.registry) {\n if (entry.packageId === packageId) {\n this.registry.delete(ns);\n removed.push(ns);\n }\n }\n this.logger.debug('Namespaces unregistered', { packageId, count: removed.length });\n return removed;\n }\n\n /**\n * Check whether a set of namespaces is available for a given package.\n */\n checkAvailability(packageId: string, namespaces: string[]): NamespaceCheckResult {\n const conflicts: NamespaceConflict[] = [];\n const suggestions: Record<string, string> = {};\n\n for (const ns of namespaces) {\n const existing = this.registry.get(ns);\n if (existing && existing.packageId !== packageId) {\n const suggestion = this.suggestAlternative(ns, packageId);\n conflicts.push({\n namespace: ns,\n existingPackageId: existing.packageId,\n incomingPackageId: packageId,\n suggestion,\n });\n suggestions[ns] = suggestion;\n }\n }\n\n return {\n available: conflicts.length === 0,\n conflicts,\n suggestions,\n };\n }\n\n /**\n * Extract namespace strings from a package's metadata definition.\n */\n extractNamespaces(config: Record<string, unknown>): string[] {\n const namespaces: string[] = [];\n const categories = [\n 'objects', 'views', 'pages', 'flows', 'workflows',\n 'apps', 'dashboards', 'reports', 'actions', 'agents',\n ];\n\n for (const category of categories) {\n const items = config[category];\n if (Array.isArray(items)) {\n for (const item of items) {\n const name = (item as Record<string, unknown>)?.name;\n if (typeof name === 'string') {\n namespaces.push(`${category}.${name}`);\n }\n }\n } else if (items && typeof items === 'object') {\n for (const key of Object.keys(items as object)) {\n namespaces.push(`${category}.${key}`);\n }\n }\n }\n\n return namespaces;\n }\n\n /**\n * Get all registered entries.\n */\n getRegistry(): ReadonlyMap<string, NamespaceEntry> {\n return this.registry;\n }\n\n /**\n * Get all namespaces belonging to a specific package.\n */\n getPackageNamespaces(packageId: string): string[] {\n const namespaces: string[] = [];\n for (const [ns, entry] of this.registry) {\n if (entry.packageId === packageId) {\n namespaces.push(ns);\n }\n }\n return namespaces;\n }\n\n /**\n * Generate a prefixed alternative namespace to avoid conflicts.\n */\n private suggestAlternative(ns: string, packageId: string): string {\n // Extract the short package name for prefixing\n const shortName = packageId\n .replace(/^@[^/]+\\//, '')\n .replace(/^plugin-/, '')\n .replace(/-/g, '_');\n\n const parts = ns.split('.');\n if (parts.length >= 2) {\n // e.g. \"objects.task\" → \"objects.crm_task\"\n return `${parts[0]}.${shortName}_${parts.slice(1).join('.')}`;\n }\n return `${shortName}_${ns}`;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACiEO,SAAS,mBAA8C,SAA8B;AACxF,QAAM,WAAgB,CAAC;AACvB,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,WAAW,oBAAI,IAAY;AAEjC,QAAM,QAAQ,CAAC,eAAuB;AAClC,QAAI,QAAQ,IAAI,UAAU,EAAG;AAE7B,QAAI,SAAS,IAAI,UAAU,GAAG;AAC1B,YAAM,IAAI,MAAM,0CAA0C,UAAU,EAAE;AAAA,IAC1E;AAEA,UAAM,SAAS,QAAQ,IAAI,UAAU;AACrC,QAAI,CAAC,QAAQ;AACT,YAAM,IAAI,MAAM,oBAAoB,UAAU,aAAa;AAAA,IAC/D;AAEA,aAAS,IAAI,UAAU;AAEvB,eAAW,OAAO,OAAO,gBAAgB,CAAC,GAAG;AACzC,UAAI,CAAC,QAAQ,IAAI,GAAG,GAAG;AACnB,cAAM,IAAI;AAAA,UACN,wBAAwB,GAAG,2BAA2B,UAAU;AAAA,QACpE;AAAA,MACJ;AACA,YAAM,GAAG;AAAA,IACb;AACA,eAAW,OAAO,OAAO,wBAAwB,CAAC,GAAG;AACjD,UAAI,QAAQ,IAAI,GAAG,EAAG,OAAM,GAAG;AAAA,IACnC;AAEA,aAAS,OAAO,UAAU;AAC1B,YAAQ,IAAI,UAAU;AACtB,aAAS,KAAK,MAAM;AAAA,EACxB;AAEA,aAAW,cAAc,QAAQ,KAAK,GAAG;AACrC,UAAM,UAAU;AAAA,EACpB;AAEA,SAAO;AACX;AAeO,SAAS,4BACZ,SACA,qBACI;AACJ,QAAM,eAAe,oBAAI,IAA8C;AACvE,UAAQ,QAAQ,CAAC,QAAQ,SAAS;AAC9B,eAAW,WAAW,OAAO,oBAAoB,CAAC,GAAG;AACjD,UAAI,CAAC,aAAa,IAAI,OAAO,GAAG;AAC5B,qBAAa,IAAI,SAAS,EAAE,QAAQ,OAAO,MAAM,KAAK,CAAC;AAAA,MAC3D;AAAA,IACJ;AAAA,EACJ,CAAC;AAED,QAAM,aAAuB,CAAC;AAC9B,UAAQ,QAAQ,CAAC,QAAQ,SAAS;AAC9B,eAAW,WAAW,OAAO,oBAAoB,CAAC,GAAG;AACjD,UAAI,oBAAoB,OAAO,EAAG;AAClC,YAAM,WAAW,aAAa,IAAI,OAAO;AACzC,UAAI,YAAY,SAAS,OAAO,MAAM;AAClC,mBAAW;AAAA,UACP,IAAI,OAAO,IAAI,uBAAuB,OAAO,uBAAuB,OAAO,qBAC3D,SAAS,MAAM,oCAAoC,SAAS,IAAI,OAAO,IAAI,2DACxC,SAAS,MAAM,SAC9D,OAAO,IAAI;AAAA,QAEnB;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ,CAAC;AAED,MAAI,WAAW,SAAS,GAAG;AACvB,UAAM,IAAI;AAAA,MACN;AAAA,MAA4D,WAAW,KAAK,QAAQ,CAAC;AAAA,IACzF;AAAA,EACJ;AACJ;AASO,SAAS,uBACZ,uBACA,SACA,aACM;AACN,MAAI,CAAC,sBAAuB,QAAO;AACnC,MAAI,eAAe;AACnB,aAAW,UAAU,SAAS;AAC1B,QAAI,OAAO,kBAAkB,SAAS,WAAW,GAAG;AAChD,qBAAe,KAAK,WAAW,qCAAqC,OAAO,IAAI;AAE/E;AAAA,IACJ;AAAA,EACJ;AACA,SAAO,mBAAmB,qBAAqB,iEAClB,YAAY;AAC7C;AAUO,SAAS,8BACZ,QACA,qBACI;AACJ,aAAW,WAAW,OAAO,oBAAoB,CAAC,GAAG;AACjD,QAAI,oBAAoB,OAAO,EAAG;AAClC,UAAM,IAAI;AAAA,MACN,oBAAoB,OAAO,IAAI,uBAAuB,OAAO,8MAGzD,OAAO;AAAA,IAEf;AAAA,EACJ;AACJ;;;AC3HA,SAAS,cAAc,MAAc,UAAkC,QAAkC;AACrG,SAAO,MAAM,oBAAoB,IAAI,IAAI;AAAA,IACrC,MAAM;AAAA,IACN,cAAc,SAAS;AAAA,EAC3B,CAAC;AACL;AAsBA,eAAsB,sBAClB,MACA,UACA,QACA,OAAuB,CAAC,GACX;AACb,gBAAc,MAAM,UAAU,MAAM;AAEpC,aAAW,WAAW,UAAU;AAC5B,QAAI;AACA,YAAM,QAAQ,GAAG,IAAI;AAAA,IACzB,SAAS,OAAO;AACZ,aAAO,MAAM,wBAAwB,IAAI,IAAI,KAAc;AAAA,IAE/D;AAAA,EACJ;AACJ;AAoBA,eAAsB,wBAClB,MACA,UACA,QACA,OAAuB,CAAC,GACX;AACb,MAAI,OAAQ,eAAc,MAAM,UAAU,MAAM;AAEhD,aAAW,WAAW,UAAU;AAC5B,UAAM,QAAQ,GAAG,IAAI;AAAA,EACzB;AACJ;;;AC9HO,IAAe,mBAAf,MAAgC;AAAA,EAcnC,YAAY,QAAgB;AAb5B,SAAU,UAA+B,oBAAI,IAAI;AACjD,SAAU,WAAgD,oBAAI,IAAI;AAClE,SAAU,QAAsE,oBAAI,IAAI;AACxF,SAAU,QAAqB;AAW3B,SAAK,SAAS;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,cAAc,eAAkC;AACtD,QAAI,KAAK,UAAU,eAAe;AAC9B,YAAM,IAAI;AAAA,QACN,qCAAqC,aAAa,WAAW,KAAK,KAAK;AAAA,MAC3E;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKU,eAAqB;AAC3B,QAAI,KAAK,UAAU,QAAQ;AACvB,YAAM,IAAI,MAAM,8DAA8D;AAAA,IAClF;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,gBAA+B;AACrC,WAAO;AAAA,MACH,iBAAiB,CAAC,MAAM,YAAY;AAChC,YAAI,KAAK,oBAAoB,KAAK;AAC9B,cAAI,KAAK,SAAS,IAAI,IAAI,GAAG;AACzB,kBAAM,IAAI,MAAM,qBAAqB,IAAI,sBAAsB;AAAA,UACnE;AACA,eAAK,SAAS,IAAI,MAAM,OAAO;AAAA,QACnC,OAAO;AAEH,eAAK,SAAS,SAAS,MAAM,OAAO;AAAA,QACxC;AACA,aAAK,OAAO,KAAK,YAAY,IAAI,gBAAgB,EAAE,SAAS,KAAK,CAAC;AAAA,MACtE;AAAA,MACA,YAAY,CAAI,SAAoB;AAChC,YAAI,KAAK,oBAAoB,KAAK;AAC9B,gBAAM,UAAU,KAAK,SAAS,IAAI,IAAI;AACtC,cAAI,CAAC,SAAS;AACV,kBAAM,IAAI;AAAA,cACN,qBAAqB,IAAI,cAAc,KAAK,uBAAuB,IAAI,CAAC;AAAA,YAC5E;AAAA,UACJ;AACA,iBAAO;AAAA,QACX,OAAO;AAEH,iBAAO,KAAK,SAAS,IAAO,IAAI;AAAA,QACpC;AAAA,MACJ;AAAA,MACA,gBAAgB,CAAI,MAAc,mBAA4B;AAC1D,YAAI,KAAK,oBAAoB,KAAK;AAC9B,cAAI,CAAC,KAAK,SAAS,IAAI,IAAI,GAAG;AAC1B,kBAAM,IAAI,MAAM,qBAAqB,IAAI,yDAAyD;AAAA,UACtG;AACA,eAAK,SAAS,IAAI,MAAM,cAAc;AAAA,QAC1C,OAAO;AAEH,cAAI,CAAC,KAAK,SAAS,IAAI,IAAI,GAAG;AAC1B,kBAAM,IAAI,MAAM,qBAAqB,IAAI,yDAAyD;AAAA,UACtG;AACA,eAAK,SAAS,SAAS,MAAM,cAAc;AAAA,QAC/C;AACA,aAAK,OAAO,KAAK,YAAY,IAAI,cAAc,EAAE,SAAS,KAAK,CAAC;AAAA,MACpE;AAAA,MACA,MAAM,CAAC,MAAM,YAAY;AACrB,YAAI,CAAC,KAAK,MAAM,IAAI,IAAI,GAAG;AACvB,eAAK,MAAM,IAAI,MAAM,CAAC,CAAC;AAAA,QAC3B;AACA,aAAK,MAAM,IAAI,IAAI,EAAG,KAAK,OAAO;AAAA,MACtC;AAAA;AAAA;AAAA;AAAA,MAIA,SAAS,OAAO,SAAS,SAAS;AAC9B,cAAM,wBAAwB,MAAM,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,GAAG,QAAW,IAAI;AAAA,MACnF;AAAA,MACA,aAAa,MAAM;AACf,YAAI,KAAK,oBAAoB,KAAK;AAC9B,iBAAO,IAAI,IAAI,KAAK,QAAQ;AAAA,QAChC,OAAO;AAGH,iBAAO,oBAAI,IAAI;AAAA,QACnB;AAAA,MACJ;AAAA,MACA,QAAQ,KAAK;AAAA,MACb,WAAW,MAAM;AAAA,MACjB,wBAAwB,CAAC,OAAO,UAAU,YAAY,kBAAkB;AACpE,cAAM,IAAI,MAAM,2EAAsE;AAAA,MAC1F;AAAA,MACA,kBAAkB,OAAU,OAAe,aAAiC;AACxE,cAAM,IAAI,MAAM,qEAAgE;AAAA,MACpF;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,sBAAgC;AACtC,WAAO,mBAAmB,KAAK,OAAO;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,qBAAqB,MAAuB;AAElD,WAAO,KAAK,SAAS,IAAI,IAAI;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOU,qBAAqB,SAAyB;AACpD,gCAA4B,SAAS,CAAC,SAAS,KAAK,qBAAqB,IAAI,CAAC;AAAA,EAClF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,uBAAuB,aAA6B;AAC1D,WAAO,uBAAuB,KAAK,uBAAuB,KAAK,QAAQ,OAAO,GAAG,WAAW;AAAA,EAChG;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAgB,cAAc,QAA+B;AACzD,UAAM,aAAa,OAAO;AAC1B,SAAK,OAAO,KAAK,wBAAwB,UAAU,EAAE;AAIrD,kCAA8B,QAAQ,CAAC,SAAS,KAAK,qBAAqB,IAAI,CAAC;AAE/E,SAAK,wBAAwB;AAC7B,QAAI;AACA,YAAM,OAAO,KAAK,KAAK,OAAO;AAC9B,WAAK,OAAO,KAAK,uBAAuB,UAAU,EAAE;AAAA,IACxD,SAAS,OAAO;AACZ,WAAK,OAAO,MAAM,uBAAuB,UAAU,IAAI,KAAc;AACrE,YAAM;AAAA,IACV,UAAE;AACE,WAAK,wBAAwB;AAAA,IACjC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAgB,eAAe,QAA+B;AAC1D,QAAI,CAAC,OAAO,MAAO;AAEnB,UAAM,aAAa,OAAO;AAC1B,SAAK,OAAO,KAAK,oBAAoB,UAAU,EAAE;AAEjD,QAAI;AACA,YAAM,OAAO,MAAM,KAAK,OAAO;AAC/B,WAAK,OAAO,KAAK,mBAAmB,UAAU,EAAE;AAAA,IACpD,SAAS,OAAO;AACZ,WAAK,OAAO,MAAM,wBAAwB,UAAU,IAAI,KAAc;AACtE,YAAM;AAAA,IACV;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAgB,iBAAiB,QAA+B;AAC5D,QAAI,CAAC,OAAO,QAAS;AAErB,UAAM,aAAa,OAAO;AAC1B,SAAK,OAAO,KAAK,sBAAsB,UAAU,EAAE;AAEnD,QAAI;AACA,YAAM,OAAO,QAAQ;AACrB,WAAK,OAAO,KAAK,qBAAqB,UAAU,EAAE;AAAA,IACtD,SAAS,OAAO;AACZ,WAAK,OAAO,MAAM,0BAA0B,UAAU,IAAI,KAAc;AACxE,YAAM;AAAA,IACV;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0BA,MAAgB,YAAY,SAAiB,MAA4B;AACrE,UAAM,sBAAsB,MAAM,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI;AAAA,EACnF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4CA,MAAgB,mBAAmB,SAAiB,MAA4B;AAC5E,UAAM,wBAAwB,MAAM,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI;AAAA,EACrF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAwB;AACpB,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,aAAkC;AAC9B,WAAO,IAAI,IAAI,KAAK,OAAO;AAAA,EAC/B;AAQJ;;;AC7UA,IAAM,cAAwC;AAAA,EAC1C,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AACZ;AAEA,IAAM,eAAyC;AAAA,EAC3C,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AACZ;AAEA,IAAM,QAAQ;AAYd,SAAS,kBAAkB,MAAwB;AAC/C,SAAO,KACF,QAAQ,sBAAsB,OAAO,EACrC,QAAQ,yBAAyB,OAAO,EACxC,QAAQ,sBAAsB,OAAO,EACrC,MAAM,eAAe,EACrB,OAAO,OAAO,EACd,IAAI,CAAC,SAAS,KAAK,YAAY,CAAC;AACzC;AAQA,SAAS,gBAAgB,MAAsB;AAC3C,MAAI,gBAAgB,KAAK,IAAI,EAAG,QAAO;AACvC,MAAI,qBAAqB,KAAK,IAAI,EAAG,QAAO,KAAK,MAAM,GAAG,EAAE;AAC5D,MAAI,aAAa,KAAK,IAAI,EAAG,QAAO,KAAK,MAAM,GAAG,EAAE;AACpD,SAAO;AACX;AAuBA,IAAM,iCAAiC,oBAAI,IAAI;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,CAAC;AAGD,SAAS,yBAAyB,MAAc,YAA6B;AACzE,aAAW,QAAQ,CAAC,MAAM,gBAAgB,IAAI,CAAC,GAAG;AAC9C,QAAI,KAAK,UAAU,WAAW,UAAU,CAAC,KAAK,SAAS,UAAU,EAAG;AACpE,QAAI,+BAA+B,IAAI,KAAK,MAAM,GAAG,KAAK,SAAS,WAAW,MAAM,CAAC,EAAG,QAAO;AAAA,EACnG;AACA,SAAO;AACX;AAGA,SAAS,gBAAgB,OAAiB,KAAwB;AAC9D,WAAS,IAAI,GAAG,IAAI,IAAI,UAAU,MAAM,QAAQ,KAAK;AACjD,QAAI,IAAI,MAAM,CAAC,MAAM,WAAW,MAAM,IAAI,MAAM,MAAM,IAAI,EAAG,QAAO;AAAA,EACxE;AACA,SAAO;AACX;AAaA,SAAS,uBAAuB,WAAqB,cAAiC;AAClF,MAAI,aAAa,WAAW,KAAK,UAAU,WAAW,EAAG,QAAO;AAIhE,MAAI,aAAa,SAAS,GAAG;AACzB,UAAM,QAAQ,aAAa,KAAK,EAAE;AAClC,WACI,gBAAgB,WAAW,YAAY,KACvC,UAAU,KAAK,CAAC,SAAS,SAAS,SAAS,gBAAgB,IAAI,MAAM,KAAK;AAAA,EAElF;AAEA,QAAM,aAAa,aAAa,CAAC;AACjC,QAAM,aAAa,UAAU,OAAO,CAAC,SAAS,QAAQ,KAAK,IAAI,CAAC,EAAE,SAAS;AAC3E,SAAO,UAAU;AAAA,IACb,CAAC,SACG,SAAS,cACR,cAAc,gBAAgB,IAAI,MAAM,cACzC,yBAAyB,MAAM,UAAU;AAAA,EACjD;AACJ;AAUA,SAAS,aAAa,QAAkD;AACpE,MAAI,OAAO,YAAY,aAAa;AAChC,UAAM,UAAW,QAAgB,KAAK;AACtC,QAAI,YAAY,UAAa,YAAY,GAAI,QAAO;AAAA,EACxD;AACA,SAAO,QAAQ,QAAQ,KAAK;AAChC;AAaA,SAAS,gBAAmB,IAA2B;AACnD,MAAI,OAAO,YAAY,YAAa,QAAO;AAE3C,QAAM,mBAAoB,QAA2D;AACrF,MAAI,OAAO,qBAAqB,YAAY;AACxC,QAAI;AACA,aAAO,iBAAiB,KAAK,SAAS,QAAQ,EAAE,EAAE;AAAA,IACtD,QAAQ;AACJ,aAAO;AAAA,IACX;AAAA,EACJ;AAKA,MAAI;AACA,WAAO,QAAQ,EAAE;AAAA,EACrB,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAEO,IAAM,eAAN,MAAM,cAA+B;AAAA,EAcxC,YAAY,SAAgC,CAAC,GAAG,WAAgC,CAAC,GAAG;AAHpF;AAAA,SAAQ,iBAAiB;AACzB,SAAQ,sBAAsB;AAG1B,SAAK,SAAS;AAAA,MACV,MAAM,OAAO;AAAA,MACb,OAAO,OAAO,SAAS;AAAA,MACvB,QAAQ,OAAO,UAAU;AAAA,MACzB,QAAQ,OAAO,UAAU,CAAC,YAAY,SAAS,UAAU,KAAK;AAAA,MAC9D,gBAAgB,OAAO,kBAAkB;AAAA,MACzC,MAAM,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMb,UAAU;AAAA,QACN,SAAS,OAAO,UAAU,WAAW;AAAA,QACrC,UAAU,OAAO,UAAU,YAAY;AAAA,MAC3C;AAAA,IACJ;AACA,SAAK,WAAW;AAChB,SAAK,iBAAiB,KAAK,OAAO,OAAO,IAAI,iBAAiB,EAAE,OAAO,CAAC,UAAU,MAAM,SAAS,CAAC;AAElG,QAAI,KAAK,OAAO,QAAQ,OAAO,YAAY,aAAa;AACpD,WAAK,eAAe,KAAK,OAAO,IAAI;AAAA,IACxC;AAAA,EACJ;AAAA,EAEQ,eAAe,MAAc;AACjC,UAAM,KAAK,gBAA0C,IAAI;AACzD,UAAMA,YAAW,gBAA4C,MAAM;AACnE,QAAI,CAAC,MAAM,CAACA,WAAU;AAClB,WAAK,mBAAmB,MAAM,sCAAsC;AACpE;AAAA,IACJ;AAEA,QAAI;AACA,SAAG,UAAUA,UAAS,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AACxD,YAAM,SAAS,GAAG,kBAAkB,MAAM,EAAE,OAAO,IAAI,CAAC;AAKxD,aAAO,GAAG,SAAS,CAAC,QAAe,KAAK,mBAAmB,MAAM,IAAI,OAAO,CAAC;AAC7E,WAAK,aAAa;AAClB,WAAK,iBAAiB;AAAA,IAC1B,SAAS,KAAK;AACV,WAAK,mBAAmB,MAAO,IAAc,OAAO;AAAA,IACxD;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,mBAAmB,MAAc,QAAgB;AACrD,SAAK,aAAa;AAClB,SAAK,iBAAiB;AACtB,QAAI,KAAK,oBAAqB;AAC9B,SAAK,sBAAsB;AAE3B,UAAM,QAAQ,KAAK,OAAO,OAAO,IAAI,KAAK,OAAO,IAAI,OAAO;AAC5D,UAAM,SAAS,GAAG,KAAK,wDAAmD,IAAI,KAAK,MAAM;AACzF,QAAI,OAAO,YAAY,eAAgB,QAAgB,QAAQ;AAC3D,MAAC,QAAgB,OAAO,MAAM,SAAS,IAAI;AAAA,IAC/C,WAAW,OAAO,YAAY,aAAa;AACvC,cAAQ,KAAK,MAAM;AAAA,IACvB;AAAA,EACJ;AAAA,EAEQ,UAAU,OAA0B;AACxC,WAAO,YAAY,KAAK,KAAK,YAAY,KAAK,OAAO,KAAK;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYQ,oBAAoB,KAAsB;AAC9C,UAAM,YAAY,kBAAkB,GAAG;AACvC,WAAO,KAAK,eAAe,KAAK,CAAC,YAAY,uBAAuB,WAAW,OAAO,CAAC;AAAA,EAC3F;AAAA,EAEQ,gBAAgB,KAAe;AACnC,QAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,UAAM,WAAW,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,GAAG,IAAI,EAAE,GAAG,IAAI;AAC1D,eAAW,OAAO,UAAU;AACxB,UAAI,KAAK,oBAAoB,GAAG,GAAG;AAC/B,iBAAS,GAAG,IAAI;AAAA,MACpB,WAAW,OAAO,SAAS,GAAG,MAAM,YAAY,SAAS,GAAG,MAAM,MAAM;AACpE,iBAAS,GAAG,IAAI,KAAK,gBAAgB,SAAS,GAAG,CAAC;AAAA,MACtD;AAAA,IACJ;AACA,WAAO;AAAA,EACX;AAAA,EAEQ,MAAM,OAAiB,SAAiB,MAA4B,OAAe;AACvF,QAAI,CAAC,KAAK,UAAU,KAAK,EAAG;AAE5B,UAAM,UAAU,KAAK,gBAAgB;AAAA,MACjC,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,MACH,GAAI,QAAQ,EAAE,OAAO,EAAE,SAAS,MAAM,SAAS,OAAO,MAAM,MAAM,EAAE,IAAI,CAAC;AAAA,IAC7E,CAAC;AAED,UAAM,aAAa,OAAO,KAAK,OAAO,EAAE,SAAS;AACjD,UAAM,MAAK,oBAAI,KAAK,GAAE,YAAY;AAElC,UAAM,eAAe,UAAU,WAAW,UAAU;AACpD,UAAM,OAAO,OAAO,YAAY,cAAe,UAAkB;AACjE,UAAM,SAAS,OAAQ,eAAe,KAAK,SAAS,KAAK,SAAU;AAEnE,QAAI;AACJ,QAAI;AAEJ,QAAI,KAAK,OAAO,WAAW,QAAQ;AAC/B,aAAO,YAAY,KAAK,UAAU;AAAA,QAC9B,MAAM;AAAA,QACN;AAAA,QACA,GAAI,KAAK,OAAO,OAAO,EAAE,MAAM,KAAK,OAAO,KAAK,IAAI,CAAC;AAAA,QACrD,KAAK;AAAA,QACL,GAAG;AAAA,MACP,CAAC;AAAA,IACL,WAAW,KAAK,OAAO,WAAW,QAAQ;AACtC,YAAM,QAAQ,CAAC,IAAI,MAAM,YAAY,GAAG,OAAO;AAC/C,UAAI,WAAY,OAAM,KAAK,KAAK,UAAU,OAAO,CAAC;AAClD,aAAO,YAAY,MAAM,KAAK,KAAK;AAAA,IACvC,OAAO;AAEH,YAAM,QAAQ,KAAK,OAAO,OAAO,IAAI,KAAK,OAAO,IAAI,OAAO;AAC5D,YAAM,OAAO,GAAG,EAAE,IAAI,MAAM,YAAY,CAAC;AACzC,UAAI,OAAO,IAAI,KAAK,GAAG,OAAO;AAC9B,UAAI,WAAY,SAAQ,IAAI,KAAK,UAAU,OAAO,CAAC;AACnD,kBAAY,OAAO;AACnB,YAAM,QAAQ,aAAa,KAAK,KAAK;AACrC,aAAO,SAAS,aAAa,MAAM,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,KAAK;AAAA,IAC9E;AAQA,QAAI,QAAQ;AACR,aAAO,MAAM,OAAO,IAAI;AAAA,IAC5B,WAAW,OAAO,YAAY,aAAa;AACvC,YAAM,KACF,UAAU,WAAW,UAAU,UAAU,QAAQ,QAC/C,UAAU,SAAS,QAAQ,OAC3B,UAAU,UAAU,QAAQ,QAC5B,QAAQ;AACd,SAAG,IAAI;AAAA,IACX;AAEA,QAAI,KAAK,YAAY;AACjB,WAAK,WAAW,MAAM,YAAY,IAAI;AAAA,IAC1C;AAAA,EACJ;AAAA,EAEA,MAAM,SAAiB,MAAkC;AACrD,SAAK,MAAM,SAAS,SAAS,IAAI;AAAA,EACrC;AAAA,EAEA,KAAK,SAAiB,MAAkC;AACpD,SAAK,MAAM,QAAQ,SAAS,IAAI;AAAA,EACpC;AAAA,EAEA,KAAK,SAAiB,MAAkC;AACpD,SAAK,MAAM,QAAQ,SAAS,IAAI;AAAA,EACpC;AAAA,EAEA,MAAM,SAAiB,aAA2C,MAAkC;AAChG,SAAK,eAAe,SAAS,SAAS,aAAa,IAAI;AAAA,EAC3D;AAAA,EAEA,MAAM,SAAiB,aAA2C,MAAkC;AAChG,SAAK,eAAe,SAAS,SAAS,aAAa,IAAI;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4BQ,eACJ,OACA,SACA,aACA,MACI;AACJ,QAAI,uBAAuB,OAAO;AAC9B,WAAK,MAAM,OAAO,SAAS,MAAM,WAAW;AAC5C;AAAA,IACJ;AACA,UAAM,SAAS,eAAe,OAAO,EAAE,GAAG,aAAa,GAAG,KAAK,IAAK,eAAe;AACnF,SAAK,MAAM,OAAO,SAAS,MAAM;AAAA,EACrC;AAAA,EAEA,IAAI,YAAoB,MAAmB;AACvC,SAAK,KAAK,SAAS,KAAK,SAAS,IAAI,EAAE,KAAK,IAAI,MAAS;AAAA,EAC7D;AAAA,EAEA,MAAM,SAA4C;AAK9C,UAAM,QAAQ,IAAI,cAAa,EAAE,GAAG,KAAK,QAAQ,MAAM,OAAU,GAAG,EAAE,GAAG,KAAK,UAAU,GAAG,QAAQ,CAAC;AACpG,UAAM,OAAO,OAAO,KAAK,OAAO;AAChC,UAAM,aAAa,KAAK;AACxB,WAAO;AAAA,EACX;AAAA,EAEA,UAAU,SAAiB,QAA+B;AACtD,WAAO,KAAK,MAAM,EAAE,SAAS,OAAO,CAAC;AAAA,EACzC;AAAA,EAEA,MAAM,UAAyB;AAC3B,UAAM,SAAS,KAAK;AACpB,SAAK,aAAa;AAKlB,QAAI,CAAC,UAAU,CAAC,KAAK,eAAgB;AACrC,SAAK,iBAAiB;AACtB,UAAM,IAAI,QAAc,CAAC,YAAY,OAAO,IAAI,OAAO,CAAC;AAAA,EAC5D;AACJ;AAEO,SAAS,aAAa,QAA8C;AACvE,SAAO,IAAI,aAAa,MAAM;AAClC;;;ACzeA,IAAAC,iBAAsC;;;ACHtC,iBAAkB;AAyBX,IAAM,wBAAN,MAA4B;AAAA,EAGjC,YAAY,QAAgB;AAC1B,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,qBAA8B,QAAwB,QAAgB;AACpE,QAAI,CAAC,OAAO,cAAc;AACxB,WAAK,OAAO,MAAM,UAAU,OAAO,IAAI,6CAA6C;AACpF,aAAO;AAAA,IACT;AAEA,QAAI;AAEF,YAAM,kBAAkB,OAAO,aAAa,MAAM,MAAM;AAExD,WAAK,OAAO,MAAM,mCAA8B,OAAO,IAAI,IAAI;AAAA,QAC7D,QAAQ,OAAO;AAAA,QACf,YAAY,OAAO,KAAK,UAAU,CAAC,CAAC,EAAE;AAAA,MACxC,CAAC;AAED,aAAO;AAAA,IACT,SAAS,OAAO;AACd,UAAI,iBAAiB,aAAE,UAAU;AAC/B,cAAM,kBAAkB,KAAK,gBAAgB,KAAK;AAClD,cAAM,eAAe;AAAA,UACnB,UAAU,OAAO,IAAI;AAAA,UACrB,GAAG,gBAAgB,IAAI,OAAK,OAAO,EAAE,IAAI,KAAK,EAAE,OAAO,EAAE;AAAA,QAC3D,EAAE,KAAK,IAAI;AAEX,aAAK,OAAO,MAAM,cAAc,QAAW;AAAA,UACzC,QAAQ,OAAO;AAAA,UACf,QAAQ;AAAA,QACV,CAAC;AAED,cAAM,IAAI,MAAM,YAAY;AAAA,MAC9B;AAGA,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,sBAA+B,QAAwB,eAAgC;AACrF,QAAI,CAAC,OAAO,cAAc;AACxB,aAAO;AAAA,IACT;AAEA,QAAI;AAGF,YAAM,gBAAiB,OAAO,aAAqB,QAAQ;AAC3D,YAAM,kBAAkB,cAAc,MAAM,aAAa;AAEzD,WAAK,OAAO,MAAM,oCAA+B,OAAO,IAAI,EAAE;AAC9D,aAAO;AAAA,IACT,SAAS,OAAO;AACd,UAAI,iBAAiB,aAAE,UAAU;AAC/B,cAAM,kBAAkB,KAAK,gBAAgB,KAAK;AAClD,cAAM,eAAe;AAAA,UACnB,UAAU,OAAO,IAAI;AAAA,UACrB,GAAG,gBAAgB,IAAI,OAAK,OAAO,EAAE,IAAI,KAAK,EAAE,OAAO,EAAE;AAAA,QAC3D,EAAE,KAAK,IAAI;AAEX,cAAM,IAAI,MAAM,YAAY;AAAA,MAC9B;AAEA,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,iBAA0B,QAAuC;AAC/D,QAAI,CAAC,OAAO,cAAc;AACxB,aAAO;AAAA,IACT;AAEA,QAAI;AAEF,YAAM,WAAW,OAAO,aAAa,MAAM,CAAC,CAAC;AAC7C,WAAK,OAAO,MAAM,6BAA6B,OAAO,IAAI,EAAE;AAC5D,aAAO;AAAA,IACT,SAAS,OAAO;AAEd,WAAK,OAAO,MAAM,gCAAgC,OAAO,IAAI,EAAE;AAC/D,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,cAAc,QAAwB,QAAsB;AAC1D,QAAI,CAAC,OAAO,cAAc;AACxB,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,OAAO,aAAa,UAAU,MAAM;AACnD,WAAO,OAAO;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,gBAAgB,QAAwB,QAAqD;AAC3F,QAAI,CAAC,OAAO,cAAc;AACxB,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,SAAS,OAAO,aAAa,UAAU,MAAM;AAEnD,QAAI,OAAO,SAAS;AAClB,aAAO,CAAC;AAAA,IACV;AAEA,WAAO,KAAK,gBAAgB,OAAO,KAAK;AAAA,EAC1C;AAAA;AAAA,EAIQ,gBAAgB,OAAgE;AACtF,WAAO,MAAM,OAAO,IAAI,CAAC,OAAmB;AAAA,MAC1C,MAAM,EAAE,KAAK,KAAK,GAAG,KAAK;AAAA,MAC1B,SAAS,EAAE;AAAA,IACb,EAAE;AAAA,EACJ;AACF;AAQO,SAAS,4BAA4B,QAAuC;AACjF,SAAO,IAAI,sBAAsB,MAAM;AACzC;;;ACtKA,yBAOO;AAEA,IAAM,gBAAgB;AAC7B,IAAM,aAAa;AAInB,SAAS,aAAa,KAA0B;AAC9C,SAAO,OAAO,QAAQ,eAAW,qCAAiB,GAAG,IAAI;AAC3D;AACA,SAAS,YAAY,KAA0B;AAC7C,SAAO,OAAO,QAAQ,eAAW,oCAAgB,GAAG,IAAI;AAC1D;AACA,SAAS,QAAQ,SAA0C;AACzD,SAAO,OAAO,YAAY,WAAW,IAAI,YAAY,EAAE,OAAO,OAAO,IAAI;AAC3E;AAGO,SAAS,yBAA0E;AACxF,QAAM,EAAE,WAAW,WAAW,QAAI,wCAAoB,SAAS;AAC/D,SAAO;AAAA,IACL,cAAc,UAAU,OAAO,EAAE,MAAM,QAAQ,QAAQ,MAAM,CAAC,EAAE,SAAS;AAAA,IACzE,eAAe,WAAW,OAAO,EAAE,MAAM,SAAS,QAAQ,MAAM,CAAC,EAAE,SAAS;AAAA,EAC9E;AACF;AAMO,SAAS,YACd,SACA,YACA,QAAQ,WACA;AACR,MAAI,MAAM,SAAS,GAAG,EAAG,OAAM,IAAI,MAAM,4BAA4B;AACrE,QAAM,UAAM,mBAAAC,MAAW,MAAM,QAAQ,OAAO,GAAG,aAAa,UAAU,CAAC;AACvE,SAAO,GAAG,UAAU,GAAG,KAAK,IAAI,IAAI,SAAS,WAAW,CAAC;AAC3D;AASO,SAAS,eAAe,GAAsD;AACnF,MAAI,OAAO,MAAM,YAAY,CAAC,EAAE,WAAW,UAAU,EAAG,QAAO;AAC/D,QAAM,OAAO,EAAE,MAAM,WAAW,MAAM;AACtC,QAAM,MAAM,KAAK,QAAQ,GAAG;AAC5B,MAAI,OAAO,EAAG,QAAO;AACrB,QAAM,QAAQ,KAAK,MAAM,GAAG,GAAG;AAC/B,QAAM,MAAM,KAAK,MAAM,MAAM,CAAC;AAC9B,MAAI,CAAC,SAAS,CAAC,IAAK,QAAO;AAC3B,MAAI;AACF,WAAO,EAAE,KAAK,WAAW,OAAO,WAAW,IAAI,WAAW,OAAO,KAAK,KAAK,WAAW,CAAC,EAAE;AAAA,EAC3F,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,cACd,SACA,WACA,WACS;AACT,QAAM,SAAS,eAAe,SAAS;AACvC,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI;AACF,eAAO,mBAAAC,QAAa,MAAM,QAAQ,OAAO,GAAG,YAAY,SAAS,GAAG,OAAO,SAAS;AAAA,EACtF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQO,SAAS,mBAAmB,SAKxB;AACT,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ,YAAY;AAAA,IACpB,QAAQ,aAAa;AAAA,EACvB,EAAE,KAAK,IAAI;AACb;AAmBA,eAAsB,yBACpB,MACA,cACgC;AAChC,QAAM,MAAM,KAAK;AACjB,MAAI,CAAC,IAAK,QAAO,EAAE,IAAI,MAAM,UAAU,OAAO,QAAQ,wBAAwB;AAE9E,QAAM,SAAS,eAAe,GAAG;AACjC,MAAI,CAAC,OAAQ,QAAO,EAAE,IAAI,OAAO,UAAU,OAAO,QAAQ,yBAAyB;AAEnF,MAAI,CAAC,cAAc;AACjB,WAAO,EAAE,IAAI,MAAM,UAAU,OAAO,QAAQ,uCAAuC;AAAA,EACrF;AAEA,QAAM,MAAM,MAAM,aAAa,OAAO,KAAK;AAC3C,MAAI,CAAC,IAAK,QAAO,EAAE,IAAI,OAAO,UAAU,OAAO,QAAQ,0BAA0B,OAAO,KAAK,IAAI;AAEjG,SAAO,cAAc,KAAK,UAAU,KAAK,GAAG,IACxC,EAAE,IAAI,MAAM,UAAU,KAAK,IAC3B,EAAE,IAAI,OAAO,UAAU,OAAO,QAAQ,8CAA8C;AAC1F;AAGO,SAAS,wBACd,SAOA,mBACS;AACT,MAAI,CAAC,QAAQ,mBAAoB,QAAO;AACxC,SAAO,cAAc,mBAAmB,OAAO,GAAG,QAAQ,oBAAoB,iBAAiB;AACjG;AAiBA,eAAsB,qBACpB,OAUA,MAKqC;AACrC,QAAM,kBAAkB,KAAK,mBAAmB;AAEhD,QAAM,YAAY,MAAM;AAAA,IACtB,EAAE,UAAU,MAAM,UAAU,WAAW,MAAM,QAAQ,UAAU;AAAA,IAC/D,KAAK;AAAA,EACP;AACA,MAAI,CAAC,UAAU,IAAI;AACjB,WAAO,EAAE,IAAI,OAAO,mBAAmB,OAAO,kBAAkB,OAAO,QAAQ,UAAU,OAAO;AAAA,EAClG;AAEA,MAAI,mBAAmB;AACvB,MAAI,KAAK,mBAAmB;AAC1B,uBAAmB,wBAAwB,MAAM,SAAS,KAAK,iBAAiB;AAAA,EAClF;AACA,MAAI,mBAAmB,CAAC,kBAAkB;AACxC,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,mBAAmB,UAAU;AAAA,MAC7B;AAAA,MACA,QAAQ,KAAK,oBACT,kDACA;AAAA,IACN;AAAA,EACF;AAEA,SAAO,EAAE,IAAI,MAAM,mBAAmB,UAAU,UAAU,iBAAiB;AAC7E;;;ACtOO,IAAK,mBAAL,kBAAKC,sBAAL;AAEH,EAAAA,kBAAA,eAAY;AAEZ,EAAAA,kBAAA,eAAY;AAEZ,EAAAA,kBAAA,YAAS;AAND,SAAAA;AAAA,GAAA;AA6FL,IAAM,eAAN,MAAmB;AAAA,EAUtB,YAAY,QAAgB;AAN5B,SAAQ,gBAA6C,oBAAI,IAAI;AAC7D,SAAQ,mBAAqD,oBAAI,IAAI;AACrE,SAAQ,mBAAqC,oBAAI,IAAI;AACrD,SAAQ,iBAAgD,oBAAI,IAAI;AAChE,SAAQ,WAAwB,oBAAI,IAAI;AAGpC,SAAK,SAAS;AACd,SAAK,kBAAkB,IAAI,sBAAsB,MAAM;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAA8B;AACrC,SAAK,UAAU;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAsB,MAA6B;AAC/C,WAAO,KAAK,iBAAiB,IAAI,IAAI;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAW,QAA2C;AACxD,UAAM,YAAY,KAAK,IAAI;AAE3B,QAAI;AACA,WAAK,OAAO,KAAK,mBAAmB,OAAO,IAAI,EAAE;AAGjD,YAAM,WAAW,KAAK,iBAAiB,MAAM;AAG7C,WAAK,wBAAwB,QAAQ;AAGrC,YAAM,eAAe,KAAK,0BAA0B,QAAQ;AAC5D,UAAI,CAAC,aAAa,YAAY;AAC1B,cAAM,IAAI,MAAM,yBAAyB,aAAa,OAAO,EAAE;AAAA,MACnE;AAGA,UAAI,SAAS,cAAc;AACvB,aAAK,qBAAqB,QAAQ;AAAA,MACtC;AAGA,UAAI,SAAS,WAAW;AACpB,cAAM,KAAK,sBAAsB,QAAQ;AAAA,MAC7C;AAGA,WAAK,cAAc,IAAI,SAAS,MAAM,QAAQ;AAE9C,YAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,WAAK,OAAO,KAAK,kBAAkB,OAAO,IAAI,KAAK,QAAQ,KAAK;AAEhE,aAAO;AAAA,QACH,SAAS;AAAA,QACT,QAAQ;AAAA,QACR;AAAA,MACJ;AAAA,IACJ,SAAS,OAAO;AACZ,WAAK,OAAO,MAAM,0BAA0B,OAAO,IAAI,IAAI,KAAc;AACzE,aAAO;AAAA,QACH,SAAS;AAAA,QACT;AAAA,QACA,UAAU,KAAK,IAAI,IAAI;AAAA,MAC3B;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,uBAAuB,cAAyC;AAC5D,QAAI,KAAK,iBAAiB,IAAI,aAAa,IAAI,GAAG;AAC9C,YAAM,IAAI,MAAM,oBAAoB,aAAa,IAAI,sBAAsB;AAAA,IAC/E;AAEA,SAAK,iBAAiB,IAAI,aAAa,MAAM,YAAY;AACzD,SAAK,OAAO,MAAM,+BAA+B,aAAa,IAAI,KAAK,aAAa,SAAS,GAAG;AAAA,EACpG;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAc,MAAc,SAA8B;AAC5D,UAAM,eAAe,KAAK,iBAAiB,IAAI,IAAI;AAEnD,QAAI,CAAC,cAAc;AAEf,YAAM,WAAW,KAAK,iBAAiB,IAAI,IAAI;AAC/C,UAAI,CAAC,UAAU;AACX,cAAM,IAAI,MAAM,YAAY,IAAI,aAAa;AAAA,MACjD;AACA,aAAO;AAAA,IACX;AAEA,YAAQ,aAAa,WAAW;AAAA,MAC5B,KAAK;AACD,eAAO,MAAM,KAAK,oBAAuB,YAAY;AAAA,MAEzD,KAAK;AACD,eAAO,MAAM,KAAK,uBAA0B,YAAY;AAAA,MAE5D,KAAK;AACD,YAAI,CAAC,SAAS;AACV,gBAAM,IAAI,MAAM,yCAAyC,IAAI,GAAG;AAAA,QACpE;AACA,eAAO,MAAM,KAAK,iBAAoB,cAAc,OAAO;AAAA,MAE/D;AACI,cAAM,IAAI,MAAM,8BAA8B,aAAa,SAAS,EAAE;AAAA,IAC9E;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,MAAc,SAAoB;AAC9C,QAAI,KAAK,iBAAiB,IAAI,IAAI,GAAG;AACjC,YAAM,IAAI,MAAM,YAAY,IAAI,sBAAsB;AAAA,IAC1D;AACA,SAAK,iBAAiB,IAAI,MAAM,OAAO;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAe,MAAc,SAAoB;AAC7C,QAAI,CAAC,KAAK,WAAW,IAAI,GAAG;AACxB,YAAM,IAAI,MAAM,YAAY,IAAI,aAAa;AAAA,IACjD;AACA,SAAK,iBAAiB,IAAI,MAAM,OAAO;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,MAAuB;AAC9B,WAAO,KAAK,iBAAiB,IAAI,IAAI,KAAK,KAAK,iBAAiB,IAAI,IAAI;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,6BAAuC;AACnC,UAAM,SAAmB,CAAC;AAC1B,UAAM,UAAU,oBAAI,IAAY;AAChC,UAAM,WAAW,oBAAI,IAAY;AAEjC,UAAM,QAAQ,CAAC,aAAqB,OAAiB,CAAC,MAAM;AACxD,UAAI,SAAS,IAAI,WAAW,GAAG;AAC3B,cAAM,QAAQ,CAAC,GAAG,MAAM,WAAW,EAAE,KAAK,MAAM;AAChD,eAAO,KAAK,KAAK;AACjB;AAAA,MACJ;AAEA,UAAI,QAAQ,IAAI,WAAW,GAAG;AAC1B;AAAA,MACJ;AAEA,eAAS,IAAI,WAAW;AAExB,YAAM,eAAe,KAAK,iBAAiB,IAAI,WAAW;AAC1D,UAAI,cAAc,cAAc;AAC5B,mBAAW,OAAO,aAAa,cAAc;AACzC,gBAAM,KAAK,CAAC,GAAG,MAAM,WAAW,CAAC;AAAA,QACrC;AAAA,MACJ;AAEA,eAAS,OAAO,WAAW;AAC3B,cAAQ,IAAI,WAAW;AAAA,IAC3B;AAEA,eAAW,eAAe,KAAK,iBAAiB,KAAK,GAAG;AACpD,YAAM,WAAW;AAAA,IACrB;AAEA,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAkB,YAAiD;AACrE,UAAM,SAAS,KAAK,cAAc,IAAI,UAAU;AAEhD,QAAI,CAAC,QAAQ;AACT,aAAO;AAAA,QACH,SAAS;AAAA,QACT,SAAS;AAAA,QACT,WAAW,oBAAI,KAAK;AAAA,MACxB;AAAA,IACJ;AAEA,QAAI,CAAC,OAAO,aAAa;AACrB,aAAO;AAAA,QACH,SAAS;AAAA,QACT,SAAS;AAAA,QACT,WAAW,oBAAI,KAAK;AAAA,MACxB;AAAA,IACJ;AAEA,QAAI;AACA,YAAM,SAAS,MAAM,OAAO,YAAY;AACxC,aAAO;AAAA,QACH,GAAG;AAAA,QACH,WAAW,oBAAI,KAAK;AAAA,MACxB;AAAA,IACJ,SAAS,OAAO;AACZ,aAAO;AAAA,QACH,SAAS;AAAA,QACT,SAAS,wBAAyB,MAAgB,OAAO;AAAA,QACzD,WAAW,oBAAI,KAAK;AAAA,MACxB;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAAuB;AAC9B,SAAK,eAAe,OAAO,OAAO;AAClC,SAAK,OAAO,MAAM,kBAAkB,OAAO,EAAE;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAgD;AAC5C,WAAO,IAAI,IAAI,KAAK,aAAa;AAAA,EACrC;AAAA;AAAA,EAIQ,iBAAiB,QAAgC;AAGrD,UAAM,WAAW;AAEjB,QAAI,CAAC,SAAS,SAAS;AACnB,eAAS,UAAU;AAAA,IACvB;AAEA,WAAO;AAAA,EACX;AAAA,EAEQ,wBAAwB,QAA8B;AAC1D,QAAI,CAAC,OAAO,MAAM;AACd,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC7C;AAEA,QAAI,CAAC,OAAO,MAAM;AACd,YAAM,IAAI,MAAM,kCAAkC;AAAA,IACtD;AAEA,QAAI,CAAC,KAAK,uBAAuB,OAAO,OAAO,GAAG;AAC9C,YAAM,IAAI,MAAM,6BAA6B,OAAO,OAAO,EAAE;AAAA,IACjE;AAAA,EACJ;AAAA,EAEQ,0BAA0B,QAA8C;AAG5E,UAAM,UAAU,OAAO;AAEvB,QAAI,CAAC,KAAK,uBAAuB,OAAO,GAAG;AACvC,aAAO;AAAA,QACH,YAAY;AAAA,QACZ,eAAe;AAAA,QACf,SAAS;AAAA,MACb;AAAA,IACJ;AAEA,WAAO;AAAA,MACH,YAAY;AAAA,MACZ,eAAe;AAAA,IACnB;AAAA,EACJ;AAAA,EAEQ,uBAAuB,SAA0B;AACrD,UAAM,cAAc;AACpB,WAAO,YAAY,KAAK,OAAO;AAAA,EACnC;AAAA,EAEQ,qBAAqB,QAAwB,QAAoB;AACrE,QAAI,CAAC,OAAO,cAAc;AACtB;AAAA,IACJ;AAEA,QAAI,WAAW,QAAW;AAIrB,WAAK,OAAO,MAAM,UAAU,OAAO,IAAI,yDAAyD;AAChG;AAAA,IACL;AAEA,SAAK,gBAAgB,qBAAqB,QAAQ,MAAM;AAAA,EAC5D;AAAA,EAEA,MAAc,sBAAsB,QAAuC;AACvE,QAAI,CAAC,OAAO,WAAW;AACnB;AAAA,IACJ;AAWA,UAAM,SAAS,eAAe,OAAO,SAAS;AAC9C,QAAI,CAAC,QAAQ;AACT,YAAM,IAAI;AAAA,QACN,UAAU,OAAO,IAAI;AAAA,MACzB;AAAA,IACJ;AACA,SAAK,OAAO;AAAA,MACR,UAAU,OAAO,IAAI,+BAA+B,OAAO,GAAG,WAAW,OAAO,KAAK;AAAA,IAEzF;AAAA,EACJ;AAAA,EAEA,MAAc,oBAAuB,cAA+C;AAChF,QAAI,WAAW,KAAK,iBAAiB,IAAI,aAAa,IAAI;AAE1D,QAAI,CAAC,UAAU;AAEX,iBAAW,MAAM,KAAK,sBAAsB,YAAY;AACxD,WAAK,iBAAiB,IAAI,aAAa,MAAM,QAAQ;AACrD,WAAK,OAAO,MAAM,8BAA8B,aAAa,IAAI,EAAE;AAAA,IACvE;AAEA,WAAO;AAAA,EACX;AAAA,EAEA,MAAc,uBAA0B,cAA+C;AACnF,UAAM,WAAW,MAAM,KAAK,sBAAsB,YAAY;AAC9D,SAAK,OAAO,MAAM,8BAA8B,aAAa,IAAI,EAAE;AACnE,WAAO;AAAA,EACX;AAAA,EAEA,MAAc,iBAAoB,cAAmC,SAA6B;AAC9F,QAAI,CAAC,KAAK,eAAe,IAAI,OAAO,GAAG;AACnC,WAAK,eAAe,IAAI,SAAS,oBAAI,IAAI,CAAC;AAAA,IAC9C;AAEA,UAAM,QAAQ,KAAK,eAAe,IAAI,OAAO;AAC7C,QAAI,WAAW,MAAM,IAAI,aAAa,IAAI;AAE1C,QAAI,CAAC,UAAU;AACX,iBAAW,MAAM,KAAK,sBAAsB,cAAc,OAAO;AACjE,YAAM,IAAI,aAAa,MAAM,QAAQ;AACrC,WAAK,OAAO,MAAM,2BAA2B,aAAa,IAAI,YAAY,OAAO,GAAG;AAAA,IACxF;AAEA,WAAO;AAAA,EACX;AAAA,EAEA,MAAc,sBAAsB,cAAmC,SAAgC;AACnG,QAAI,CAAC,KAAK,SAAS;AACf,YAAM,IAAI,MAAM,2DAA2D,aAAa,IAAI,GAAG;AAAA,IACnG;AAEA,QAAI,KAAK,SAAS,IAAI,aAAa,IAAI,GAAG;AACtC,YAAM,IAAI,MAAM,iCAAiC,MAAM,KAAK,KAAK,QAAQ,EAAE,KAAK,MAAM,CAAC,OAAO,aAAa,IAAI,EAAE;AAAA,IACrH;AAEA,SAAK,SAAS,IAAI,aAAa,IAAI;AACnC,QAAI;AACA,aAAO,MAAM,aAAa,QAAQ,KAAK,SAAS,OAAO;AAAA,IAC3D,UAAE;AACE,WAAK,SAAS,OAAO,aAAa,IAAI;AAAA,IAC1C;AAAA,EACJ;AACJ;;;AC5eO,IAAM,SAAS,OAAO,YAAY,eACnB,QAAQ,YAAY,QACpB,QAAQ,SAAS,QAAQ;AAKxC,SAAS,OAAO,KAAa,cAA2C;AAE3E,MAAI,OAAO,YAAY,eAAe,QAAQ,KAAK;AAC/C,WAAO,QAAQ,IAAI,GAAG,KAAK;AAAA,EAC/B;AAIA,MAAI;AAEA,QAAI,OAAO,eAAe,eAAe,WAAW,SAAS,KAAK;AAE9D,aAAO,WAAW,QAAQ,IAAI,GAAG,KAAK;AAAA,IAC1C;AAAA,EACJ,SAAS,GAAG;AAAA,EAEZ;AAEA,SAAO;AACX;AAKO,SAAS,SAAS,OAAe,GAAS;AAC7C,MAAI,QAAQ;AACR,YAAQ,KAAK,IAAI;AAAA,EACrB;AACJ;AAKO,SAAS,iBAA0D;AACtE,MAAI,QAAQ;AACR,WAAO,QAAQ,YAAY;AAAA,EAC/B;AACA,SAAO,EAAE,UAAU,GAAG,WAAW,EAAE;AACvC;;;AClCO,SAAS,oBAAoB;AAClC,QAAM,QAAQ,oBAAI,IAAkD;AACpE,MAAI,OAAO;AACX,MAAI,SAAS;AACb,SAAO;AAAA,IACL,eAAe;AAAA,MACb,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,SAAS;AAAA,IACX;AAAA,IACA,cAAc;AAAA,IACd,MAAM,IAAiB,KAAqC;AAC1D,YAAM,QAAQ,MAAM,IAAI,GAAG;AAC3B,UAAI,CAAC,SAAU,MAAM,WAAW,KAAK,IAAI,IAAI,MAAM,SAAU;AAC3D,cAAM,OAAO,GAAG;AAChB;AACA,eAAO;AAAA,MACT;AACA;AACA,aAAO,MAAM;AAAA,IACf;AAAA,IACA,MAAM,IAAiB,KAAa,OAAU,KAA6B;AACzE,YAAM,IAAI,KAAK,EAAE,OAAO,SAAS,MAAM,KAAK,IAAI,IAAI,MAAM,MAAO,OAAU,CAAC;AAAA,IAC9E;AAAA,IACA,MAAM,OAAO,KAA+B;AAAE,aAAO,MAAM,OAAO,GAAG;AAAA,IAAG;AAAA,IACxE,MAAM,IAAI,KAA+B;AAAE,aAAO,MAAM,IAAI,GAAG;AAAA,IAAG;AAAA,IAClE,MAAM,QAAuB;AAAE,YAAM,MAAM;AAAA,IAAG;AAAA,IAC9C,MAAM,QAAQ;AAAE,aAAO,EAAE,MAAM,QAAQ,UAAU,MAAM,KAAK;AAAA,IAAG;AAAA,EACjE;AACF;;;ACjCO,SAAS,oBAAoB;AAClC,QAAM,WAAW,oBAAI,IAAwB;AAC7C,MAAI,QAAQ;AACZ,SAAO;AAAA,IACL,eAAe;AAAA,MACb,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,SAAS;AAAA,IACX;AAAA,IACA,cAAc;AAAA,IACd,MAAM,QAAqB,OAAe,MAA0B;AAClE,YAAM,KAAK,gBAAgB,EAAE,KAAK;AAClC,YAAM,MAAM,SAAS,IAAI,KAAK,KAAK,CAAC;AACpC,iBAAW,MAAM,IAAK,IAAG,EAAE,IAAI,MAAM,UAAU,GAAG,WAAW,KAAK,IAAI,EAAE,CAAC;AACzE,aAAO;AAAA,IACT;AAAA,IACA,MAAM,UAAU,OAAe,SAAqD;AAClF,eAAS,IAAI,OAAO,CAAC,GAAI,SAAS,IAAI,KAAK,KAAK,CAAC,GAAI,OAAO,CAAC;AAAA,IAC/D;AAAA,IACA,MAAM,YAAY,OAA8B;AAAE,eAAS,OAAO,KAAK;AAAA,IAAG;AAAA,IAC1E,MAAM,eAAgC;AAAE,aAAO;AAAA,IAAG;AAAA,IAClD,MAAM,MAAM,OAA8B;AAAE,eAAS,OAAO,KAAK;AAAA,IAAG;AAAA,EACtE;AACF;;;ACnCA,oBAA0C;AASnC,SAAS,UACd,QACA,QACyB;AACzB,QAAM,SAAkC,EAAE,GAAG,OAAO;AACpD,aAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,UAAM,OAAO,OAAO,GAAG;AACvB,UAAM,OAAO,OAAO,GAAG;AACvB,QACE,QAAQ,QACL,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,KAC/C,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,GAClD;AACA,aAAO,GAAG,IAAI;AAAA,QACZ;AAAA,QACA;AAAA,MACF;AAAA,IACF,OAAO;AACL,aAAO,GAAG,IAAI;AAAA,IAChB;AAAA,EACF;AACA,SAAO;AACT;AAaO,SAAS,cAAc,iBAAyB,kBAAgD;AACrG,MAAI,iBAAiB,WAAW,EAAG,QAAO;AAG1C,MAAI,iBAAiB,SAAS,eAAe,EAAG,QAAO;AAGvD,QAAM,QAAQ,gBAAgB,YAAY;AAC1C,QAAM,YAAY,iBAAiB,KAAK,OAAK,EAAE,YAAY,MAAM,KAAK;AACtE,MAAI,UAAW,QAAO;AAGtB,QAAM,WAAW,gBAAgB,MAAM,GAAG,EAAE,CAAC,EAAE,YAAY;AAC3D,QAAM,YAAY,iBAAiB,KAAK,OAAK,EAAE,YAAY,MAAM,QAAQ;AACzE,MAAI,UAAW,QAAO;AAGtB,QAAM,eAAe,iBAAiB,KAAK,OAAK,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,YAAY,MAAM,QAAQ;AAC1F,MAAI,aAAc,QAAO;AAEzB,SAAO;AACT;AAaO,SAAS,mBAAmB;AACjC,QAAM,eAAe,oBAAI,IAAqC;AAK9D,QAAM,WAAW,oBAAI,IAAqC;AAC1D,MAAI,gBAAgB;AAOpB,MAAI;AAKJ,WAAS,WAAW,MAA+B,KAAiC;AAClF,UAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,QAAI,UAAmB;AACvB,eAAW,QAAQ,OAAO;AACxB,UAAI,WAAW,QAAQ,OAAO,YAAY,SAAU,QAAO;AAC3D,gBAAW,QAAoC,IAAI;AAAA,IACrD;AACA,WAAO,OAAO,YAAY,WAAW,UAAU;AAAA,EACjD;AAGA,WAAS,aAAa,QAAqD;AACzE,UAAM,OAAO,aAAa,IAAI,MAAM;AACpC,UAAM,OAAO,SAAS,IAAI,MAAM;AAChC,QAAI,QAAQ,KAAM,QAAO,UAAU,MAAM,IAAI;AAC7C,WAAO,QAAQ;AAAA,EACjB;AAKA,WAAS,oBAAoB,QAAqD;AAEhF,UAAM,QAAQ,aAAa,MAAM;AACjC,QAAI,MAAO,QAAO;AAGlB,UAAM,aAAa,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,aAAa,KAAK,GAAG,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC;AAC5E,UAAM,WAAW,cAAc,QAAQ,UAAU;AACjD,QAAI,SAAU,QAAO,aAAa,QAAQ;AAE1C,WAAO;AAAA,EACT;AAEA,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAML,eAAe;AAAA,MACb,QAAQ;AAAA,MACR,SAAS;AAAA,IACX;AAAA,IACA,cAAc;AAAA,IAEd,EAAE,KAAa,QAAgB,QAA0C;AACvE,YAAM,OAAO,oBAAoB,MAAM,KAAK,aAAa,aAAa;AACtE,YAAM,QAAQ,OAAO,WAAW,MAAM,GAAG,IAAI;AAC7C,UAAI,SAAS,KAAM,QAAO;AAC1B,UAAI,CAAC,OAAQ,QAAO;AAEpB,aAAO,MAAM,QAAQ,kBAAkB,CAAC,GAAG,SAAS,OAAO,OAAO,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC;AAAA,IAC3F;AAAA,IAEA,gBAAgB,QAAyC;AACvD,aAAO,oBAAoB,MAAM,KAAK,CAAC;AAAA,IACzC;AAAA,IAEA,iBAAiB,QAAgB,MAAqC;AACpE,YAAM,WAAW,aAAa,IAAI,MAAM;AACxC,UAAI,UAAU;AACZ,qBAAa,IAAI,QAAQ,UAAU,UAAU,IAAI,CAAC;AAAA,MACpD,OAAO;AACL,qBAAa,IAAI,QAAQ,EAAE,GAAG,KAAK,CAAC;AAAA,MACtC;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,4BAA4B,UAAyD;AACnF,eAAS,MAAM;AACf,iBAAW,CAAC,QAAQ,IAAI,KAAK,OAAO,QAAQ,YAAY,CAAC,CAAC,GAAG;AAC3D,YAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AACvC,iBAAS,IAAI,QAAQ,EAAE,GAAG,KAAK,CAAC;AAAA,MAClC;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAiBA,aAAuB;AACrB,UAAI,iBAAkB,QAAO,CAAC,GAAG,gBAAgB;AACjD,aAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,aAAa,KAAK,GAAG,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC;AAAA,IAClE;AAAA;AAAA,IAGA,oBAAoB,SAA8C;AAChE,6BAAmB,yCAA0B,OAAO;AAAA,IACtD;AAAA,IAEA,mBAA2B;AACzB,aAAO;AAAA,IACT;AAAA,IAEA,iBAAiB,QAAsB;AACrC,sBAAgB;AAAA,IAClB;AAAA,EACF;AACF;;;ACvHA,oBAAiC;AAGjC,IAAM,wBAA2C;AAU1C,SAAS,6BAA6B,MAAsB;AAC/D,aAAO,gCAAiB,IAAI;AAChC;AASA,SAAS,gBAAgB,SAA2D;AAChF,QAAM,MAAM,IAAI,MAAM,OAAO;AAC7B,MAAI,OAAO;AACX,MAAI,SAAS;AACb,SAAO;AACX;AAsBO,SAAS,+BACZ,MACA,MACA,MACuC;AACvC,MAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,IAAI,GAAG;AAClE,UAAM,QAAQ,SAAS,OAAO,SAAS,MAAM,QAAQ,IAAI,IAAI,aAAa,KAAK,OAAO,IAAI;AAC1F,UAAM;AAAA,MACF,8BAA8B,IAAI,OAAO,IAAI,eAAe,KAAK,wSAGL,IAAI;AAAA,IACpE;AAAA,EACJ;AACA,QAAM,eAAgB,KAA4B;AAClD,MAAI,iBAAiB,UAAa,iBAAiB,MAAM;AACrD,UAAM;AAAA,MACF,8BAA8B,IAAI,OAAO,IAAI,qBAAqB,OAAO,YAAY,CAAC,8CAChE,IAAI;AAAA,IAG9B;AAAA,EACJ;AACJ;;;ACpJO,SAAS,uBAAuB;AAErC,QAAM,QAAQ,oBAAI,IAA8B;AAIhD,WAAS,WAAW,MAAgC;AAClD,UAAM,YAAY,6BAA6B,IAAI;AACnD,QAAI,MAAM,MAAM,IAAI,SAAS;AAC7B,QAAI,CAAC,KAAK;AACR,YAAM,oBAAI,IAAI;AACd,YAAM,IAAI,WAAW,GAAG;AAAA,IAC1B;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,IAKL,eAAe;AAAA,MACb,QAAQ;AAAA,MACR,SAAS;AAAA,IACX;AAAA,IACA,cAAc;AAAA,IACd,MAAM,SAAS,MAAc,MAAc,MAA0B;AAInE,qCAA+B,MAAM,MAAM,IAAI;AAC/C,iBAAW,IAAI,EAAE,IAAI,MAAM,IAAI;AAAA,IACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAeA,iBAAiB,MAAc,MAAc,MAAiB;AAC5D,iBAAW,IAAI,EAAE,IAAI,MAAM,IAAI;AAAA,IACjC;AAAA,IACA,MAAM,IAAI,MAAc,MAA4B;AAClD,aAAO,WAAW,IAAI,EAAE,IAAI,IAAI;AAAA,IAClC;AAAA,IACA,MAAM,KAAK,MAA8B;AACvC,aAAO,MAAM,KAAK,WAAW,IAAI,EAAE,OAAO,CAAC;AAAA,IAC7C;AAAA,IACA,MAAM,WAAW,MAAc,MAA6B;AAC1D,iBAAW,IAAI,EAAE,OAAO,IAAI;AAAA,IAC9B;AAAA,IACA,MAAM,OAAO,MAAc,MAAgC;AACzD,aAAO,WAAW,IAAI,EAAE,IAAI,IAAI;AAAA,IAClC;AAAA,IACA,MAAM,UAAU,MAAiC;AAC/C,aAAO,MAAM,KAAK,WAAW,IAAI,EAAE,KAAK,CAAC;AAAA,IAC3C;AAAA,IACA,MAAM,UAAU,MAA4B;AAC1C,aAAO,WAAW,QAAQ,EAAE,IAAI,IAAI;AAAA,IACtC;AAAA,IACA,MAAM,cAA8B;AAClC,aAAO,MAAM,KAAK,WAAW,QAAQ,EAAE,OAAO,CAAC;AAAA,IACjD;AAAA,EACF;AACF;;;AC1EO,SAAS,kBAAkB;AAChC,QAAM,OAAO,oBAAI,IAAiB;AAClC,SAAO;AAAA,IACL,eAAe;AAAA,MACb,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,SAAS;AAAA,IACX;AAAA,IACA,cAAc;AAAA,IACd,MAAM,SAAS,MAAc,UAAe,SAA6B;AAAE,WAAK,IAAI,MAAM,EAAE,UAAU,QAAQ,CAAC;AAAA,IAAG;AAAA,IAClH,MAAM,OAAO,MAA6B;AAAE,WAAK,OAAO,IAAI;AAAA,IAAG;AAAA,IAC/D,MAAM,QAAQ,MAAc,MAA+B;AACzD,YAAM,MAAM,KAAK,IAAI,IAAI;AACzB,UAAI,KAAK,QAAS,OAAM,IAAI,QAAQ,EAAE,OAAO,MAAM,KAAK,CAAC;AAAA,IAC3D;AAAA,IACA,MAAM,gBAAgC;AAAE,aAAO,CAAC;AAAA,IAAG;AAAA,IACnD,MAAM,WAA8B;AAAE,aAAO,CAAC,GAAG,KAAK,KAAK,CAAC;AAAA,IAAG;AAAA,EACjE;AACF;;;ACQA,IAAAC,iBAAyC;AAmBzC,IAAM,aAAa;AAqBnB,IAAM,cAAc;AAOpB,eAAsB,6BACpB,QACA,QACyD;AACzD,MAAI;AACJ,MAAI;AACF,WAAQ,MAAM,OAAO,KAAK,gBAAgB;AAAA,MACxC,OAAO,EAAE,MAAM,eAAe,OAAO,SAAS;AAAA,IAChD,CAAC,KAAM,CAAC;AACR,QAAI,KAAK,WAAW,GAAG;AAErB,aAAQ,MAAM,OAAO,KAAK,gBAAgB;AAAA,QACxC,OAAO,EAAE,MAAM,gBAAgB,OAAO,SAAS;AAAA,MACjD,CAAC,KAAM,CAAC;AAAA,IACV;AAAA,EACF,SAAS,KAAU;AACjB,YAAQ,QAAQ,wEAAmE;AAAA,MACjF,OAAO,KAAK;AAAA,IACd,CAAC;AACD,WAAO;AAAA,EACT;AAEA,QAAM,WAAoD,CAAC;AAC3D,QAAM,SAAS,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,GAAG,QAAQ,EAAE,EAAE,cAAc,OAAO,GAAG,QAAQ,EAAE,CAAC,CAAC;AAClG,aAAW,OAAO,QAAQ;AACxB,QAAI;AACJ,QAAI;AACF,aAAO,OAAO,IAAI,aAAa,WAAW,KAAK,MAAM,IAAI,QAAQ,IAAI,IAAI;AAAA,IAC3E,QAAQ;AACN;AAAA,IACF;AACA,QAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AAMvC,UAAM,aAAa,wCAAyB,OAAO,CAAC,QAAQ,KAAK,GAAG,MAAM,MAAS;AACnF,QAAI,WAAW,SAAS,GAAG;AACzB,cAAQ;AAAA,QACN,gCAAgC,KAAK,IAAI,0CACnC,WAAW,KAAK,IAAI,CAAC;AAAA,MAE7B;AACA;AAAA,IACF;AAEA,UAAM,SACH,OAAO,MAAM,WAAW,YAAY,KAAK,WACtC,OAAO,KAAK,SAAS,YAAY,YAAY,KAAK,IAAI,IAAI,IAAI,IAAI,OAAO,WAC1E;AACL,QAAI,CAAC,QAAQ;AACX,cAAQ;AAAA,QACN,gCAAgC,KAAK,IAAI;AAAA,MAE3C;AACA;AAAA,IACF;AAIA,UAAM;AAAA,MACJ,MAAM;AAAA,MAAI,QAAQ;AAAA,MAClB,YAAY;AAAA,MAAI,iBAAiB;AAAA,MAAK,aAAa;AAAA,MACnD,OAAO;AAAA,MAAK,aAAa;AAAA,MAAK,cAAc;AAAA,MAAK,aAAa;AAAA,MAC9D,GAAG;AAAA,IACL,IAAI;AACJ,aAAS,MAAM,IAAI,UAAU,SAAS,MAAM,KAAK,CAAC,GAAG,OAAkC;AAAA,EACzF;AACA,SAAO;AACT;AAUO,SAAS,4BAA4B,KAAuB;AACjE,MAAI,OAAO,IAAI,SAAS,WAAY;AAEpC,QAAM,QAAQ,uBAAO,2BAA2B;AAChD,QAAM,mBAAmB,MAAsC;AAC7D,QAAI;AACJ,QAAI;AAAE,aAAO,IAAI,WAAW,MAAM;AAAA,IAAG,QAAQ;AAAE,aAAO;AAAA,IAAM;AAC5D,QAAI,CAAC,QAAQ,OAAO,KAAK,gCAAgC,WAAY,QAAO;AAC5E,UAAM,UAAU,KAAK,UAAU;AAC/B,QAAI,YAAY,QAAW;AACzB,WAAK,UAAU,IAAI;AACnB,aAAO;AAAA,IACT;AACA,WAAO,YAAY,QAAQ,OAAO;AAAA,EACpC;AAIA,MAAI,QAAuB,QAAQ,QAAQ;AAC3C,QAAM,OAAO,MAAqB;AAChC,UAAM,MAAM,MAAM,KAAK,YAAY;AACjC,YAAM,OAAO,iBAAiB;AAC9B,UAAI,CAAC,KAAM;AACX,UAAI;AACJ,UAAI;AAAE,iBAAS,IAAI,WAAW,UAAU;AAAA,MAAG,QAAQ;AAAE;AAAA,MAAQ;AAC7D,UAAI,CAAC,UAAU,OAAO,OAAO,SAAS,WAAY;AAClD,YAAM,QAAQ,MAAM,6BAA6B,QAAQ,IAAI,MAAM;AACnE,UAAI,UAAU,KAAM;AACpB,WAAK,4BAA4B,KAAK;AACtC,UAAI,OAAO,OAAO,+CAA+C;AAAA,QAC/D,SAAS,OAAO,KAAK,KAAK;AAAA,MAC5B,CAAC;AAAA,IACH,CAAC;AACD,YAAQ,IAAI,MAAM,MAAM,MAAS;AACjC,WAAO;AAAA,EACT;AAEA,MAAI,KAAK,gBAAgB,YAAY;AAGnC,QAAI,iBAAiB,GAAG;AACtB,UAAI,WAAgB;AACpB,UAAI;AAAE,mBAAW,IAAI,WAAW,UAAU;AAAA,MAAG,QAAQ;AAAA,MAAmC;AACxF,UAAI,YAAY,OAAO,SAAS,uBAAuB,YAAY;AACjE,iBAAS,mBAAmB,CAAC,QAAuD;AAClF,cAAI,KAAK,SAAS,iBAAiB,IAAI,UAAU,QAAS;AAC1D,eAAK,KAAK,EAAE,MAAM,CAAC,QAAa;AAC9B,gBAAI,OAAO,OAAO,6DAA6D;AAAA,cAC7E,MAAM,IAAI;AAAA,cACV,OAAO,KAAK;AAAA,YACd,CAAC;AAAA,UACH,CAAC;AAAA,QACH,CAAC;AAAA,MACH;AAAA,IACF;AACA,UAAM,KAAK;AAAA,EACb,CAAC;AACD,MAAI,KAAK,qBAAqB,YAAY;AACxC,UAAM,KAAK;AAAA,EACb,CAAC;AACH;;;AChMO,IAAM,0BAAqE;AAAA,EAChF,UAAU;AAAA,EACV,OAAO;AAAA,EACP,OAAO;AAAA,EACP,MAAO;AACT;;;ACUA,SAAS,aAAa,QAAmC;AACrD,SAAO,OAAO,UAAU,IAAI,OAAO,OAAO,KAAK;AACnD;AAkCO,SAAS,+BACZ,UACA,MACM;AACN,SACI,uBAAuB,KAAK,IAAI,oCAA+B,aAAa,IAAI,CAAC,+BACpD,aAAa,QAAQ,CAAC;AAK3D;AAmCO,SAAS,qBACZ,UACA,QACA,QACa;AACb,QAAM,WAAW,SAAS,IAAI,OAAO,IAAI;AAEzC,MAAI,aAAa,QAAW;AAKxB,WAAO,KAAK,+BAA+B,UAAU,MAAM,GAAG;AAAA,MAC1D,QAAQ,OAAO;AAAA,MACf,mBAAmB,SAAS;AAAA,MAC5B,oBAAoB,OAAO;AAAA,IAC/B,CAAC;AAAA,EACL;AAEA,WAAS,IAAI,OAAO,MAAM,MAAM;AAEhC,SAAO;AACX;;;AClIO,IAAM,eAAN,MAAmB;AAAA,EAmBtB,YAAY,WAAmB,oBAAiC;AARhE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAQ,eAA2B,MAAM;AAAA,IAAC;AAStC,SAAK,SAAS,IAAI,QAAe,CAAC,SAAS,WAAW;AAClD,WAAK,eAAe;AACpB,WAAK,QAAQ,WAAW,MAAM,OAAO,mBAAmB,CAAC,GAAG,SAAS;AAAA,IACzE,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,UAAgB;AACZ,iBAAa,KAAK,KAAK;AACvB,SAAK,QAAQ;AACb,SAAK,aAAa;AAAA,EACtB;AACJ;AAgBA,eAAsB,gBAClB,WACA,WACA,oBACU;AACV,QAAM,QAAQ,IAAI,aAAa,WAAW,kBAAkB;AAE5D,MAAI;AACA,WAAO,MAAM,QAAQ,KAAK,CAAC,WAAW,MAAM,MAAM,CAAC;AAAA,EACvD,UAAE;AACE,UAAM,QAAQ;AAAA,EAClB;AACJ;;;AdvCO,IAAM,eAAN,MAAmB;AAAA,EAmBtB,YAAY,SAA6B,CAAC,GAAG;AAlB7C,SAAQ,UAAuC,oBAAI,IAAI;AACvD,SAAQ,WAA6B,oBAAI,IAAI;AAC7C,SAAQ,QAAsE,oBAAI,IAAI;AACtF,SAAQ,QAAsE;AAK9E,SAAQ,iBAA8B,oBAAI,IAAI;AAC9C,SAAQ,mBAAwC,oBAAI,IAAI;AACxD,SAAQ,mBAA+C,CAAC;AASpD,SAAK,SAAS;AAAA,MACV,uBAAuB;AAAA;AAAA,MACvB,kBAAkB;AAAA,MAClB,iBAAiB;AAAA;AAAA,MACjB,mBAAmB;AAAA,MACnB,GAAG;AAAA,IACP;AAEA,SAAK,SAAS,aAAa,OAAO,MAAM;AACxC,SAAK,eAAe,IAAI,aAAa,KAAK,MAAM;AAGhD,SAAK,UAAU;AAAA,MACX,iBAAiB,CAAC,MAAM,YAAY;AAChC,aAAK,gBAAgB,MAAM,OAAO;AAAA,MACtC;AAAA,MACA,wBAAwB,CAAC,MAAM,SAAS,WAAW,iBAAiB;AAChE,aAAK,uBAAuB,MAAM,SAAS,WAAW,YAAY;AAAA,MACtE;AAAA,MACA,YAAY,CAAI,SAAiB;AAE7B,cAAM,UAAU,KAAK,SAAS,IAAI,IAAI;AACtC,YAAI,SAAS;AACT,iBAAO;AAAA,QACX;AAGA,cAAM,gBAAgB,KAAK,aAAa,mBAAsB,IAAI;AAClE,YAAI,eAAe;AAEf,eAAK,SAAS,IAAI,MAAM,aAAa;AACrC,iBAAO;AAAA,QACX;AAkBA,YAAI,CAAC,KAAK,aAAa,WAAW,IAAI,GAAG;AACrC,gBAAM,IAAI;AAAA,YACN,qBAAqB,IAAI,cAAc,KAAK,uBAAuB,IAAI,CAAC;AAAA,UAC5E;AAAA,QACJ;AAMA,cAAM,IAAI,MAAM,YAAY,IAAI,wBAAwB;AAAA,MAC5D;AAAA,MACA,gBAAgB,CAAI,MAAc,mBAA4B;AAC1D,cAAM,aAAa,KAAK,SAAS,IAAI,IAAI,KAAK,KAAK,aAAa,WAAW,IAAI;AAC/E,YAAI,CAAC,YAAY;AACb,gBAAM,IAAI,MAAM,qBAAqB,IAAI,yDAAyD;AAAA,QACtG;AACA,aAAK,SAAS,IAAI,MAAM,cAAc;AACtC,aAAK,aAAa,eAAe,MAAM,cAAc;AACrD,aAAK,OAAO,KAAK,YAAY,IAAI,cAAc,EAAE,SAAS,KAAK,CAAC;AAAA,MACpE;AAAA,MACA,MAAM,CAAC,MAAM,YAAY;AACrB,YAAI,CAAC,KAAK,MAAM,IAAI,IAAI,GAAG;AACvB,eAAK,MAAM,IAAI,MAAM,CAAC,CAAC;AAAA,QAC3B;AACA,aAAK,MAAM,IAAI,IAAI,EAAG,KAAK,OAAO;AAAA,MACtC;AAAA;AAAA;AAAA;AAAA;AAAA,MAKA,SAAS,OAAO,SAAS,SAAS;AAC9B,cAAM,wBAAwB,MAAM,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,GAAG,QAAW,IAAI;AAAA,MACnF;AAAA,MACA,aAAa,MAAM;AACf,eAAO,IAAI,IAAI,KAAK,QAAQ;AAAA,MAChC;AAAA,MACA,kBAAkB,CAAI,MAAc,YAAgC;AAChE,eAAO,KAAK,aAAa,WAAc,MAAM,OAAO;AAAA,MACxD;AAAA,MACA,QAAQ,KAAK;AAAA,MACb,WAAW,MAAM;AAAA;AAAA,IACrB;AAEA,SAAK,aAAa,WAAW,KAAK,OAAO;AAGzC,QAAI,KAAK,OAAO,kBAAkB;AAC9B,WAAK,wBAAwB;AAAA,IACjC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,IAAI,QAA+B;AACrC,QAAI,KAAK,UAAU,QAAQ;AACvB,YAAM,IAAI,MAAM,8DAA8D;AAAA,IAClF;AAGA,UAAM,SAAS,MAAM,KAAK,aAAa,WAAW,MAAM;AAExD,QAAI,CAAC,OAAO,WAAW,CAAC,OAAO,QAAQ;AACnC,YAAM,IAAI,MAAM,0BAA0B,OAAO,IAAI,MAAM,OAAO,OAAO,OAAO,EAAE;AAAA,IACtF;AAEA,UAAM,aAAa,OAAO;AAC1B,UAAM,aAAa,qBAAqB,KAAK,SAAS,YAAY,KAAK,MAAM;AAS7E,QAAI,eAAe,QAAW;AAC1B,WAAK,OAAO,KAAK,sBAAsB,WAAW,IAAI,IAAI,WAAW,OAAO,IAAI;AAAA,QAC5E,QAAQ,WAAW;AAAA,QACnB,SAAS,WAAW;AAAA,MACxB,CAAC;AAAA,IACL;AAEA,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAmB,MAAc,SAAkB;AAC/C,QAAI,KAAK,SAAS,IAAI,IAAI,GAAG;AACzB,YAAM,IAAI,MAAM,qBAAqB,IAAI,sBAAsB;AAAA,IACnE;AACA,SAAK,SAAS,IAAI,MAAM,OAAO;AAC/B,SAAK,aAAa,gBAAgB,MAAM,OAAO;AAC/C,SAAK,OAAO,KAAK,YAAY,IAAI,gBAAgB,EAAE,SAAS,KAAK,CAAC;AAClE,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA,EAKA,uBACI,MACA,SACA,yCACA,cACI;AACJ,SAAK,aAAa,uBAAuB;AAAA,MACrC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACJ,CAAC;AACD,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,yBAAyB;AAC7B,QAAI,KAAK,OAAO,qBAAsB;AACtC,eAAW,CAAC,aAAa,WAAW,KAAK,OAAO,QAAQ,oCAAqB,GAAG;AAC5E,UAAI,gBAAgB,OAAQ;AAC5B,YAAM,aAAa,KAAK,SAAS,IAAI,WAAW,KAAK,KAAK,aAAa,WAAW,WAAW;AAC7F,UAAI,CAAC,YAAY;AACb,cAAM,UAAU,wBAAwB,WAAW;AACnD,YAAI,SAAS;AACT,gBAAM,WAAW,QAAQ;AACzB,eAAK,gBAAgB,aAAa,QAAQ;AAC1C,eAAK,OAAO,MAAM,iDAAiD,WAAW,kBAAkB;AAAA,QACpG;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKQ,6BAA6B;AACjC,QAAI,KAAK,OAAO,sBAAsB;AAClC,WAAK,OAAO,MAAM,uCAAuC;AACzD;AAAA,IACJ;AAEA,SAAK,OAAO,MAAM,2CAA2C;AAC7D,UAAM,kBAA4B,CAAC;AACnC,UAAM,sBAAgC,CAAC;AAGvC,eAAW,CAAC,aAAa,WAAW,KAAK,OAAO,QAAQ,oCAAqB,GAAG;AAC5E,YAAM,aAAa,KAAK,SAAS,IAAI,WAAW,KAAK,KAAK,aAAa,WAAW,WAAW;AAE7F,UAAI,CAAC,YAAY;AACb,YAAI,gBAAgB,YAAY;AAC5B,eAAK,OAAO,MAAM,uCAAuC,WAAW,EAAE;AACtE,0BAAgB,KAAK,WAAW;AAAA,QACpC,WAAW,gBAAgB,QAAQ;AAE/B,gBAAM,UAAU,wBAAwB,WAAW;AACnD,cAAI,SAAS;AACT,kBAAM,WAAW,QAAQ;AACzB,iBAAK,gBAAgB,aAAa,QAAQ;AAC1C,iBAAK,OAAO,KAAK,YAAY,WAAW,gDAA2C;AAAA,UACvF,OAAO;AACH,iBAAK,OAAO,KAAK,8DAA8D,WAAW,EAAE;AAC5F,gCAAoB,KAAK,WAAW;AAAA,UACxC;AAAA,QACJ,OAAO;AACH,eAAK,OAAO,KAAK,uCAAuC,WAAW,EAAE;AAAA,QACzE;AAAA,MACJ;AAAA,IACJ;AAEA,QAAI,gBAAgB,SAAS,GAAG;AAC5B,YAAM,WAAW,sDAAsD,gBAAgB,KAAK,IAAI,CAAC;AACjG,WAAK,OAAO,MAAM,QAAQ;AAC1B,YAAM,IAAI,MAAM,QAAQ;AAAA,IAC5B;AAEA,QAAI,oBAAoB,SAAS,GAAG;AAChC,WAAK,OAAO,KAAK,qEAAqE,oBAAoB,KAAK,IAAI,CAAC,EAAE;AAAA,IAC1H;AAEA,SAAK,OAAO,KAAK,iCAAiC;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAA2B;AAC7B,QAAI,KAAK,UAAU,QAAQ;AACvB,YAAM,IAAI,MAAM,sCAAsC;AAAA,IAC1D;AAEA,SAAK,QAAQ;AACb,SAAK,OAAO,KAAK,mBAAmB;AAEpC,QAAI;AAEA,YAAM,SAAS,KAAK,aAAa,2BAA2B;AAC5D,UAAI,OAAO,SAAS,GAAG;AACnB,aAAK,OAAO,KAAK,2CAA2C,EAAE,OAAO,CAAC;AAAA,MAC1E;AAGA,YAAM,iBAAiB,KAAK,oBAAoB;AAKhD,kCAA4B,gBAAgB,CAAC,SAAS,KAAK,cAAc,IAAI,CAAC;AAG9E,WAAK,OAAO,KAAK,uBAAuB;AACxC,iBAAW,UAAU,gBAAgB;AACjC,cAAM,KAAK,sBAAsB,MAAM;AAAA,MAC3C;AAMA,WAAK,uBAAuB;AAG5B,WAAK,OAAO,KAAK,wBAAwB;AACzC,WAAK,QAAQ;AAEb,iBAAW,UAAU,gBAAgB;AACjC,cAAM,SAAS,MAAM,KAAK,uBAAuB,MAAM;AAEvD,YAAI,CAAC,OAAO,SAAS;AACjB,eAAK,OAAO,MAAM,0BAA0B,OAAO,IAAI,IAAI,OAAO,KAAK;AACvE,gBAAM,UAAU,OAAO,iBAAiB,QAAQ,OAAO,MAAM,UAAU,OAAO,OAAO,KAAK;AAC1F,gBAAM,YAAY,OAAO,iBAAiB,QAAQ,OAAO,MAAM,QAAQ;AACvE,kBAAQ,MAAM,mCAAmC,OAAO,IAAI,IAAI,SAAS,SAAS;AAElF,cAAI,KAAK,OAAO,mBAAmB;AAC/B,iBAAK,OAAO,KAAK,iCAAiC;AAClD,kBAAM,KAAK,uBAAuB;AAMlC,kBAAM,MAAW,IAAI;AAAA,cACjB,UAAU,OAAO,IAAI,yCAAyC,OAAO;AAAA,YACzE;AACA,gBAAI,OAAO,iBAAiB,OAAO;AAC/B,kBAAI,QAAQ,OAAO;AACnB,kBAAI,gBAAgB;AAAA,YACxB;AACA,kBAAM;AAAA,UACV;AAAA,QACJ;AAAA,MACJ;AAGA,WAAK,2BAA2B;AAChC,WAAK,OAAO,MAAM,8BAA8B;AAChD,YAAM,KAAK,QAAQ,QAAQ,cAAc;AAWzC,WAAK,OAAO,MAAM,qCAAqC;AACvD,YAAM,KAAK,QAAQ,QAAQ,qBAAqB;AAShD,WAAK,OAAO,MAAM,kCAAkC;AACpD,YAAM,KAAK,QAAQ,QAAQ,kBAAkB;AAE7C,WAAK,OAAO,KAAK,2BAAsB;AAAA,IAC3C,SAAS,OAAO;AACZ,WAAK,QAAQ;AACb,YAAM;AAAA,IACV;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAA0B;AAC5B,QAAI,KAAK,UAAU,aAAa,KAAK,UAAU,YAAY;AACvD,WAAK,OAAO,KAAK,oCAAoC;AACrD;AAAA,IACJ;AAEA,QAAI,KAAK,UAAU,WAAW;AAC1B,YAAM,IAAI,MAAM,6BAA6B;AAAA,IACjD;AAEA,SAAK,QAAQ;AACb,SAAK,OAAO,KAAK,2BAA2B;AAW5C,UAAM,uBAAuB,IAAI,MAAM,2BAA2B;AAElE,QAAI;AAeA,YAAM;AAAA,QACF,KAAK,gBAAgB;AAAA,QACrB,KAAK,OAAO;AAAA,QACZ,MAAM;AAAA,MACV;AAEA,WAAK,QAAQ;AACb,WAAK,OAAO,KAAK,mCAA8B;AAAA,IACnD,SAAS,OAAO;AACZ,WAAK,QAAQ;AAEb,UAAI,UAAU,sBAAsB;AAKhC,aAAK,OAAO,MAAM,0CAAqC,KAAc;AAErE,cAAM,KAAK,OAAO,QAAQ;AAC1B,gBAAQ,KAAK,CAAC;AAAA,MAClB,OAAO;AAUH,aAAK,OAAO;AAAA,UACR;AAAA,UACA;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ,UAAE;AACE,YAAM,KAAK,OAAO,QAAQ;AAAA,IAC9B;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAkB,YAAkC;AACtD,WAAO,MAAM,KAAK,aAAa,kBAAkB,UAAU;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,wBAAmD;AACrD,UAAM,UAAU,oBAAI,IAAI;AAExB,eAAW,cAAc,KAAK,QAAQ,KAAK,GAAG;AAC1C,YAAM,SAAS,MAAM,KAAK,kBAAkB,UAAU;AACtD,cAAQ,IAAI,YAAY,MAAM;AAAA,IAClC;AAEA,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAwC;AACpC,WAAO,IAAI,IAAI,KAAK,gBAAgB;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,UAAU,MAAuB;AAC7B,WAAO,KAAK,QAAQ,IAAI,IAAI;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,WAAc,MAAiB;AAC3B,WAAO,KAAK,QAAQ,WAAc,IAAI;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAAmB,MAAc,SAA8B;AACjE,WAAO,MAAM,KAAK,aAAa,WAAc,MAAM,OAAO;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAW,SAAuB;AAC9B,SAAK,aAAa,WAAW,OAAO;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAqB;AACjB,WAAO,KAAK,UAAU;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,WAAmB;AACf,WAAO,KAAK;AAAA,EAChB;AAAA;AAAA,EAIA,MAAc,sBAAsB,QAAuC;AACvE,UAAM,UAAU,OAAO,kBAAkB,KAAK,OAAO;AAErD,SAAK,OAAO,MAAM,SAAS,OAAO,IAAI,IAAI,EAAE,QAAQ,OAAO,KAAK,CAAC;AAIjE,kCAA8B,QAAQ,CAAC,SAAS,KAAK,cAAc,IAAI,CAAC;AAExE,SAAK,wBAAwB,OAAO;AACpC,QAAI;AACA,YAAM,KAAK;AAAA,QACP,OAAO,KAAK,KAAK,OAAO;AAAA,QACxB;AAAA,QACA,UAAU,OAAO,IAAI,uBAAuB,OAAO;AAAA,MACvD;AAAA,IACJ,UAAE;AACE,WAAK,wBAAwB;AAAA,IACjC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA2BA,MAAc,mBACV,WACA,SACA,SACU;AACV,WAAO,gBAAgB,WAAW,SAAS,MAAM,IAAI,MAAM,OAAO,CAAC;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,cAAc,MAAuB;AACzC,WAAO,KAAK,SAAS,IAAI,IAAI,KAAK,KAAK,aAAa,WAAW,IAAI;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,uBAAuB,aAA6B;AACxD,WAAO,uBAAuB,KAAK,uBAAuB,KAAK,QAAQ,OAAO,GAAG,WAAW;AAAA,EAChG;AAAA,EAEA,MAAc,uBAAuB,QAAsD;AACvF,QAAI,CAAC,OAAO,OAAO;AACf,aAAO,EAAE,SAAS,MAAM,YAAY,OAAO,KAAK;AAAA,IACpD;AAEA,UAAM,UAAU,OAAO,kBAAkB,KAAK,OAAO;AACrD,UAAM,YAAY,KAAK,IAAI;AAE3B,SAAK,OAAO,MAAM,UAAU,OAAO,IAAI,IAAI,EAAE,QAAQ,OAAO,KAAK,CAAC;AAElE,QAAI;AACA,YAAM,KAAK;AAAA,QACP,OAAO,MAAM,KAAK,OAAO;AAAA,QACzB;AAAA,QACA,UAAU,OAAO,IAAI,wBAAwB,OAAO;AAAA,MACxD;AAEA,YAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,WAAK,eAAe,IAAI,OAAO,IAAI;AACnC,WAAK,iBAAiB,IAAI,OAAO,MAAM,QAAQ;AAE/C,WAAK,OAAO,MAAM,mBAAmB,OAAO,IAAI,KAAK,QAAQ,KAAK;AAElE,aAAO;AAAA,QACH,SAAS;AAAA,QACT,YAAY,OAAO;AAAA,QACnB,WAAW;AAAA,MACf;AAAA,IACJ,SAAS,OAAO;AACZ,YAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,YAAM,YAAa,MAAgB,QAAQ,SAAS,SAAS;AAE7D,aAAO;AAAA,QACH,SAAS;AAAA,QACT,YAAY,OAAO;AAAA,QACnB;AAAA,QACA,WAAW;AAAA,QACX,UAAU;AAAA,MACd;AAAA,IACJ;AAAA,EACJ;AAAA,EAEA,MAAc,yBAAwC;AAClD,UAAM,oBAAoB,MAAM,KAAK,KAAK,cAAc,EAAE,QAAQ;AAElE,eAAW,cAAc,mBAAmB;AACxC,YAAM,SAAS,KAAK,QAAQ,IAAI,UAAU;AAC1C,UAAI,QAAQ,SAAS;AACjB,YAAI;AACA,eAAK,OAAO,MAAM,aAAa,UAAU,EAAE;AAC3C,gBAAM,OAAO,QAAQ;AAAA,QACzB,SAAS,OAAO;AACZ,eAAK,OAAO,MAAM,uBAAuB,UAAU,IAAI,KAAc;AAAA,QACzE;AAAA,MACJ;AAAA,IACJ;AAEA,SAAK,eAAe,MAAM;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiCA,MAAc,+BAA8C;AACxD,UAAM,sBAAsB,mBAAmB,KAAK,MAAM,IAAI,iBAAiB,KAAK,CAAC,GAAG,KAAK,MAAM;AAAA,EACvG;AAAA,EAEA,MAAc,kBAAiC;AAK3C,UAAM,KAAK,6BAA6B;AAGxC,UAAM,iBAAiB,MAAM,KAAK,KAAK,QAAQ,OAAO,CAAC,EAAE,QAAQ;AACjE,eAAW,UAAU,gBAAgB;AACjC,UAAI,OAAO,SAAS;AAChB,aAAK,OAAO,MAAM,YAAY,OAAO,IAAI,IAAI,EAAE,QAAQ,OAAO,KAAK,CAAC;AACpE,YAAI;AACA,gBAAM,OAAO,QAAQ;AAAA,QACzB,SAAS,OAAO;AACZ,eAAK,OAAO,MAAM,2BAA2B,OAAO,IAAI,IAAI,KAAc;AAAA,QAC9E;AAAA,MACJ;AAAA,IACJ;AAGA,eAAW,WAAW,KAAK,kBAAkB;AACzC,UAAI;AACA,cAAM,QAAQ;AAAA,MAClB,SAAS,OAAO;AACZ,aAAK,OAAO,MAAM,0BAA0B,KAAc;AAAA,MAC9D;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,sBAAwC;AAC5C,WAAO,mBAAmB,KAAK,OAAO;AAAA,EAC1C;AAAA,EAEQ,0BAAgC;AACpC,UAAM,UAA4B,CAAC,UAAU,WAAW,SAAS;AACjE,QAAI,qBAAqB;AAEzB,UAAM,iBAAiB,OAAO,WAAmB;AAC7C,UAAI,oBAAoB;AACpB,aAAK,OAAO,KAAK,0CAA0C,MAAM,EAAE;AACnE;AAAA,MACJ;AAEA,2BAAqB;AACrB,WAAK,OAAO,KAAK,YAAY,MAAM,iCAAiC;AAEpE,UAAI;AACA,cAAM,KAAK,SAAS;AACpB,iBAAS,CAAC;AAAA,MACd,SAAS,OAAO;AACZ,aAAK,OAAO,MAAM,mBAAmB,KAAc;AACnD,iBAAS,CAAC;AAAA,MACd;AAAA,IACJ;AAEA,QAAI,QAAQ;AACR,iBAAW,UAAU,SAAS;AAC1B,gBAAQ,GAAG,QAAQ,MAAM,eAAe,MAAM,CAAC;AAAA,MACnD;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAAoC;AAC3C,SAAK,iBAAiB,KAAK,OAAO;AAAA,EACtC;AACJ;;;Ae7yBO,IAAM,aAAN,cAAyB,iBAAiB;AAAA,EAC7C,YAAY,QAA6C;AACrD,UAAM,SAAS,aAAa,QAAQ,MAAM;AAC1C,UAAM,MAAM;AAGZ,SAAK,UAAU,KAAK,cAAc;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,IAAI,QAAsB;AACtB,SAAK,aAAa;AAElB,yBAAqB,KAAK,SAAS,QAAQ,KAAK,MAAM;AAEtD,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,YAA2B;AAC7B,SAAK,cAAc,MAAM;AAEzB,SAAK,QAAQ;AACb,SAAK,OAAO,KAAK,mBAAmB;AAGpC,UAAM,iBAAiB,KAAK,oBAAoB;AAKhD,SAAK,qBAAqB,cAAc;AAGxC,SAAK,OAAO,KAAK,uBAAuB;AACxC,eAAW,UAAU,gBAAgB;AACjC,YAAM,KAAK,cAAc,MAAM;AAAA,IACnC;AAGA,SAAK,OAAO,KAAK,wBAAwB;AACzC,SAAK,QAAQ;AAEb,eAAW,UAAU,gBAAgB;AACjC,YAAM,KAAK,eAAe,MAAM;AAAA,IACpC;AAsBA,QAAI;AAKA,YAAM,KAAK,mBAAmB,cAAc;AAM5C,YAAM,KAAK,mBAAmB,qBAAqB;AAGnD,YAAM,KAAK,mBAAmB,kBAAkB;AAAA,IACpD,SAAS,OAAO;AACZ,WAAK,QAAQ;AACb,YAAM;AAAA,IACV;AACA,SAAK,OAAO,KAAK,6BAAwB;AAAA,MACrC,aAAa,KAAK,QAAQ;AAAA,IAC9B,CAAC;AAAA,EACL;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,WAA0B;AAC5B,UAAM,KAAK,QAAQ;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC3B,QAAI,KAAK,UAAU,WAAW;AAC1B,WAAK,OAAO,KAAK,wBAAwB;AACzC;AAAA,IACJ;AAEA,SAAK,QAAQ;AACb,SAAK,OAAO,KAAK,kBAAkB;AAanC,UAAM,KAAK,YAAY,iBAAiB;AAGxC,UAAM,iBAAiB,KAAK,oBAAoB;AAChD,eAAW,UAAU,eAAe,QAAQ,GAAG;AAC3C,YAAM,KAAK,iBAAiB,MAAM;AAAA,IACtC;AAEA,SAAK,QAAQ;AACb,SAAK,OAAO,KAAK,0BAAqB;AAGtC,QAAI,KAAK,UAAU,OAAQ,KAAK,OAAwB,YAAY,YAAY;AAC5E,YAAO,KAAK,OAAwB,QAAQ;AAAA,IAChD;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAc,MAAiB;AAC3B,WAAO,KAAK,QAAQ,WAAc,IAAI;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,YAAqB;AACjB,WAAO,KAAK,UAAU;AAAA,EAC1B;AACJ;;;ACpMA;AAAA;AAAA;AAAA;AAAA;;;AC0BA,SAAS,mBAAmB,OAAwB;AAClD,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO;AACjC,SAAO,OAAO;AAChB;AASA,SAAS,yBAAyB,QAAyB;AACzD,MAAI,WAAW,QAAW;AACxB,WACE;AAAA,EAGJ;AACA,MAAI,WAAW,MAAM;AACnB,WAAO;AAAA,EACT;AACA,SACE;AAGJ;AAEO,IAAM,aAAN,MAAiB;AAAA,EACtB,YAAoB,SAA+B;AAA/B;AAAA,EAAgC;AAAA,EAEpD,MAAM,SAAS,OAA4C;AACzD,UAAM,UAAwB,CAAC;AAC/B,eAAW,YAAY,MAAM,WAAW;AACtC,cAAQ,KAAK,MAAM,KAAK,YAAY,QAAQ,CAAC;AAAA,IAC/C;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,YAAY,UAAgD;AAChE,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,UAAmC,CAAC;AAM1C,QAAI,SAAS,OAAO;AAClB,iBAAW,QAAQ,SAAS,OAAO;AACjC,YAAI;AACF,gBAAM,KAAK,QAAQ,MAAM,OAAO;AAAA,QAClC,SAAS,GAAG;AACT,iBAAO;AAAA,YACL,YAAY,SAAS;AAAA,YACrB,QAAQ;AAAA,YACR,OAAO,CAAC;AAAA,YACR,OAAO,iBAAiB,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,YAClE,UAAU,KAAK,IAAI,IAAI;AAAA,UACzB;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,UAAM,cAA4B,CAAC;AACnC,QAAI,iBAAiB;AACrB,QAAI,gBAAyB;AAG7B,eAAW,QAAQ,SAAS,OAAO;AACjC,YAAM,gBAAgB,KAAK,IAAI;AAC/B,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,QAAQ,MAAM,OAAO;AAC/C,oBAAY,KAAK;AAAA,UACf,UAAU,KAAK;AAAA,UACf,QAAQ;AAAA,UACR;AAAA,UACA,UAAU,KAAK,IAAI,IAAI;AAAA,QACzB,CAAC;AAAA,MACH,SAAS,GAAG;AACV,yBAAiB;AACjB,wBAAgB;AAChB,oBAAY,KAAK;AAAA,UACf,UAAU,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,OAAO;AAAA,UACP,UAAU,KAAK,IAAI,IAAI;AAAA,QACzB,CAAC;AACD;AAAA,MACF;AAAA,IACF;AAGA,QAAI,SAAS,UAAU;AACrB,iBAAW,QAAQ,SAAS,UAAU;AACpC,YAAI;AACF,gBAAM,KAAK,QAAQ,MAAM,OAAO;AAAA,QAClC,SAAS,GAAG;AAEV,cAAI,gBAAgB;AACjB,6BAAiB;AACjB,4BAAgB,oBAAoB,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,UACjF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,YAAY,SAAS;AAAA,MACrB,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,OAAO;AAAA,MACP,UAAU,KAAK,IAAI,IAAI;AAAA,IACzB;AAAA,EACF;AAAA,EAEA,MAAc,QAAQ,MAAmB,SAAoD;AAG3F,UAAM,iBAAiB,KAAK,iBAAiB,KAAK,QAAQ,OAAO;AAGjE,UAAM,SAAS,MAAM,KAAK,QAAQ,QAAQ,gBAAgB,OAAO;AAGjE,QAAI,KAAK,SAAS;AAChB,iBAAW,CAAC,SAAS,IAAI,KAAK,OAAO,QAAQ,KAAK,OAAO,GAAG;AAC1D,gBAAQ,OAAO,IAAI,KAAK,eAAe,QAAQ,IAAI;AAAA,MACrD;AAAA,IACF;AAGA,QAAI,KAAK,YAAY;AACnB,iBAAW,aAAa,KAAK,YAAY;AACvC,aAAK,OAAO,QAAQ,WAAW,OAAO;AAAA,MACxC;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,iBAAiB,QAAuB,SAAiD;AAC/F,UAAM,YAAY,KAAK,UAAU,MAAM;AACvC,UAAM,WAAW,UAAU,QAAQ,oBAAoB,CAAC,QAAQ,YAAoB;AAClF,YAAM,QAAQ,KAAK,eAAe,SAAS,QAAQ,KAAK,CAAC;AACzD,UAAI,UAAU,OAAW,QAAO;AAChC,aAAO,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,KAAK;AAAA,IACjE,CAAC;AACD,QAAI;AACF,aAAO,KAAK,MAAM,QAAQ;AAAA,IAC5B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,eAAe,KAAc,MAAuB;AAC1D,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,QAAI,UAAe;AACnB,eAAW,QAAQ,OAAO;AACxB,UAAI,YAAY,QAAQ,YAAY,OAAW,QAAO;AACtD,gBAAU,QAAQ,IAAI;AAAA,IACxB;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,OAAO,QAAiB,WAA6B,UAAmC;AAC9F,UAAM,SAAS,KAAK,eAAe,QAAQ,UAAU,KAAK;AAE1D,UAAM,WAAW,UAAU;AAE3B,YAAQ,UAAU,UAAU;AAAA,MAC1B,KAAK;AACH,YAAI,WAAW,SAAU,OAAM,IAAI,MAAM,qBAAqB,UAAU,KAAK,aAAa,QAAQ,SAAS,MAAM,EAAE;AACnH;AAAA,MACF,KAAK;AACH,YAAI,WAAW,SAAU,OAAM,IAAI,MAAM,qBAAqB,UAAU,KAAK,iBAAiB,QAAQ,SAAS,MAAM,EAAE;AACvH;AAAA,MACF,KAAK;AACF,YAAI,MAAM,QAAQ,MAAM,GAAG;AACvB,cAAI,CAAC,OAAO,SAAS,QAAQ,EAAG,OAAM,IAAI,MAAM,qBAAqB,UAAU,KAAK,2BAA2B,QAAQ,EAAE;AAAA,QAC7H,WAAW,OAAO,WAAW,UAAU;AACnC,cAAI,CAAC,OAAO,SAAS,OAAO,QAAQ,CAAC,EAAG,OAAM,IAAI,MAAM,qBAAqB,UAAU,KAAK,4BAA4B,QAAQ,EAAE;AAAA,QACtI,OAAO;AASH,gBAAM,IAAI;AAAA,YACN,qBAAqB,UAAU,KAAK,6FACe,mBAAmB,MAAM,CAAC,OAC7E,yBAAyB,MAAM;AAAA,UACnC;AAAA,QACJ;AACA;AAAA,MACH,KAAK;AACH,YAAI,WAAW,QAAQ,WAAW,OAAW,OAAM,IAAI,MAAM,qBAAqB,UAAU,KAAK,UAAU;AAC3G;AAAA,MACF,KAAK;AACF,YAAI,WAAW,QAAQ,WAAW,OAAW,OAAM,IAAI,MAAM,qBAAqB,UAAU,KAAK,cAAc;AAC/G;AAAA;AAAA,MAEH;AACE,cAAM,IAAI,MAAM,+BAA+B,UAAU,QAAQ,EAAE;AAAA,IACvE;AAAA,EACF;AACF;;;ACzOA,iBAA+D;AAI/D,IAAI;AAiCJ,SAAS,mBAA0D;AACjE,MAAI,oBAAoB,QAAW;AACjC,UAAM,MAAM,+BAAoB,MAAM,CAAC,CAAC;AACxC,UAAM,OAAO,qCAA0B,MAAM,CAAC,CAAC;AAC/C,UAAM,UAAU,IAAI,WAAW,GAAG,IAAI,QAAQ,IAAI,IAAI,OAAO;AAC7D,sBAAkB,EAAE,SAAS,UAAU,GAAG,OAAO,GAAG,KAAK,UAAU,GAAG;AAAA,EACxE;AACA,SAAO;AACT;AAkBO,IAAM,kBAAN,MAAsD;AAAA,EAU3D,YAAoB,SAAyB,WAAoB;AAA7C;AAAyB;AAAA,EAAqB;AAAA;AAAA,EAG1D,YAAwC;AAC9C,QAAI,KAAK,iBAAiB,QAAW;AACnC,WAAK,eAAe,KAAK,iBAAiB;AAAA,IAC5C;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsCA,MAAc,mBAA+C;AAC3D,UAAM,EAAE,SAAS,SAAS,IAAI,iBAAiB;AAC/C,UAAM,WAAW,GAAG,KAAK,OAAO,GAAG,OAAO;AAC1C,QAAI;AAEJ,QAAI;AACF,YAAM,UAAkC,CAAC;AACzC,UAAI,KAAK,WAAW;AAClB,gBAAQ,eAAe,IAAI,UAAU,KAAK,SAAS;AAAA,MACrD;AACA,YAAM,WAAW,MAAM,MAAM,UAAU,EAAE,QAAQ,OAAO,QAAQ,CAAC;AACjE,UAAI,SAAS,IAAI;AACf,cAAM,OAAO,MAAM,SAAS,KAAK;AAKjC,cAAM,MAAO,QAAQ,OAAO,SAAS,YAAY,YAAY,OACxD,OACC,MAAiE;AACvE,cAAM,aAAa,KAAK,QAAQ;AAChC,YAAI,OAAO,eAAe,YAAY,WAAW,SAAS,GAAG;AAC3D,iBAAO;AAAA,YACL,MAAM;AAAA,YACN,QAAQ;AAAA,YACR,KAAK,OAAO,QAAQ;AAAA,UACtB;AAAA,QACF;AACA,cAAM,OAAO,QAAQ,aAAa,SAAS,MAAM;AAAA,MACnD,OAAO;AACL,cAAM,OAAO,QAAQ,aAAa,SAAS,MAAM;AAAA,MACnD;AAAA,IACF,SAAS,OAAO;AACd,YAAM,OAAO,QAAQ,0BAA2B,MAAgB,OAAO;AAAA,IACzE;AAEA,UAAM,QAA2B,EAAE,MAAM,UAAU,QAAQ,cAAc,IAAI;AAI7E,YAAQ;AAAA,MACN,4KAEkB,KAAK,OAAO,GAAG,QAAQ,oIAC4C,GAAG;AAAA,IAI1F;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,cAAc,OAA0B,YAA4B;AAC1E,WAAO,GAAG,KAAK,OAAO,GAAG,MAAM,IAAI,IAAI,mBAAmB,UAAU,CAAC;AAAA,EACvE;AAAA;AAAA,EAGQ,UAAU,OAA0B,YAAoB,IAAqB;AACnF,WAAO,GAAG,KAAK,cAAc,OAAO,UAAU,CAAC,IAAI,mBAAmB,OAAO,EAAE,CAAC,CAAC;AAAA,EACnF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,UAAU,OAAkC;AAClD,UAAM,MAAM,MAAM,WAAW,cACzB,4BACA;AACJ,WAAO,4BAA4B,KAAK,OAAO,GAAG,MAAM,IAAI,gBAAgB,GAAG,KAAK,MAAM,GAAG;AAAA,EAC/F;AAAA,EAEA,MAAM,QAAQ,QAAuB,UAAqD;AACxF,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,IAClB;AACA,QAAI,KAAK,WAAW;AAClB,cAAQ,eAAe,IAAI,UAAU,KAAK,SAAS;AAAA,IACrD;AAEA,QAAI,OAAO,MAAM;AACb,cAAQ,UAAU,IAAI,OAAO;AAAA,IACjC;AAEA,YAAQ,OAAO,MAAM;AAAA,MACnB,KAAK;AACH,eAAO,KAAK,aAAa,OAAO,QAAQ,OAAO,WAAW,CAAC,GAAG,OAAO;AAAA,MACvE,KAAK;AACH,eAAO,KAAK,aAAa,OAAO,QAAQ,OAAO,WAAW,CAAC,GAAG,OAAO;AAAA,MACvE,KAAK;AACH,eAAO,KAAK,aAAa,OAAO,QAAQ,OAAO,WAAW,CAAC,GAAG,OAAO;AAAA,MACvE,KAAK;AACH,eAAO,KAAK,WAAW,OAAO,QAAQ,OAAO,WAAW,CAAC,GAAG,OAAO;AAAA,MACnE,KAAK;AACL,eAAO,KAAK,aAAa,OAAO,QAAQ,OAAO,WAAW,CAAC,GAAG,OAAO;AAAA,MACvE,KAAK;AACH,eAAO,KAAK,WAAW,OAAO,QAAQ,OAAO,WAAW,CAAC,GAAG,OAAO;AAAA,MACnE,KAAK;AACD,cAAM,KAAK,OAAO,OAAO,SAAS,YAAY,GAAI;AAClD,eAAO,IAAI,QAAQ,aAAW,WAAW,MAAM,QAAQ,EAAE,QAAQ,GAAG,CAAC,GAAG,EAAE,CAAC;AAAA,MACjF;AACE,cAAM,IAAI,MAAM,2CAA2C,OAAO,IAAI,EAAE;AAAA,IAC5E;AAAA,EACF;AAAA,EAEA,MAAc,aAAa,YAAoB,MAA+B,SAAiC;AAC7G,UAAM,QAAQ,MAAM,KAAK,UAAU;AACnC,UAAM,WAAW,MAAM,MAAM,KAAK,cAAc,OAAO,UAAU,GAAG;AAAA,MAClE,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AACD,WAAO,KAAK,eAAe,UAAU,KAAK,UAAU,KAAK,CAAC;AAAA,EAC5D;AAAA,EAEA,MAAc,aAAa,YAAoB,MAA+B,SAAiC;AAC7G,UAAM,EAAE,IAAI,GAAG,OAAO,IAAI;AAC1B,QAAI,CAAC,GAAI,OAAM,IAAI,MAAM,sCAAsC;AAK/D,UAAM,QAAQ,MAAM,KAAK,UAAU;AACnC,UAAM,WAAW,MAAM,MAAM,KAAK,UAAU,OAAO,YAAY,EAAE,GAAG;AAAA,MAClE,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,MAAM;AAAA,IAC7B,CAAC;AACD,WAAO,KAAK,eAAe,UAAU,KAAK,UAAU,KAAK,CAAC;AAAA,EAC5D;AAAA,EAEA,MAAc,aAAa,YAAoB,MAA+B,SAAiC;AAC7G,UAAM,KAAK,KAAK;AAChB,QAAI,CAAC,GAAI,OAAM,IAAI,MAAM,sCAAsC;AAC/D,UAAM,QAAQ,MAAM,KAAK,UAAU;AACnC,UAAM,WAAW,MAAM,MAAM,KAAK,UAAU,OAAO,YAAY,EAAE,GAAG;AAAA,MAClE,QAAQ;AAAA,MACR;AAAA,IACF,CAAC;AACD,WAAO,KAAK,eAAe,UAAU,KAAK,UAAU,KAAK,CAAC;AAAA,EAC5D;AAAA,EAEA,MAAc,WAAW,YAAoB,MAA+B,SAAiC;AAC3G,UAAM,KAAK,KAAK;AAChB,QAAI,CAAC,GAAI,OAAM,IAAI,MAAM,oCAAoC;AAC7D,UAAM,QAAQ,MAAM,KAAK,UAAU;AACnC,UAAM,WAAW,MAAM,MAAM,KAAK,UAAU,OAAO,YAAY,EAAE,GAAG;AAAA,MAClE,QAAQ;AAAA,MACR;AAAA,IACF,CAAC;AACD,WAAO,KAAK,eAAe,UAAU,KAAK,UAAU,KAAK,CAAC;AAAA,EAC5D;AAAA,EAEA,MAAc,aAAa,YAAoB,MAA+B,SAAiC;AAG3G,UAAM,QAAQ,MAAM,KAAK,UAAU;AACnC,UAAM,WAAW,MAAM,MAAM,GAAG,KAAK,cAAc,OAAO,UAAU,CAAC,UAAU;AAAA,MAC3E,QAAQ;AAAA,MACR;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC7B,CAAC;AACD,WAAO,KAAK,eAAe,UAAU,KAAK,UAAU,KAAK,CAAC;AAAA,EAC9D;AAAA,EAEA,MAAc,WAAW,UAAkB,MAA+B,SAAiC;AAKvG,UAAM,SAAU,KAAK,UAAqB;AAC1C,UAAM,OAAO,KAAK,OAAO,KAAK,UAAU,KAAK,IAAI,IAAI;AACrD,UAAM,MAAM,SAAS,WAAW,MAAM,IAAI,WAAW,GAAG,KAAK,OAAO,GAAG,QAAQ;AAE/E,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAC9B;AAAA,MACA;AAAA,MACA;AAAA,IACJ,CAAC;AACD,WAAO,KAAK,eAAe,QAAQ;AAAA,EACvC;AAAA,EAEA,MAAc,eAAe,UAAoB,WAAoB;AACnE,QAAI,CAAC,SAAS,IAAI;AACd,YAAM,OAAO,MAAM,SAAS,KAAK;AAWjC,YAAM,iBAAiB,SAAS,WAAW,OAAO,SAAS,WAAW;AACtE,YAAM,IAAI;AAAA,QACR,cAAc,SAAS,MAAM,KAAK,IAAI,GAAG,kBAAkB,YAAY,WAAM,SAAS,KAAK,EAAE;AAAA,MAC/F;AAAA,IACJ;AACA,UAAM,cAAc,SAAS,QAAQ,IAAI,cAAc;AACvD,QAAI,eAAe,YAAY,SAAS,kBAAkB,GAAG;AACzD,aAAO,SAAS,KAAK;AAAA,IACzB;AACA,WAAO,SAAS,KAAK;AAAA,EACvB;AACF;;;ACvUA,IAAI,eAA+C;AAiE5C,IAAM,0BAAN,MAA8B;AAAA,EAInC,YAAY,QAA+B,QAAgB;AACzD,SAAK,SAAS;AACd,SAAK,SAAS;AAEd,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,sBAAsB,QAA8D;AAExF,QAAI,CAAC,OAAO,WAAW;AACrB,aAAO,KAAK,qBAAqB,MAAM;AAAA,IACzC;AAEA,QAAI;AAEF,YAAM,cAAc,KAAK,mBAAmB,OAAO,IAAI;AAGvD,YAAM,YAAY,KAAK,OAAO,kBAAkB,IAAI,WAAW;AAC/D,UAAI,CAAC,WAAW;AACd,cAAM,QAAQ,wCAAwC,WAAW;AACjE,aAAK,OAAO,KAAK,OAAO,EAAE,QAAQ,OAAO,MAAM,YAAY,CAAC;AAE5D,YAAI,KAAK,OAAO,cAAc,CAAC,KAAK,OAAO,iBAAiB;AAC1D,gBAAM,IAAI,MAAM,KAAK;AAAA,QACvB;AAEA,eAAO;AAAA,UACL,UAAU;AAAA,UACV;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAGA,YAAM,aAAa,KAAK,kBAAkB,MAAM;AAGhD,YAAM,UAAU,MAAM,KAAK;AAAA,QACzB;AAAA,QACA,OAAO;AAAA,QACP;AAAA,MACF;AAEA,UAAI,CAAC,SAAS;AACZ,cAAM,QAAQ,6CAA6C,OAAO,IAAI;AACtE,aAAK,OAAO,MAAM,OAAO,QAAW,EAAE,QAAQ,OAAO,MAAM,YAAY,CAAC;AACxE,cAAM,IAAI,MAAM,KAAK;AAAA,MACvB;AAEA,WAAK,OAAO,KAAK,qCAAgC,OAAO,IAAI,IAAI;AAAA,QAC9D,QAAQ,OAAO;AAAA,QACf;AAAA,QACA,WAAW,KAAK,OAAO;AAAA,MACzB,CAAC;AAED,aAAO;AAAA,QACL,UAAU;AAAA,QACV;AAAA,QACA,WAAW,KAAK,OAAO;AAAA,MACzB;AAAA,IAEF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,iCAAiC,OAAO,IAAI,IAAI,KAAc;AAEhF,UAAI,KAAK,OAAO,YAAY;AAC1B,cAAM;AAAA,MACR;AAEA,aAAO;AAAA,QACL,UAAU;AAAA,QACV,OAAQ,MAAgB;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,aAAqB,WAAyB;AAC9D,SAAK,OAAO,kBAAkB,IAAI,aAAa,SAAS;AACxD,SAAK,OAAO,KAAK,sCAAsC,WAAW,EAAE;AAAA,EACtE;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,aAA2B;AACzC,SAAK,OAAO,kBAAkB,OAAO,WAAW;AAChD,SAAK,OAAO,KAAK,2BAA2B,WAAW,EAAE;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA,EAKA,uBAAiC;AAC/B,WAAO,MAAM,KAAK,KAAK,OAAO,kBAAkB,KAAK,CAAC;AAAA,EACxD;AAAA;AAAA,EAIQ,qBAAqB,QAAqD;AAChF,QAAI,KAAK,OAAO,YAAY;AAC1B,YAAM,QAAQ,2CAA2C,OAAO,IAAI;AACpE,WAAK,OAAO,MAAM,OAAO,QAAW,EAAE,QAAQ,OAAO,KAAK,CAAC;AAC3D,YAAM,IAAI,MAAM,KAAK;AAAA,IACvB;AAEA,SAAK,OAAO,KAAK,oCAA0B,OAAO,IAAI,IAAI;AAAA,MACxD,QAAQ,OAAO;AAAA,MACf,gBAAgB;AAAA,IAClB,CAAC;AAED,WAAO;AAAA,MACL,UAAU;AAAA,MACV,OAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,mBAAmB,YAA4B;AAGrD,UAAM,QAAQ,WAAW,MAAM,GAAG;AAElC,QAAI,MAAM,SAAS,GAAG;AACpB,YAAM,IAAI,MAAM,+BAA+B,UAAU,qCAAqC;AAAA,IAChG;AAGA,WAAO,GAAG,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AAAA,EAChC;AAAA,EAEQ,kBAAkB,QAAgC;AAExD,QAAI,OAAQ,WAAmB,WAAW,aAAa;AACrD,aAAO,KAAK,yBAAyB,MAAM;AAAA,IAC7C;AAGA,WAAO,KAAK,sBAAsB,MAAM;AAAA,EAC1C;AAAA,EAEQ,sBAAsB,QAAgC;AAE5D,QAAI,CAAC,cAAc;AACjB,WAAK,OAAO,KAAK,kDAAkD;AACnE,aAAO,KAAK,0BAA0B,MAAM;AAAA,IAC9C;AAGA,UAAM,aAAa,KAAK,oBAAoB,MAAM;AAClD,WAAO,aAAa,WAAW,QAAQ,EAAE,OAAO,UAAU,EAAE,OAAO,KAAK;AAAA,EAC1E;AAAA,EAEQ,yBAAyB,QAAgC;AAG/D,SAAK,OAAO,MAAM,uDAAuD;AACzE,WAAO,KAAK,0BAA0B,MAAM;AAAA,EAC9C;AAAA,EAEQ,0BAA0B,QAAgC;AAEhE,UAAM,aAAa,KAAK,oBAAoB,MAAM;AAClD,QAAI,OAAO;AAEX,aAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,YAAM,OAAO,WAAW,WAAW,CAAC;AACpC,cAAS,QAAQ,KAAK,OAAQ;AAC9B,aAAO,OAAO;AAAA,IAChB;AAEA,WAAO,KAAK,SAAS,EAAE;AAAA,EACzB;AAAA,EAEQ,oBAAoB,QAAgC;AAG1D,UAAM,QAAkB;AAAA,MACtB,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO,KAAK,SAAS;AAAA,IACvB;AAEA,QAAI,OAAO,OAAO;AAChB,YAAM,KAAK,OAAO,MAAM,SAAS,CAAC;AAAA,IACpC;AAEA,QAAI,OAAO,SAAS;AAClB,YAAM,KAAK,OAAO,QAAQ,SAAS,CAAC;AAAA,IACtC;AAEA,WAAO,MAAM,KAAK,GAAG;AAAA,EACvB;AAAA,EAEA,MAAc,sBACZ,MACA,WACA,WACkB;AAElB,QAAI,OAAQ,WAAmB,WAAW,aAAa;AACrD,aAAO,KAAK,6BAA6B,MAAM,WAAW,SAAS;AAAA,IACrE;AAGA,WAAO,KAAK,0BAA0B,MAAM,WAAW,SAAS;AAAA,EAClE;AAAA,EAEA,MAAc,0BACZ,MACA,WACA,WACkB;AAClB,QAAI,CAAC,cAAc;AACjB,UAAI;AAEF,uBAAe,MAAM,OAAO,QAAQ;AAAA,MACtC,SAAS,GAAG;AAAA,MAEZ;AAAA,IACF;AAEA,QAAI,CAAC,cAAc;AACjB,WAAK,OAAO,MAAM,wDAAwD;AAC1E,aAAO;AAAA,IACT;AAEA,QAAI;AAEF,UAAI,KAAK,OAAO,cAAc,SAAS;AAErC,cAAM,SAAS,aAAa,aAAa,QAAQ;AACjD,eAAO,OAAO,IAAI;AAClB,eAAO,OAAO;AAAA,UACZ;AAAA,YACE,KAAK;AAAA,YACL,QAAQ;AAAA,YACR,MAAM;AAAA,UACR;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF,OAAO;AAEL,cAAM,SAAS,aAAa,aAAa,YAAY;AACrD,eAAO,OAAO,IAAI;AAClB,eAAO,OAAO,OAAO,WAAW,WAAW,QAAQ;AAAA,MACrD;AAAA,IACF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,iCAAiC,KAAc;AACjE,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAc,6BACZ,MACA,WACA,WACkB;AAClB,QAAI;AACF,YAAM,SAAS,WAAW,QAAQ;AAClC,UAAI,CAAC,QAAQ;AACX,aAAK,OAAO,MAAM,gDAAgD;AAClE,eAAO;AAAA,MACT;AAGA,YAAM,UAAU,UACb,QAAQ,8BAA8B,EAAE,EACxC,QAAQ,4BAA4B,EAAE,EACtC,QAAQ,OAAO,EAAE;AACpB,YAAM,WAAW,WAAW,KAAK,KAAK,OAAO,GAAG,OAAK,EAAE,WAAW,CAAC,CAAC;AAGpE,UAAI;AACJ,UAAI;AAEJ,UAAI,KAAK,OAAO,cAAc,SAAS;AACrC,0BAAkB,EAAE,MAAM,SAAS,YAAY,QAAQ;AACvD,0BAAkB,EAAE,MAAM,SAAS,MAAM,UAAU;AAAA,MACrD,OAAO;AACL,0BAAkB,EAAE,MAAM,qBAAqB,MAAM,UAAU;AAC/D,0BAAkB,EAAE,MAAM,oBAAoB;AAAA,MAChD;AAEA,YAAM,YAAY,MAAM,OAAO;AAAA,QAC7B;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,CAAC,QAAQ;AAAA,MACX;AAGA,YAAM,iBAAiB,WAAW,KAAK,KAAK,SAAS,GAAG,OAAK,EAAE,WAAW,CAAC,CAAC;AAG5E,YAAM,YAAY,IAAI,YAAY,EAAE,OAAO,IAAI;AAE/C,aAAO,MAAM,OAAO,OAAO,iBAAiB,WAAW,gBAAgB,SAAS;AAAA,IAClF,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,yCAAyC,KAAc;AACzE,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,iBAAuB;AAC7B,QAAI,CAAC,KAAK,OAAO,qBAAqB,KAAK,OAAO,kBAAkB,SAAS,GAAG;AAC9E,WAAK,OAAO,KAAK,8DAA8D;AAAA,IACjF;AAEA,QAAI,CAAC,KAAK,OAAO,WAAW;AAC1B,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,QAAI,CAAC,CAAC,SAAS,OAAO,EAAE,SAAS,KAAK,OAAO,SAAS,GAAG;AACvD,YAAM,IAAI,MAAM,0BAA0B,KAAK,OAAO,SAAS,EAAE;AAAA,IACnE;AAAA,EACF;AACF;;;AC3VO,IAAM,2BAAN,MAA+B;AAAA,EAKpC,YAAY,QAAgB;AAH5B,SAAQ,qBAAqD,oBAAI,IAAI;AACrE,SAAQ,qBAAsD,oBAAI,IAAI;AAGpE,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,0BAA0B,YAAoB,cAAwC;AACpF,SAAK,mBAAmB,IAAI,YAAY,YAAY;AAEpD,UAAM,cAAiC;AAAA,MACrC,kBAAkB,CAAC,YAAY,KAAK,mBAAmB,cAAc,OAAO;AAAA,MAC5E,gBAAgB,CAAC,SAAS,KAAK,gBAAgB,cAAc,IAAI;AAAA,MACjE,aAAa,CAAC,SAAS,KAAK,cAAc,cAAc,IAAI;AAAA,MAC5D,cAAc,CAAC,SAAS,KAAK,eAAe,cAAc,IAAI;AAAA,MAC9D,mBAAmB,CAAC,QAAQ,KAAK,mBAAmB,cAAc,GAAG;AAAA,IACvE;AAEA,SAAK,mBAAmB,IAAI,YAAY,WAAW;AAEnD,SAAK,OAAO,KAAK,sCAAsC,UAAU,IAAI;AAAA,MACnE,QAAQ;AAAA,MACR,iBAAiB,aAAa;AAAA,IAChC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,2BAA2B,YAAoB,SAAsD;AACnG,SAAK,mBAAmB,IAAI,YAAY,2BAA2B,OAAO,CAAC;AAC3E,SAAK,OAAO,KAAK,8CAA8C,UAAU,IAAI;AAAA,MAC3E,QAAQ;AAAA,MACR,UAAU,SAAS,UAAU,UAAU;AAAA,MACvC,OAAO,SAAS,OAAO,UAAU;AAAA,MACjC,SAAS,SAAS,SAAS,UAAU;AAAA,MACrC,IAAI,SAAS,IAAI,UAAU;AAAA,IAC7B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,qBAAqB,YAAoB,aAA2B;AAClE,UAAM,SAAS,KAAK,gBAAgB,YAAY,CAAC,UAAU,MAAM,iBAAiB,WAAW,CAAC;AAE9F,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,QAAQ,6BAA6B,UAAU,0BAA0B,WAAW;AAC1F,WAAK,OAAO,KAAK,OAAO;AAAA,QACtB,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,QAAQ,OAAO;AAAA,MACjB,CAAC;AACD,YAAM,IAAI,MAAM,KAAK;AAAA,IACvB;AAEA,SAAK,OAAO,MAAM,2BAA2B,UAAU,OAAO,WAAW,EAAE;AAAA,EAC7E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,mBAAmB,YAAoB,UAAwB;AAC7D,UAAM,SAAS,KAAK,gBAAgB,YAAY,CAAC,UAAU,MAAM,eAAe,QAAQ,CAAC;AAEzF,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,QAAQ,6BAA6B,UAAU,wBAAwB,QAAQ;AACrF,WAAK,OAAO,KAAK,OAAO;AAAA,QACtB,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,QAAQ,OAAO;AAAA,MACjB,CAAC;AACD,YAAM,IAAI,MAAM,KAAK;AAAA,IACvB;AAEA,SAAK,OAAO,MAAM,yBAAyB,UAAU,OAAO,QAAQ,EAAE;AAAA,EACxE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,gBAAgB,YAAoB,MAAoB;AACtD,UAAM,SAAS,KAAK,gBAAgB,YAAY,CAAC,UAAU,MAAM,YAAY,IAAI,CAAC;AAElF,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,QAAQ,6BAA6B,UAAU,qBAAqB,IAAI;AAC9E,WAAK,OAAO,KAAK,OAAO;AAAA,QACtB,QAAQ;AAAA,QACR;AAAA,QACA,QAAQ,OAAO;AAAA,MACjB,CAAC;AACD,YAAM,IAAI,MAAM,KAAK;AAAA,IACvB;AAEA,SAAK,OAAO,MAAM,sBAAsB,UAAU,OAAO,IAAI,EAAE;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,iBAAiB,YAAoB,MAAoB;AACvD,UAAM,SAAS,KAAK,gBAAgB,YAAY,CAAC,UAAU,MAAM,aAAa,IAAI,CAAC;AAEnF,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,QAAQ,6BAA6B,UAAU,sBAAsB,IAAI;AAC/E,WAAK,OAAO,KAAK,OAAO;AAAA,QACtB,QAAQ;AAAA,QACR;AAAA,QACA,QAAQ,OAAO;AAAA,MACjB,CAAC;AACD,YAAM,IAAI,MAAM,KAAK;AAAA,IACvB;AAEA,SAAK,OAAO,MAAM,uBAAuB,UAAU,OAAO,IAAI,EAAE;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,sBAAsB,YAAoB,KAAmB;AAC3D,UAAM,SAAS,KAAK,gBAAgB,YAAY,CAAC,UAAU,MAAM,kBAAkB,GAAG,CAAC;AAEvF,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,QAAQ,6BAA6B,UAAU,sBAAsB,GAAG;AAC9E,WAAK,OAAO,KAAK,OAAO;AAAA,QACtB,QAAQ;AAAA,QACR;AAAA,QACA,QAAQ,OAAO;AAAA,MACjB,CAAC;AACD,YAAM,IAAI,MAAM,KAAK;AAAA,IACvB;AAEA,SAAK,OAAO,MAAM,4BAA4B,UAAU,OAAO,GAAG,EAAE;AAAA,EACtE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,sBAAsB,YAAoD;AACxE,WAAO,KAAK,mBAAmB,IAAI,UAAU;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,qBAAqB,YAAmD;AACtE,WAAO,KAAK,mBAAmB,IAAI,UAAU;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAAkB,YAA0B;AAC1C,SAAK,mBAAmB,OAAO,UAAU;AACzC,SAAK,mBAAmB,OAAO,UAAU;AACzC,SAAK,OAAO,KAAK,mCAAmC,UAAU,EAAE;AAAA,EAClE;AAAA;AAAA,EAIQ,gBACN,YACA,OACuB;AACvB,UAAM,cAAc,KAAK,mBAAmB,IAAI,UAAU;AAE1D,QAAI,CAAC,aAAa;AAChB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ;AAAA,MACV;AAAA,IACF;AAEA,UAAM,UAAU,MAAM,WAAW;AAEjC,WAAO;AAAA,MACL;AAAA,MACA,QAAQ,UAAU,SAAY;AAAA,IAChC;AAAA,EACF;AAAA,EAEQ,mBAAmB,cAAkC,aAA8B;AAEzF,WAAO,aAAa,KAAK,SAAO;AAC9B,YAAM,aAAa,IAAI,SAAS;AAGhC,UAAI,WAAW,SAAS,sBAAsB,GAAG;AAC/C,eAAO;AAAA,MACT;AAGA,UAAI,WAAW,SAAS,oBAAoB,WAAW,EAAE,GAAG;AAC1D,eAAO;AAAA,MACT;AAGA,YAAM,kBAAkB,YAAY,MAAM,GAAG,EAAE,CAAC;AAChD,UAAI,WAAW,SAAS,oBAAoB,eAAe,EAAE,GAAG;AAC9D,eAAO;AAAA,MACT;AAEA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEQ,gBAAgB,cAAkC,UAA2B;AAEnF,WAAO,aAAa,KAAK,SAAO;AAC9B,YAAM,aAAa,IAAI,SAAS;AAGhC,UAAI,WAAW,SAAS,mBAAmB,GAAG;AAC5C,eAAO;AAAA,MACT;AAGA,UAAI,WAAW,SAAS,iBAAiB,QAAQ,EAAE,GAAG;AACpD,eAAO;AAAA,MACT;AAGA,YAAM,eAAe,SAAS,MAAM,GAAG,EAAE,CAAC;AAC1C,UAAI,WAAW,SAAS,iBAAiB,YAAY,EAAE,GAAG;AACxD,eAAO;AAAA,MACT;AAEA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEQ,UAAU,SAAiB,KAAsB;AACvD,UAAM,WAAW,QACd,MAAM,IAAI,EACV,IAAI,aAAW;AACd,YAAM,UAAU,QAAQ,QAAQ,sBAAsB,MAAM;AAC5D,aAAO,QAAQ,QAAQ,OAAO,OAAO;AAAA,IACvC,CAAC,EACA,KAAK,IAAI;AACZ,WAAO,IAAI,OAAO,IAAI,QAAQ,GAAG,EAAE,KAAK,GAAG;AAAA,EAC7C;AAAA,EAEQ,cAAc,cAAkC,MAAuB;AAE7E,WAAO,aAAa,KAAK,SAAO;AAC9B,YAAM,aAAa,IAAI,SAAS;AAGhC,UAAI,WAAW,SAAS,0BAA0B,GAAG;AACnD,cAAM,QAAQ,IAAI,UAAU;AAC5B,YAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG;AAC/C,iBAAO;AAAA,QACT;AACA,eAAO,MAAM,KAAK,OAAK,OAAO,MAAM,YAAY,KAAK,UAAU,GAAG,IAAI,CAAC;AAAA,MACzE;AAEA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEQ,eAAe,cAAkC,MAAuB;AAE9E,WAAO,aAAa,KAAK,SAAO;AAC9B,YAAM,aAAa,IAAI,SAAS;AAGhC,UAAI,WAAW,SAAS,2BAA2B,GAAG;AACpD,cAAM,QAAQ,IAAI,UAAU;AAC5B,YAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG;AAC/C,iBAAO;AAAA,QACT;AACA,eAAO,MAAM,KAAK,OAAK,OAAO,MAAM,YAAY,KAAK,UAAU,GAAG,IAAI,CAAC;AAAA,MACzE;AAEA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEQ,mBAAmB,cAAkC,KAAsB;AAEjF,WAAO,aAAa,KAAK,SAAO;AAC9B,YAAM,aAAa,IAAI,SAAS;AAGhC,UAAI,WAAW,SAAS,kBAAkB,GAAG;AAC3C,cAAM,QAAQ,IAAI,UAAU;AAC5B,YAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG;AAC/C,iBAAO;AAAA,QACT;AACA,eAAO,MAAM,KAAK,OAAK,OAAO,MAAM,YAAY,KAAK,UAAU,GAAG,GAAG,CAAC;AAAA,MACxE;AAEA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF;AAMO,IAAM,sBAAN,MAAmD;AAAA,EACxD,YACU,YACA,oBACA,aACR;AAHQ;AACA;AACA;AAAA,EACP;AAAA,EAEH,gBAAgB,MAAc,SAAoB;AAEhD,SAAK,YAAY,gBAAgB,MAAM,OAAO;AAAA,EAChD;AAAA,EAEA,WAAc,MAAiB;AAE7B,SAAK,mBAAmB,qBAAqB,KAAK,YAAY,IAAI;AAClE,WAAO,KAAK,YAAY,WAAc,IAAI;AAAA,EAC5C;AAAA,EAEA,eAAkB,MAAc,gBAAyB;AAEvD,SAAK,mBAAmB,qBAAqB,KAAK,YAAY,IAAI;AAClE,SAAK,YAAY,eAAe,MAAM,cAAc;AAAA,EACtD;AAAA,EAEA,cAAgC;AAE9B,WAAO,KAAK,YAAY,YAAY;AAAA,EACtC;AAAA,EAEA,KAAK,MAAc,SAAyD;AAE1E,SAAK,YAAY,KAAK,MAAM,OAAO;AAAA,EACrC;AAAA,EAEA,MAAM,QAAQ,SAAiB,MAA4B;AAEzD,SAAK,mBAAmB,mBAAmB,KAAK,YAAY,IAAI;AAChE,UAAM,KAAK,YAAY,QAAQ,MAAM,GAAG,IAAI;AAAA,EAC9C;AAAA,EAEA,IAAI,SAAS;AACX,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA,EAEA,YAAY;AACV,WAAO,KAAK,YAAY,UAAU;AAAA,EACpC;AAAA,EAEA,uBAAuB,MAAc,SAAwD,WAA4D,cAA+B;AACtL,SAAK,YAAY,uBAAuB,MAAM,SAAS,WAAW,YAAY;AAAA,EAChF;AAAA,EAEA,iBAAoB,MAAc,SAA6B;AAC7D,WAAO,KAAK,YAAY,iBAAoB,MAAM,OAAO;AAAA,EAC3D;AACF;AAQO,SAAS,+BAA+B,QAA0C;AACvF,SAAO,IAAI,yBAAyB,MAAM;AAC5C;AAMA,SAAS,eAAe,SAAiB,OAAwB;AAC/D,MAAI,YAAY,OAAO,YAAY,KAAM,QAAO;AAChD,QAAM,WAAW,QACd,MAAM,IAAI,EACV,IAAI,CAAC,YAAY,QAAQ,QAAQ,sBAAsB,MAAM,EAAE,QAAQ,OAAO,OAAO,CAAC,EACtF,KAAK,IAAI;AACZ,SAAO,IAAI,OAAO,IAAI,QAAQ,GAAG,EAAE,KAAK,KAAK;AAC/C;AAGA,SAAS,OAAO,KAAqB;AACnC,MAAI;AACF,WAAO,IAAI,IAAI,GAAG,EAAE;AAAA,EACtB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,SAAS,CAAC,MAA4B,UAC1C,MAAM,QAAQ,IAAI,KAAK,KAAK,KAAK,CAAC,MAAM,MAAM,SAAS,eAAe,GAAG,KAAK,CAAC;AAY1E,SAAS,2BACd,SACmB;AACnB,QAAM,WAAW,SAAS;AAC1B,QAAM,QAAQ,SAAS;AACvB,QAAM,UAAU,SAAS;AACzB,QAAM,KAAK,SAAS;AACpB,SAAO;AAAA,IACL,kBAAkB,CAAC,SAAS,OAAO,UAAU,IAAI;AAAA,IACjD,gBAAgB,CAAC,SAAS,OAAO,OAAO,IAAI;AAAA,IAC5C,aAAa,CAAC,SAAS,OAAO,IAAI,IAAI;AAAA,IACtC,cAAc,CAAC,SAAS,OAAO,IAAI,IAAI;AAAA,IACvC,mBAAmB,CAAC,QAClB,OAAO,SAAS,OAAO,GAAG,CAAC,KAAK,OAAO,SAAS,GAAG;AAAA,EACvD;AACF;;;ACheO,IAAM,0BAAN,MAA8B;AAAA,EAYnC,YAAY,QAAsB;AARlC;AAAA,SAAQ,iBAAiB,oBAAI,IAAiC;AAG9D;AAAA,SAAQ,SAAS,oBAAI,IAAyB;AAG9C;AAAA,SAAQ,eAAe,oBAAI,IAA6B;AAGtD,SAAK,SAAS,OAAO,MAAM,EAAE,WAAW,oBAAoB,CAAC;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB,UAAkB,eAA0C;AAC9E,SAAK,eAAe,IAAI,UAAU,aAAa;AAE/C,SAAK,OAAO,KAAK,qCAAqC;AAAA,MACpD;AAAA,MACA,iBAAiB,cAAc,YAAY;AAAA,IAC7C,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,gBACE,UACA,cACA,WACA,WACM;AAEN,UAAM,gBAAgB,KAAK,eAAe,IAAI,QAAQ;AACtD,QAAI,CAAC,eAAe;AAClB,YAAM,IAAI,MAAM,yCAAyC,QAAQ,EAAE;AAAA,IACrE;AAEA,UAAM,aAAa,cAAc,YAAY,KAAK,OAAK,EAAE,OAAO,YAAY;AAC5E,QAAI,CAAC,YAAY;AACf,YAAM,IAAI,MAAM,cAAc,YAAY,2BAA2B,QAAQ,EAAE;AAAA,IACjF;AAGA,QAAI,CAAC,KAAK,OAAO,IAAI,QAAQ,GAAG;AAC9B,WAAK,OAAO,IAAI,UAAU,oBAAI,IAAI,CAAC;AAAA,IACrC;AACA,SAAK,OAAO,IAAI,QAAQ,EAAG,IAAI,YAAY;AAG3C,UAAM,WAAW,GAAG,QAAQ,IAAI,YAAY;AAC5C,SAAK,aAAa,IAAI,UAAU;AAAA,MAC9B;AAAA,MACA;AAAA,MACA,WAAW,oBAAI,KAAK;AAAA,MACpB;AAAA,MACA;AAAA,IACF,CAAC;AAED,SAAK,OAAO,KAAK,sBAAsB;AAAA,MACrC;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,UAAkB,cAA4B;AAC7D,UAAM,SAAS,KAAK,OAAO,IAAI,QAAQ;AACvC,QAAI,QAAQ;AACV,aAAO,OAAO,YAAY;AAE1B,YAAM,WAAW,GAAG,QAAQ,IAAI,YAAY;AAC5C,WAAK,aAAa,OAAO,QAAQ;AAEjC,WAAK,OAAO,KAAK,sBAAsB,EAAE,UAAU,aAAa,CAAC;AAAA,IACnE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB,UAAkB,WAA0B;AAC9D,UAAM,gBAAgB,KAAK,eAAe,IAAI,QAAQ;AACtD,QAAI,CAAC,eAAe;AAClB,YAAM,IAAI,MAAM,yCAAyC,QAAQ,EAAE;AAAA,IACrE;AAEA,eAAW,cAAc,cAAc,aAAa;AAClD,WAAK,gBAAgB,UAAU,WAAW,IAAI,SAAS;AAAA,IACzD;AAEA,SAAK,OAAO,KAAK,2BAA2B,EAAE,UAAU,UAAU,CAAC;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,UAAkB,cAA+B;AAC7D,UAAM,SAAS,KAAK,OAAO,IAAI,QAAQ;AACvC,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,IACT;AAGA,QAAI,CAAC,OAAO,IAAI,YAAY,GAAG;AAC7B,aAAO;AAAA,IACT;AAGA,UAAM,WAAW,GAAG,QAAQ,IAAI,YAAY;AAC5C,UAAM,eAAe,KAAK,aAAa,IAAI,QAAQ;AACnD,QAAI,cAAc,aAAa,aAAa,YAAY,oBAAI,KAAK,GAAG;AAClE,WAAK,iBAAiB,UAAU,YAAY;AAC5C,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,YACE,UACA,UACA,QACA,YACuB;AACvB,UAAM,gBAAgB,KAAK,eAAe,IAAI,QAAQ;AACtD,QAAI,CAAC,eAAe;AAClB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ;AAAA,MACV;AAAA,IACF;AAGA,UAAM,sBAAsB,cAAc,YAAY,OAAO,OAAK;AAEhE,UAAI,EAAE,aAAa,UAAU;AAC3B,eAAO;AAAA,MACT;AAGA,UAAI,CAAC,EAAE,QAAQ,SAAS,MAAM,GAAG;AAC/B,eAAO;AAAA,MACT;AAGA,UAAI,cAAc,EAAE,QAAQ,aAAa;AACvC,YAAI,CAAC,EAAE,OAAO,YAAY,SAAS,UAAU,GAAG;AAC9C,iBAAO;AAAA,QACT;AAAA,MACF;AAEA,aAAO;AAAA,IACT,CAAC;AAED,QAAI,oBAAoB,WAAW,GAAG;AACpC,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,2BAA2B,MAAM,OAAO,QAAQ;AAAA,MAC1D;AAAA,IACF;AAGA,UAAM,qBAAqB,oBAAoB;AAAA,MAAO,OACpD,KAAK,cAAc,UAAU,EAAE,EAAE;AAAA,IACnC;AAEA,QAAI,mBAAmB,WAAW,GAAG;AACnC,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,oBAAoB,oBAAoB,CAAC,EAAE;AAAA,MAC7C;AAAA,IACF;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,oBAAoB,mBAAmB,IAAI,OAAK,EAAE,EAAE;AAAA,IACtD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAqB,UAAsC;AACzD,UAAM,gBAAgB,KAAK,eAAe,IAAI,QAAQ;AACtD,WAAO,eAAe,eAAe,CAAC;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,sBAAsB,UAA4B;AAChD,UAAM,SAAS,KAAK,OAAO,IAAI,QAAQ;AACvC,WAAO,SAAS,MAAM,KAAK,MAAM,IAAI,CAAC;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,sBAAsB,UAAsC;AAC1D,UAAM,gBAAgB,KAAK,eAAe,IAAI,QAAQ;AACtD,QAAI,CAAC,eAAe;AAClB,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,UAAU,KAAK,OAAO,IAAI,QAAQ,KAAK,oBAAI,IAAI;AAErD,WAAO,cAAc,YAAY;AAAA,MAAO,OACtC,EAAE,YAAY,CAAC,QAAQ,IAAI,EAAE,EAAE;AAAA,IACjC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,0BAA0B,UAA2B;AACnD,WAAO,KAAK,sBAAsB,QAAQ,EAAE,WAAW;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,UAAkB,cAAmD;AACnF,UAAM,WAAW,GAAG,QAAQ,IAAI,YAAY;AAC5C,WAAO,KAAK,aAAa,IAAI,QAAQ;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,wBACE,YACA,SAKS;AACT,YAAQ,WAAW,OAAO;AAAA,MACxB,KAAK;AACH,eAAO;AAAA,MAET,KAAK;AACH,eAAO,CAAC,CAAC,QAAQ;AAAA,MAEnB,KAAK;AACH,eAAO,CAAC,CAAC,QAAQ;AAAA,MAEnB,KAAK;AACH,eAAO,CAAC,CAAC,QAAQ;AAAA,MAEnB,KAAK;AACH,eAAO;AAAA,MAET;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,uBAAuB,UAAwB;AAC7C,SAAK,eAAe,OAAO,QAAQ;AAEnC,UAAM,SAAS,KAAK,OAAO,IAAI,QAAQ;AACvC,QAAI,QAAQ;AACV,iBAAW,gBAAgB,QAAQ;AACjC,cAAM,WAAW,GAAG,QAAQ,IAAI,YAAY;AAC5C,aAAK,aAAa,OAAO,QAAQ;AAAA,MACnC;AACA,WAAK,OAAO,OAAO,QAAQ;AAAA,IAC7B;AAEA,SAAK,OAAO,KAAK,2BAA2B,EAAE,SAAS,CAAC;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA,EAKA,WAAiB;AACf,SAAK,eAAe,MAAM;AAC1B,SAAK,OAAO,MAAM;AAClB,SAAK,aAAa,MAAM;AAExB,SAAK,OAAO,KAAK,sCAAsC;AAAA,EACzD;AACF;;;AC/UA,uBAAqB;AA6Cd,IAAM,wBAAN,MAAM,sBAAqB;AAAA,EAehC,YAAY,QAAsB;AATlC;AAAA,SAAQ,YAAY,oBAAI,IAA4B;AAGpD;AAAA,SAAQ,sBAAsB,oBAAI,IAA4B;AAG9D;AAAA,SAAQ,kBAAkB,oBAAI,IAAoB;AAClD,SAAQ,eAAe,oBAAI,IAA8C;AAGvE,SAAK,SAAS,OAAO,MAAM,EAAE,WAAW,iBAAiB,CAAC;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,UAAkB,QAAuC;AACrE,QAAI,KAAK,UAAU,IAAI,QAAQ,GAAG;AAChC,YAAM,IAAI,MAAM,sCAAsC,QAAQ,EAAE;AAAA,IAClE;AAEA,UAAM,UAA0B;AAAA,MAC9B;AAAA,MACA;AAAA,MACA,WAAW,oBAAI,KAAK;AAAA,MACpB,eAAe;AAAA,QACb,QAAQ,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,OAAO,QAAQ,QAAQ;AAAA,QAC7D,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,OAAO,KAAK,cAAc;AAAA,QAChE,aAAa,EAAE,SAAS,GAAG,OAAO,OAAO,SAAS,eAAe;AAAA,MACnE;AAAA,IACF;AAEA,SAAK,UAAU,IAAI,UAAU,OAAO;AAGpC,UAAM,iBAAiB,eAAe;AACtC,SAAK,gBAAgB,IAAI,UAAU,eAAe,QAAQ;AAC1D,SAAK,aAAa,IAAI,UAAU,QAAQ,SAAS,CAAC;AAGlD,SAAK,wBAAwB,QAAQ;AAErC,SAAK,OAAO,KAAK,mBAAmB;AAAA,MAClC;AAAA,MACA,OAAO,OAAO;AAAA,MACd,aAAa,OAAO,QAAQ;AAAA,MAC5B,UAAU,OAAO,KAAK;AAAA,IACxB,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,UAAwB;AACrC,UAAM,UAAU,KAAK,UAAU,IAAI,QAAQ;AAC3C,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AAGA,SAAK,uBAAuB,QAAQ;AAEpC,SAAK,gBAAgB,OAAO,QAAQ;AACpC,SAAK,aAAa,OAAO,QAAQ;AACjC,SAAK,UAAU,OAAO,QAAQ;AAE9B,SAAK,OAAO,KAAK,qBAAqB,EAAE,SAAS,CAAC;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKA,oBACE,UACA,cACA,cACuC;AACvC,UAAM,UAAU,KAAK,UAAU,IAAI,QAAQ;AAC3C,QAAI,CAAC,SAAS;AACZ,aAAO,EAAE,SAAS,OAAO,QAAQ,oBAAoB;AAAA,IACvD;AAEA,UAAM,EAAE,OAAO,IAAI;AAEnB,YAAQ,cAAc;AAAA,MACpB,KAAK;AACH,eAAO,KAAK,gBAAgB,QAAQ,YAAY;AAAA,MAElD,KAAK;AACH,eAAO,KAAK,mBAAmB,QAAQ,YAAY;AAAA,MAErD,KAAK;AACH,eAAO,KAAK,mBAAmB,MAAM;AAAA,MAEvC,KAAK;AACH,eAAO,KAAK,eAAe,QAAQ,YAAY;AAAA,MAEjD;AACE,eAAO,EAAE,SAAS,OAAO,QAAQ,wBAAwB;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,gBACN,QACA,UACuC;AACvC,QAAI,OAAO,UAAU,QAAQ;AAC3B,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB;AAEA,QAAI,CAAC,OAAO,YAAY;AACtB,aAAO,EAAE,SAAS,OAAO,QAAQ,oCAAoC;AAAA,IACvE;AAGA,QAAI,CAAC,UAAU;AACb,aAAO,EAAE,SAAS,OAAO,WAAW,SAAS,OAAO;AAAA,IACtD;AAGA,UAAM,eAAe,OAAO,WAAW,gBAAgB,CAAC;AACxD,UAAM,eAAe,iBAAAC,QAAS,UAAU,iBAAAA,QAAS,QAAQ,QAAQ,CAAC;AAClE,UAAM,YAAY,aAAa,KAAK,aAAW;AAC7C,YAAM,kBAAkB,iBAAAA,QAAS,UAAU,iBAAAA,QAAS,QAAQ,OAAO,CAAC;AACpE,aAAO,aAAa,WAAW,eAAe;AAAA,IAChD,CAAC;AAED,QAAI,aAAa,SAAS,KAAK,CAAC,WAAW;AACzC,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,6BAA6B,QAAQ;AAAA,MAC/C;AAAA,IACF;AAGA,UAAM,cAAc,OAAO,WAAW,eAAe,CAAC;AACtD,UAAM,WAAW,YAAY,KAAK,YAAU;AAC1C,YAAM,iBAAiB,iBAAAA,QAAS,UAAU,iBAAAA,QAAS,QAAQ,MAAM,CAAC;AAClE,aAAO,aAAa,WAAW,cAAc;AAAA,IAC/C,CAAC;AAED,QAAI,UAAU;AACZ,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,8BAA8B,QAAQ;AAAA,MAChD;AAAA,IACF;AAEA,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,mBACN,QACA,KACuC;AACvC,QAAI,OAAO,UAAU,QAAQ;AAC3B,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB;AAEA,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO,EAAE,SAAS,OAAO,QAAQ,gCAAgC;AAAA,IACnE;AAGA,QAAI,OAAO,QAAQ,SAAS,QAAQ;AAClC,aAAO,EAAE,SAAS,OAAO,QAAQ,0BAA0B;AAAA,IAC7D;AAGA,QAAI,CAAC,KAAK;AACR,aAAO,EAAE,SAAU,OAAO,QAAQ,SAAoB,OAAO;AAAA,IAC/D;AAGA,QAAI;AACJ,QAAI;AACF,uBAAiB,IAAI,IAAI,GAAG,EAAE;AAAA,IAChC,QAAQ;AACN,aAAO,EAAE,SAAS,OAAO,QAAQ,gBAAgB,GAAG,GAAG;AAAA,IACzD;AAGA,UAAM,eAAe,OAAO,QAAQ,gBAAgB,CAAC;AACrD,QAAI,aAAa,SAAS,GAAG;AAC3B,YAAM,YAAY,aAAa,KAAK,UAAQ;AAC1C,eAAO,mBAAmB;AAAA,MAC5B,CAAC;AAED,UAAI,CAAC,WAAW;AACd,eAAO;AAAA,UACL,SAAS;AAAA,UACT,QAAQ,6BAA6B,GAAG;AAAA,QAC1C;AAAA,MACF;AAAA,IACF;AAGA,UAAM,cAAc,OAAO,QAAQ,eAAe,CAAC;AACnD,UAAM,WAAW,YAAY,KAAK,UAAQ;AACxC,aAAO,mBAAmB;AAAA,IAC5B,CAAC;AAED,QAAI,UAAU;AACZ,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,oBAAoB,GAAG;AAAA,MACjC;AAAA,IACF;AAEA,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKQ,mBACN,QACuC;AACvC,QAAI,OAAO,UAAU,QAAQ;AAC3B,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB;AAEA,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO,EAAE,SAAS,OAAO,QAAQ,gCAAgC;AAAA,IACnE;AAEA,QAAI,CAAC,OAAO,QAAQ,YAAY;AAC9B,aAAO,EAAE,SAAS,OAAO,QAAQ,+BAA+B;AAAA,IAClE;AAEA,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKQ,eACN,QACA,SACuC;AACvC,QAAI,OAAO,UAAU,QAAQ;AAC3B,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB;AAEA,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO,EAAE,SAAS,OAAO,QAAQ,oCAAoC;AAAA,IACvE;AAGA,QAAI,CAAC,SAAS;AACZ,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB;AAIA,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB,UAGlB;AACA,UAAM,UAAU,KAAK,UAAU,IAAI,QAAQ;AAC3C,QAAI,CAAC,SAAS;AACZ,aAAO,EAAE,cAAc,MAAM,YAAY,CAAC,EAAE;AAAA,IAC9C;AAEA,UAAM,aAAuB,CAAC;AAC9B,UAAM,EAAE,eAAe,OAAO,IAAI;AAGlC,QAAI,OAAO,QAAQ,WACf,cAAc,OAAO,UAAU,OAAO,OAAO,SAAS;AACxD,iBAAW,KAAK,0BAA0B,cAAc,OAAO,OAAO,MAAM,OAAO,OAAO,OAAO,EAAE;AAAA,IACrG;AAGA,QAAI,OAAO,SAAS,gBAAgB,UAChC,cAAc,IAAI,UAAU,OAAO,QAAQ,eAAe,QAAQ;AACpE,iBAAW,KAAK,uBAAuB,cAAc,IAAI,OAAO,OAAO,OAAO,QAAQ,eAAe,MAAM,GAAG;AAAA,IAChH;AAGA,QAAI,OAAO,SAAS,kBAChB,cAAc,YAAY,UAAU,OAAO,QAAQ,gBAAgB;AACrE,iBAAW,KAAK,8BAA8B,cAAc,YAAY,OAAO,MAAM,OAAO,QAAQ,cAAc,EAAE;AAAA,IACtH;AAEA,WAAO;AAAA,MACL,cAAc,WAAW,WAAW;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,UAA6C;AAC5D,UAAM,UAAU,KAAK,UAAU,IAAI,QAAQ;AAC3C,WAAO,SAAS;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKQ,wBAAwB,UAAwB;AAEtD,UAAM,WAAW,YAAY,MAAM;AACjC,WAAK,oBAAoB,QAAQ;AAAA,IACnC,GAAG,sBAAqB,sBAAsB;AAE9C,SAAK,oBAAoB,IAAI,UAAU,QAAQ;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA,EAKQ,uBAAuB,UAAwB;AACrD,UAAM,WAAW,KAAK,oBAAoB,IAAI,QAAQ;AACtD,QAAI,UAAU;AACZ,oBAAc,QAAQ;AACtB,WAAK,oBAAoB,OAAO,QAAQ;AAAA,IAC1C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,oBAAoB,UAAwB;AAClD,UAAM,UAAU,KAAK,UAAU,IAAI,QAAQ;AAC3C,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AAMA,UAAM,cAAc,eAAe;AACnC,UAAM,iBAAiB,KAAK,gBAAgB,IAAI,QAAQ,KAAK;AAC7D,UAAM,cAAc,KAAK,IAAI,GAAG,YAAY,WAAW,cAAc;AACrE,YAAQ,cAAc,OAAO,UAAU;AACvC,YAAQ,cAAc,OAAO,OAAO,KAAK;AAAA,MACvC,QAAQ,cAAc,OAAO;AAAA,MAC7B;AAAA,IACF;AAGA,UAAM,cAAc,KAAK,aAAa,IAAI,QAAQ,KAAK,EAAE,MAAM,GAAG,QAAQ,EAAE;AAC5E,UAAM,aAAa,QAAQ,SAAS;AACpC,UAAM,eAAe,WAAW,OAAO,YAAY;AACnD,UAAM,iBAAiB,WAAW,SAAS,YAAY;AAEvD,UAAM,iBAAiB,eAAe;AACtC,UAAM,iBAAiB,sBAAqB,yBAAyB;AACrE,YAAQ,cAAc,IAAI,UAAW,iBAAiB,iBAAkB;AAExE,SAAK,aAAa,IAAI,UAAU,UAAU;AAG1C,UAAM,EAAE,cAAc,WAAW,IAAI,KAAK,oBAAoB,QAAQ;AACtE,QAAI,CAAC,cAAc;AACjB,WAAK,OAAO,KAAK,sCAAsC;AAAA,QACrD;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,kBAA+C;AAC7C,WAAO,IAAI,IAAI,KAAK,SAAS;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,WAAiB;AAEf,eAAW,YAAY,KAAK,oBAAoB,KAAK,GAAG;AACtD,WAAK,uBAAuB,QAAQ;AAAA,IACtC;AAEA,SAAK,UAAU,MAAM;AACrB,SAAK,gBAAgB,MAAM;AAC3B,SAAK,aAAa,MAAM;AAExB,SAAK,OAAO,KAAK,mCAAmC;AAAA,EACtD;AACF;AA9Za,sBACa,yBAAyB;AAD5C,IAAM,uBAAN;;;ACLA,IAAM,wBAAN,MAA4B;AAAA,EAWjC,YAAY,QAAsB,QAAqC;AAPvE;AAAA,SAAQ,kBAAkB,oBAAI,IAAyC;AAGvE;AAAA,SAAQ,cAAc,oBAAI,IAAsC;AAEhE,SAAQ,gBAAwB;AAG9B,SAAK,SAAS,OAAO,MAAM,EAAE,WAAW,kBAAkB,CAAC;AAC3D,QAAI,QAAQ,kBAAkB,QAAW;AACvC,WAAK,gBAAgB,OAAO;AAAA,IAC9B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAK,QAAuD;AAChE,SAAK,OAAO,KAAK,0BAA0B;AAAA,MACzC,UAAU,OAAO;AAAA,MACjB,SAAS,OAAO;AAAA,IAClB,CAAC;AAED,UAAM,SAA0B,CAAC;AAEjC,QAAI;AAEF,YAAM,aAAa,MAAM,KAAK,SAAS,MAAM;AAC7C,aAAO,KAAK,GAAG,UAAU;AAGzB,YAAM,YAAY,MAAM,KAAK,iBAAiB,MAAM;AACpD,aAAO,KAAK,GAAG,SAAS;AAGxB,YAAM,gBAAgB,MAAM,KAAK,YAAY,MAAM;AACnD,aAAO,KAAK,GAAG,aAAa;AAG5B,YAAM,gBAAgB,MAAM,KAAK,aAAa,MAAM;AACpD,aAAO,KAAK,GAAG,aAAa;AAG5B,YAAM,eAAe,MAAM,KAAK,kBAAkB,MAAM;AACxD,aAAO,KAAK,GAAG,YAAY;AAG3B,YAAM,QAAQ,KAAK,uBAAuB,MAAM;AAEhD,YAAM,SAAmC;AAAA,QACvC,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QAClC,SAAS,EAAE,MAAM,gCAAgC,SAAS,QAAQ;AAAA,QAClE,QAAQ,SAAS,KAAK,gBAAgB,WAAW;AAAA,QACjD,iBAAiB,OAAO,IAAI,YAAU;AAAA,UACpC,IAAI,MAAM;AAAA,UACV,UAAU,MAAM;AAAA,UAChB,UAAU,MAAM;AAAA,UAChB,OAAO,MAAM;AAAA,UACb,aAAa,MAAM;AAAA,UACnB,UAAU,MAAM,WAAW,GAAG,MAAM,SAAS,IAAI,IAAI,MAAM,SAAS,IAAI,KAAK;AAAA,UAC7E,aAAa,MAAM;AAAA,UACnB,kBAAkB,CAAC;AAAA,UACnB,kBAAkB;AAAA,UAClB,gBAAgB;AAAA,QAClB,EAAE;AAAA,QACF,SAAS;AAAA,UACP,sBAAsB,OAAO;AAAA,UAC7B,eAAe,OAAO,OAAO,OAAK,EAAE,aAAa,UAAU,EAAE;AAAA,UAC7D,WAAW,OAAO,OAAO,OAAK,EAAE,aAAa,MAAM,EAAE;AAAA,UACrD,aAAa,OAAO,OAAO,OAAK,EAAE,aAAa,QAAQ,EAAE;AAAA,UACzD,UAAU,OAAO,OAAO,OAAK,EAAE,aAAa,KAAK,EAAE;AAAA,UACnD,WAAW,OAAO,OAAO,OAAK,EAAE,aAAa,MAAM,EAAE;AAAA,QACvD;AAAA,MACF;AAEA,WAAK,YAAY,IAAI,GAAG,OAAO,QAAQ,IAAI,OAAO,OAAO,IAAI,MAAM;AAEnE,WAAK,OAAO,KAAK,0BAA0B;AAAA,QACzC,UAAU,OAAO;AAAA,QACjB;AAAA,QACA,QAAQ,OAAO;AAAA,QACf,SAAS,OAAO;AAAA,MAClB,CAAC;AAED,aAAO;AAAA,IACT,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,wBAAwB;AAAA,QACxC,UAAU,OAAO;AAAA,QACjB;AAAA,MACF,CAAC;AAED,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,SAAS,QAA8C;AACnE,UAAM,SAA0B,CAAC;AAUjC,SAAK,OAAO,MAAM,sBAAsB;AAAA,MACtC,UAAU,OAAO;AAAA,MACjB,aAAa,OAAO;AAAA,IACtB,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,iBAAiB,QAA8C;AAC3E,UAAM,SAA0B,CAAC;AAEjC,QAAI,CAAC,OAAO,cAAc;AACxB,aAAO;AAAA,IACT;AAQA,eAAW,CAAC,SAAS,OAAO,KAAK,OAAO,QAAQ,OAAO,YAAY,GAAG;AACpE,YAAM,UAAU,GAAG,OAAO,IAAI,OAAO;AACrC,YAAM,gBAAgB,KAAK,gBAAgB,IAAI,OAAO;AAEtD,UAAI,eAAe;AACjB,eAAO,KAAK;AAAA,UACV,IAAI,QAAQ,cAAc,OAAO,OAAO;AAAA,UACxC,UAAU,cAAc;AAAA,UACxB,UAAU;AAAA,UACV,OAAO,0BAA0B,OAAO;AAAA,UACxC,aAAa,GAAG,OAAO,IAAI,OAAO;AAAA,UAClC,aAAa,cAAc,UACvB,cAAc,cAAc,QAAQ,KAAK,MAAM,CAAC,KAChD;AAAA,UACJ,KAAK,cAAc;AAAA,QACrB,CAAC;AAAA,MACH;AAAA,IACF;AAEA,SAAK,OAAO,MAAM,4BAA4B;AAAA,MAC5C,UAAU,OAAO;AAAA,MACjB,cAAc,OAAO,KAAK,OAAO,YAAY,EAAE;AAAA,MAC/C,iBAAiB,OAAO;AAAA,IAC1B,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,YAAY,QAA8C;AACtE,UAAM,SAA0B,CAAC;AAUjC,SAAK,OAAO,MAAM,yBAAyB;AAAA,MACzC,UAAU,OAAO;AAAA,MACjB,aAAa,OAAO;AAAA,IACtB,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,aAAa,QAA8C;AACvE,UAAM,SAA0B,CAAC;AAEjC,QAAI,CAAC,OAAO,cAAc;AACxB,aAAO;AAAA,IACT;AASA,SAAK,OAAO,MAAM,yBAAyB;AAAA,MACzC,UAAU,OAAO;AAAA,MACjB,aAAa,OAAO;AAAA,IACtB,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,kBAAkB,QAA8C;AAC5E,UAAM,SAA0B,CAAC;AASjC,SAAK,OAAO,MAAM,+BAA+B;AAAA,MAC/C,UAAU,OAAO;AAAA,MACjB,aAAa,OAAO;AAAA,IACtB,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,uBAAuB,QAAiC;AAE9D,QAAI,QAAQ;AAGZ,eAAW,SAAS,QAAQ;AAC1B,cAAQ,MAAM,UAAU;AAAA,QACtB,KAAK;AACH,mBAAS;AACT;AAAA,QACF,KAAK;AACH,mBAAS;AACT;AAAA,QACF,KAAK;AACH,mBAAS;AACT;AAAA,QACF,KAAK;AACH,mBAAS;AACT;AAAA,QACF,KAAK;AACH,mBAAS;AACT;AAAA,MACJ;AAAA,IACF;AAGA,WAAO,KAAK,IAAI,GAAG,KAAK;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,iBACE,aACA,SACA,eACM;AACN,UAAM,MAAM,GAAG,WAAW,IAAI,OAAO;AACrC,SAAK,gBAAgB,IAAI,KAAK,aAAa;AAE3C,SAAK,OAAO,MAAM,mCAAmC;AAAA,MACnD,SAAS;AAAA,MACT;AAAA,MACA,KAAK,cAAc;AAAA,IACrB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,UAAkB,SAAuD;AACrF,WAAO,KAAK,YAAY,IAAI,GAAG,QAAQ,IAAI,OAAO,EAAE;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAKA,aAAmB;AACjB,SAAK,YAAY,MAAM;AACvB,SAAK,OAAO,MAAM,4BAA4B;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,8BAA6C;AACjD,SAAK,OAAO,KAAK,iCAAiC;AAQlD,SAAK,OAAO,KAAK,kCAAkC;AAAA,MACjD,SAAS,KAAK,gBAAgB;AAAA,IAChC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,WAAiB;AACf,SAAK,gBAAgB,MAAM;AAC3B,SAAK,YAAY,MAAM;AAEvB,SAAK,OAAO,KAAK,oCAAoC;AAAA,EACvD;AACF;;;ACvVA,IAAAC,sBAAwC;AAExC,sBAAoF;AAI7E,IAAM,iBAAiB;AAG9B,IAAM,wBAAwB;AAG9B,IAAM,qBAAqB;AAQpB,SAAS,WAAW,KAAqB;AAC9C,aAAO,gCAAW,QAAQ,EAAE,OAAO,KAAK,MAAM,EAAE,OAAO,KAAK;AAC9D;AAiBO,SAAS,eAAe,SAAiB,gBAAiC;AAE/E,QAAM,aAAS,iCAAY,qBAAqB,EAAE,SAAS,WAAW;AACtE,QAAM,MAAM,GAAG,MAAM,GAAG,MAAM;AAC9B,SAAO;AAAA,IACL;AAAA,IACA,MAAM,WAAW,GAAG;AAAA,IACpB,QAAQ,IAAI,MAAM,GAAG,kBAAkB;AAAA,EACzC;AACF;AAaO,SAAS,cAAc,SAAkC;AAC9D,QAAM,IAAI,WAAW,SAAS,WAAW;AACzC,MAAI,KAAK,EAAE,KAAK,EAAG,QAAO,EAAE,KAAK;AACjC,QAAM,OAAO,WAAW,SAAS,eAAe;AAChD,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,eAAe,KAAK,MAAM,oBAAoB;AACpD,MAAI,eAAe,CAAC,GAAG,KAAK,EAAG,QAAO,aAAa,CAAC,EAAE,KAAK;AAE3D,QAAM,SAAS,KAAK,MAAM,oBAAoB,IAAI,CAAC,GAAG,KAAK;AAC3D,MAAI,UAAU,OAAO,WAAW,cAAc,EAAG,QAAO;AACxD,SAAO;AACT;AAGO,SAAS,YAAY,OAA0B;AACpD,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,OAAO,CAAC,MAAmB,OAAO,MAAM,YAAY,EAAE,SAAS,CAAC;AAAA,EAC/E;AACA,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,GAAG;AAC7C,UAAM,SAAS,cAAuB,OAAO,CAAC,CAAC;AAC/C,QAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,aAAO,OAAO,OAAO,CAAC,MAAmB,OAAO,MAAM,YAAY,EAAE,SAAS,CAAC;AAAA,IAChF;AAAA,EACF;AACA,SAAO,CAAC;AACV;AAGO,SAAS,UAAU,OAAgB,OAAwB;AAChE,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI;AACJ,MAAI,OAAO,UAAU,UAAU;AAE7B,SAAK,QAAQ,OAAO,QAAQ,MAAO;AAAA,EACrC,WAAW,iBAAiB,MAAM;AAChC,SAAK,MAAM,QAAQ;AAAA,EACrB,WAAW,OAAO,UAAU,UAAU;AACpC,SAAK,KAAK,MAAM,KAAK;AAAA,EACvB,OAAO;AACL,WAAO;AAAA,EACT;AACA,MAAI,OAAO,MAAM,EAAE,EAAG,QAAO;AAC7B,SAAO,MAAM;AACf;AAsEO,SAAS,wBACd,SAC4B;AAC5B,MAAI,CAAC,QAAS,QAAO;AACrB,aAAO,yCAAwB,QAAQ,OAAO,MAAM,QAAQ,kBAAkB,aAAa;AAC7F;AAaA,eAAsB,uBACpB,IACA,SACA,QAAgB,KAAK,IAAI,GACzB,gBACsC;AACtC,QAAM,YAAY,MAAM,uBAAuB,IAAI,SAAS,OAAO,cAAc;AACjF,SAAO,UAAU,YAAY,aAAa,UAAU,YAAY;AAClE;AAeA,eAAsB,uBACpB,IACA,SACA,QAAgB,KAAK,IAAI,GACzB,gBAC0B;AAC1B,QAAM,SAAS,cAAc,OAAO;AACpC,MAAI,CAAC,OAAQ,QAAO,EAAE,SAAS,OAAO;AACtC,MAAI,CAAC,MAAM,OAAO,GAAG,SAAS,WAAY,QAAO,EAAE,SAAS,OAAO;AAGnE,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,GAAG,KAAK,eAAe;AAAA,MAClC,OAAO,EAAE,KAAK,WAAW,MAAM,GAAG,SAAS,MAAM;AAAA,MACjD,OAAO;AAAA,MACP,SAAS,EAAE,UAAU,KAAK;AAAA,IAC5B,CAAC;AAAA,EACH,QAAQ;AACN,WAAO,EAAE,SAAS,OAAO;AAAA,EAC3B;AACA,MAAI,QAAS,KAAa,MAAO,QAAQ,KAAa;AACtD,QAAM,MAAM,MAAM,QAAQ,IAAI,IAAI,KAAK,CAAC,IAAI;AAC5C,MAAI,CAAC,OAAO,IAAI,YAAY,KAAM,QAAO,EAAE,SAAS,OAAO;AAE3D,QAAM,YAAY,IAAI,cAAc,IAAI;AACxC,MAAI,UAAU,WAAW,KAAK,EAAG,QAAO,EAAE,SAAS,OAAO;AAE1D,QAAM,SAAS,IAAI,WAAW,IAAI;AAClC,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO,EAAE,SAAS,OAAO;AASpE,QAAM,WAAW,OAAO,IAAI,2BAA2B,YAAY,IAAI,yBACnE,IAAI,yBACJ;AA0BJ,MAAI,CAAC,YAAY,gBAAgB;AAC/B,UAAM,UAAU;AAChB,YAAI,qCAAoB,OAAO,KAAK,KAAC,uCAAsB,OAAO,GAAG;AACnE,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,SACE;AAAA,MAGJ;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,SAAS;AAAA,IACT,WAAW,EAAE,QAAQ,UAAU,QAAQ,YAAY,IAAI,MAAM,EAAE;AAAA,EACjE;AACF;AAEA,SAAS,WAAW,SAAc,MAAkC;AAClE,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,QAAQ,KAAK,YAAY;AAC/B,MAAI,OAAO,QAAQ,QAAQ,YAAY;AACrC,UAAM,IAAI,QAAQ,IAAI,IAAI,KAAK,QAAQ,IAAI,KAAK;AAChD,WAAO,KAAK,OAAO,SAAY,OAAO,CAAC;AAAA,EACzC;AACA,aAAW,OAAO,OAAO,KAAK,OAAO,GAAG;AACtC,QAAI,IAAI,YAAY,MAAM,OAAO;AAC/B,YAAM,IAAI,QAAQ,GAAG;AACrB,aAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,KAAK,OAAO,SAAY,OAAO,CAAC;AAAA,IACnE;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,cAAiB,GAAW,UAAgB;AACnD,MAAI;AACF,WAAO,KAAK,MAAM,CAAC;AAAA,EACrB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AC/TA,kBAKO;AAEP,IAAAC,mBAAoC;;;ACNpC,SAAS,UAAU,OAAoC;AACrD,MAAI,SAAS,QAAQ,UAAU,GAAI,QAAO;AAC1C,MAAI,OAAO,UAAU,UAAU;AAE7B,WAAO,QAAQ,OAAO,QAAQ,MAAO;AAAA,EACvC;AACA,MAAI,iBAAiB,KAAM,QAAO,MAAM,QAAQ;AAChD,MAAI,OAAO,UAAU,SAAU,QAAO,KAAK,MAAM,KAAK;AACtD,SAAO,OAAO;AAChB;AAaO,SAAS,cAAc,KAA6C,OAAwB;AACjG,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,OAAO,UAAW,IAAY,cAAe,IAAY,SAAS;AAExE,MAAI,SAAS,UAAa,EAAE,SAAS,MAAO,QAAO;AACnD,QAAM,QAAQ,UAAW,IAAY,eAAgB,IAAY,UAAU;AAC3E,MAAI,UAAU,UAAa,EAAE,QAAQ,OAAQ,QAAO;AACpD,SAAO;AACT;AAQO,SAAS,eAAe,KAA6C,OAAwB;AAClG,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,QAAQ,UAAW,IAAY,eAAgB,IAAY,UAAU;AAC3E,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,EAAE,QAAQ;AACnB;;;AC5BO,IAAM,iBAAiB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,eAA6C;AAAA,EACxD,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,QAAQ;AAAA,EACR,UAAU;AACZ;AAQO,IAAM,yBAAuD;AAAA,EAClE,gBACE;AAAA,EACF,cACE;AAAA,EACF,QACE;AAAA,EACF,UACE;AACJ;AA6BO,SAAS,cAAc,UAAyC;AACrE,MAAI,SAAS,gBAAiB,QAAO;AACrC,MAAI,SAAS,cAAe,QAAO;AACnC,SAAO;AACT;AA2BA,SAAS,WAAW,KAAgB,QAAyB;AAC3D,UAAQ,IAAI,YAAY,CAAC,GAAG,SAAS,MAAM;AAC7C;AAGA,SAAS,gBAAgB,MAA4B,GAAiC;AACpF,SAAO,KAAK,OAAO,CAAC,MAAM,WAAW,GAAG,EAAE,MAAM,CAAC;AACnD;AAOA,SAAS,cAAc,MAA4B,GAAiC;AAClF,QAAM,SAAS,IAAI,IAAI,gBAAgB,MAAM,CAAC,CAAC;AAC/C,SAAO,KAAK;AAAA,IACV,CAAC,MACC,OAAO,IAAI,CAAC,KACX,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe;AAAA,EAC5F;AACF;AAMA,SAAS,mBAAmB,MAA4B,GAAiC;AACvF,QAAM,SAAS,IAAI,IAAI,cAAc,MAAM,CAAC,CAAC;AAC7C,SAAO,KAAK,OAAO,CAAC,MAAM,OAAO,IAAI,CAAC,KAAK,EAAE,oBAAoB,EAAE,cAAc;AACnF;AAGA,SAAS,qBAAqB,MAAyC;AACrE,SAAO,CAAC,GAAG,IAAI;AACjB;AAQO,SAAS,mBACd,SACA,MACA,WACa;AACb,UAAQ,SAAS;AAAA,IACf,KAAK;AACH,aAAO,qBAAqB,IAAI;AAAA,IAClC,KAAK;AACH,aAAO,mBAAmB,MAAM,SAAS;AAAA,IAC3C,KAAK;AACH,aAAO,cAAc,MAAM,SAAS;AAAA,IACtC,KAAK;AACH,aAAO,gBAAgB,MAAM,SAAS;AAAA,EAC1C;AACF;;;AC/IA,IAAM,qBAAyC,CAAC,OAAO,GAAG,KAAK,OAAO;AAe/D,SAAS,YAAY,KAAiD;AAC3E,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,QAAS,IAA6B;AAC5C,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,SAAO,CAAC,mBAAmB,SAAS,KAAK;AAC3C;;;AHgDA,SAASC,eAAiB,GAAW,UAAgB;AACnD,MAAI;AAAE,WAAO,KAAK,MAAM,CAAC;AAAA,EAAQ,QAAQ;AAAE,WAAO;AAAA,EAAU;AAC9D;AAEA,eAAe,QACb,IACA,QACA,OACA,QAAQ,KASR,gBACgB;AAChB,MAAI,CAAC,MAAM,OAAO,GAAG,SAAS,WAAY,QAAO,CAAC;AAClD,MAAI;AACF,UAAM,UAAU,iBAAiB,EAAE,UAAU,MAAM,UAAU,eAAe,IAAI,EAAE,UAAU,KAAK;AACjG,QAAI,OAAO,MAAM,GAAG,KAAK,QAAQ,EAAE,OAAO,OAAO,QAAQ,CAAQ;AACjE,QAAI,QAAS,KAAa,MAAO,QAAQ,KAAa;AACtD,WAAO,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC;AAAA,EACvC,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAMA,eAAsB,oBAAoB,OAAyD;AACjG,QAAM,EAAE,IAAI,QAAQ,IAAI;AACxB,QAAM,MAA4B;AAAA,IAChC,WAAW,CAAC;AAAA,IACZ,aAAa,CAAC;AAAA,IACd,mBAAmB,CAAC;AAAA,IACpB,cAAc,CAAC;AAAA,IACf,oBAAoB,CAAC;AAAA,EACvB;AAEA,MAAI;AACJ,MAAI;AAGJ,QAAM,YAAY,MAAM,uBAAuB,IAAI,SAAS,MAAM,OAAO,MAAM,cAAc;AAK7F,MAAI,UAAU,YAAY,WAAW;AACnC,QAAI,cAAc,EAAE,QAAQ,UAAU,QAAQ,SAAS,UAAU,QAAQ;AACzE,WAAO;AAAA,EACT;AACA,QAAM,eAAe,UAAU,YAAY,aAAa,UAAU,YAAY;AAC9E,MAAI,cAAc;AAChB,aAAS,aAAa;AACtB,eAAW,aAAa;AACxB,eAAW,SAAS,aAAa,QAAQ;AACvC,UAAI,CAAC,IAAI,YAAY,SAAS,KAAK,EAAG,KAAI,YAAY,KAAK,KAAK;AAAA,IAClE;AAAA,EACF;AAGA,MAAI,CAAC,UAAU,OAAO,MAAM,eAAe,YAAY;AACrD,QAAI;AACF,YAAM,cAAc,MAAM,MAAM,WAAW,OAAO;AAClD,eAAS,aAAa,MAAM,MAAM,aAAa,SAAS;AACxD,iBAAW,YAAY,aAAa,SAAS;AAC7C,UAAI,cAAc,aAAa,SAAS,SAAS,IAAI;AACrD,UAAI,aAAa,MAAM,MAAO,KAAI,QAAQ,OAAO,YAAY,KAAK,KAAK;AAAA,IACzE,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,SAAS;AACb,MAAI,SAAU,KAAI,WAAW;AAC7B,MAAI,CAAC,MAAM,OAAO,GAAG,SAAS,WAAY,QAAO;AAUjD,QAAM,SAAS,MAAM,uBAAuB,IAAI,QAAQ;AAAA,IACtD;AAAA,IACA,OAAO,MAAM;AAAA,IACb,iBAAiB,IAAI;AAAA,IACrB,WAAW,IAAI;AAAA,EACjB,CAAC;AAqBD,MAAI,cAAc,YAAY,MAAM,gBAAgB;AAClD,UAAM,UAAU,MAAM;AACtB,YAAI,sCAAoB,OAAO,KAAK,CAAC,OAAO,mBAAmB,SAAS,aAAa,QAAQ,GAAG;AAC9F,aAAO;AAAA,QACL,WAAW,CAAC;AAAA,QACZ,aAAa,CAAC;AAAA,QACd,mBAAmB,CAAC;AAAA,QACpB,cAAc,CAAC;AAAA,QACf,oBAAoB,CAAC;AAAA,QACrB,aAAa;AAAA,UACX,QAAQ;AAAA,UACR,SACE;AAAA,QAEJ;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,YAAY,OAAO;AACvB,MAAI,cAAc,OAAO;AACzB,MAAI,oBAAoB,OAAO;AAC/B,MAAI,eAAe,OAAO;AAC1B,MAAI,qBAAqB,OAAO;AAChC,MAAI,OAAO,eAAgB,KAAI,iBAAiB,OAAO;AACvD,MAAI,OAAO,QAAS,KAAI,UAAU,OAAO;AACzC,MAAI,OAAO,SAAS,CAAC,IAAI,MAAO,KAAI,QAAQ,OAAO;AAEnD,SAAO;AACT;AAuDA,eAAsB,uBACpB,IACA,QACA,OAAsC,CAAC,GACb;AAC1B,QAAM,EAAE,SAAS,IAAI;AACrB,QAAM,SAA0B;AAAA,IAC9B,WAAW,CAAC;AAAA,IACZ,aAAa,MAAM,QAAQ,KAAK,eAAe,IAAI,CAAC,GAAG,KAAK,eAAe,IAAI,CAAC;AAAA,IAChF,mBAAmB,CAAC;AAAA,IACpB,cAAc,CAAC,MAAM;AAAA,IACrB,oBAAoB,CAAC;AAAA,EACvB;AACA,MAAI,KAAK,UAAW,QAAO,QAAQ,KAAK;AACxC,MAAI,CAAC,MAAM,OAAO,GAAG,SAAS,WAAY,QAAO;AAMjD,MAAI,gBAAgB;AACpB,MAAI;AACJ,QAAM,aAAa,YAA0B;AAC3C,QAAI,CAAC,eAAe;AAClB,sBAAgB;AAChB,YAAM,OAAO,MAAM,QAAQ,IAAI,YAAY,EAAE,IAAI,OAAO,GAAG,CAAC;AAC5D,gBAAU,KAAK,CAAC;AAAA,IAClB;AACA,WAAO;AAAA,EACT;AAiBA,QAAM,eAAe,CAAC,OAAO,SAAS,CAAC,OAAO,YAAY,SAAS,SAAS;AAC5E,QAAM,CAAC,EAAE,SAAS,kBAAkB,eAAe,UAAU,IAAI,MAAM,QAAQ,IAAI;AAAA,IACjF,eAAe,WAAW,IAAI,QAAQ,QAAQ,MAAS;AAAA,IACvD,QAAQ,IAAI,cAAc,EAAE,SAAS,OAAO,GAAG,GAAG;AAAA,IAClD,QAAQ,IAAI,qBAAqB,EAAE,SAAS,OAAO,GAAG,GAAG;AAAA,IACzD,WAAW,QAAQ,IAAI,cAAc,EAAE,iBAAiB,SAAS,GAAG,GAAI,IAAI,QAAQ,QAAQ,CAAC,CAAU;AAAA,IACvG,QAAQ,IAAI,2BAA2B,EAAE,SAAS,OAAO,GAAG,GAAG;AAAA,EACjE,CAAC;AAID,MAAI,CAAC,OAAO,OAAO;AACjB,UAAM,IAAI,MAAM,WAAW;AAC3B,QAAI,GAAG,MAAO,QAAO,QAAQ,OAAO,EAAE,KAAK;AAAA,EAC7C;AAKA,QAAM,QAAQ,KAAK,SAAS,KAAK,IAAI;AAuBrC,QAAM,mBAAmB,oBAAI,IAAY;AACzC,aAAW,KAAK,SAAS;AACvB,QAAI,CAAC,cAAc,GAAG,KAAK,EAAG;AAC9B,UAAM,MAAM,EAAE,mBAAmB,EAAE;AACnC,QAAI,OAAO,QAAQ,YAAY,IAAK,kBAAiB,IAAI,GAAG;AAAA,EAC9D;AACA,SAAO,qBAAqB,MAAM,KAAK,gBAAgB;AAiBvD,QAAM,gBAAgB,QAAQ;AAAA,IAC5B,CAAC,MACC,cAAc,GAAG,KAAK,MAClB,CAAC,aAAa,EAAE,mBAAmB,EAAE,oBAAoB;AAAA,EACjE;AACA,aAAW,KAAK,eAAe;AAC7B,QAAI,EAAE,QAAQ,OAAO,EAAE,SAAS,UAAU;AACxC,iBAAW,OAAO,EAAE,KAAK,MAAM,GAAG,EAAE,IAAI,CAAC,MAAc,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,GAAG;AAChF,cAAM,QAAI,+BAAkB,GAAG;AAC/B,YAAI,CAAC,OAAO,UAAU,SAAS,CAAC,EAAG,QAAO,UAAU,KAAK,CAAC;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AAMA,aAAW,MAAM,kBAAkB;AACjC,UAAM,MAAM,GAAG,mBAAmB;AAClC,QAAI,OAAO,YAAY,QAAQ,SAAU;AACzC,QAAI,CAAC,cAAc,IAAI,KAAK,EAAG;AAC/B,UAAM,IAAI,GAAG;AACb,QAAI,OAAO,MAAM,YAAY,KAAK,CAAC,OAAO,UAAU,SAAS,CAAC,EAAG,QAAO,UAAU,KAAK,CAAC;AAAA,EAC1F;AAGA,MAAI,UAAU;AACZ,UAAM,aAAa;AACnB,UAAM,MAAM,IAAI;AAAA,MACd,WACG,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAChC,OAAO,CAAC,MAAmB,OAAO,MAAM,YAAY,EAAE,SAAS,CAAC;AAAA,IACrE;AACA,QAAI,IAAI,MAAM;AACd,WAAO,eAAe,MAAM,KAAK,GAAG;AAAA,EACtC;AAMA,QAAM,UAAU,WAAW,OAAO,CAAC,MAAM,cAAc,GAAG,KAAK,CAAC;AAChE,QAAM,QAAQ,IAAI;AAAA,IAChB,QACG,OAAO,CAAC,MAAM;AACb,YAAM,MAAO,EAAE,mBAAmB,EAAE,kBAAmB;AACvD,aAAO,EAAE,OAAO,YAAY,QAAQ;AAAA,IACtC,CAAC,EACA,IAAI,CAAC,MAAM,EAAE,qBAAqB,EAAE,eAAe,EACnD,OAAO,OAAO;AAAA,EACnB;AAGA,QAAM,oBAAoB,IAAI;AAAA,IAC5B,QACG,OAAO,CAAC,OAAQ,EAAE,mBAAmB,EAAE,kBAAmB,UAAU,IAAI,EACxE,IAAI,CAAC,MAAM,EAAE,qBAAqB,EAAE,eAAe,EACnD,OAAO,OAAO;AAAA,EACnB;AACA,MAAI,wBAAwB;AAM5B,MAAI,CAAC,OAAO,UAAU,SAAS,UAAU,EAAG,QAAO,UAAU,KAAK,UAAU;AAoB5E,MAAI,OAAO,UAAU,SAAS,GAAG;AAyB/B,UAAM,eAAe,MAAM,QAAQ,IAAI,gBAAgB,EAAE,MAAM,EAAE,KAAK,OAAO,UAAU,EAAE,GAAG,KAAK,QAAQ;AACzG,UAAM,mBAAmB,IAAI;AAAA,MAC3B,aAAa,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,OAAO;AAAA,IAC/E;AACA,QAAI,iBAAiB,OAAO,GAAG;AAC7B,aAAO,YAAY,OAAO,UAAU,OAAO,CAAC,MAAM,CAAC,iBAAiB,IAAI,CAAC,CAAC;AAAA,IAC5E;AACA,UAAM,cAAc,aAAa,OAAO,CAAC,MAAM,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,OAAO,OAAO;AAC9F,QAAI,YAAY,SAAS,GAAG;AAC1B,YAAM,UAAU,MAAM,QAAQ,IAAI,+BAA+B,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,GAAG,GAAG;AAC3G,iBAAW,KAAK,SAAS;AACvB,cAAM,KAAK,EAAE,qBAAqB,EAAE;AACpC,YAAI,GAAI,OAAM,IAAI,EAAE;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAIA,MAAI,MAAM,OAAO,GAAG;AAClB,UAAM,YAAY,MAAM,QAAQ,IAAI,sBAAsB,EAAE,IAAI,EAAE,KAAK,MAAM,KAAK,KAAK,EAAE,EAAE,GAAG,GAAG;AAOjG,UAAM,SAAS,UAAU,OAAO,CAAC,MAAM,YAAY,CAAC,CAAC;AACrD,UAAM,UAAkC,EAAE,QAAQ,GAAG,aAAa,GAAG,YAAY,GAAG,SAAS,EAAE;AAC/F,UAAM,aAAkF,CAAC;AACzF,eAAW,MAAM,QAAQ;AACvB,UAAI,GAAG,QAAQ,CAAC,OAAO,YAAY,SAAS,GAAG,IAAI,EAAG,QAAO,YAAY,KAAK,GAAG,IAAI;AACrF,UAAI,GAAG,SAAS,iCAAqB,kBAAkB,IAAI,GAAG,EAAE,EAAG,yBAAwB;AAC3F,YAAM,WAAW,OAAO,GAAG,uBAAuB,WAC9CA,eAAc,GAAG,oBAAoB,CAAC,CAAC,IACtC,GAAG,sBAAsB,GAAG;AACjC,UAAI,MAAM,QAAQ,QAAQ,GAAG;AAC3B,mBAAW,KAAK,UAAU;AACxB,cAAI,OAAO,MAAM,YAAY,CAAC,OAAO,kBAAkB,SAAS,CAAC,EAAG,QAAO,kBAAkB,KAAK,CAAC;AAAA,QACrG;AAAA,MACF;AACA,YAAM,OAAO,OAAO,GAAG,oBAAoB,WACvCA,eAAc,GAAG,iBAAiB,CAAC,CAAC,IACnC,GAAG,mBAAmB,GAAG;AAC9B,UAAI,QAAQ,OAAO,SAAS,UAAU;AACpC,mBAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,IAA+B,GAAG;AACxE,cAAI,OAAO,QAAQ,YAAY,EAAE,OAAO,SAAU;AAClD,gBAAM,MAAM,WAAW,GAAG;AAC1B,cAAI,CAAC,OAAO,QAAQ,GAAG,IAAI,QAAQ,GAAG,GAAG;AACvC,uBAAW,GAAG,IAAI;AAAA,UACpB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,QAAI,OAAO,KAAK,UAAU,EAAE,SAAS,EAAG,QAAO,iBAAiB;AAAA,EAClE;AAGA,MAAI,yBAAyB,CAAC,OAAO,UAAU,SAAS,2CAA+B,GAAG;AACxF,WAAO,UAAU,QAAQ,2CAA+B;AAAA,EAC1D;AAYA,SAAO,UAAU,cAAc;AAAA,IAC7B,iBAAiB;AAAA;AAAA;AAAA,IAGjB,eAAe,sCAA0B,KAAK,CAAC,MAAc,OAAO,YAAY,SAAS,CAAC,CAAC;AAAA,EAC7F,CAAC;AAID,MAAI,CAAC,OAAO,YAAY,SAAS,SAAS,GAAG;AAC3C,UAAM,YAAa,MAAM,WAAW,IAA4C;AAChF,QAAI,aAAa,QAAQ,aAAa,KAAK,aAAa,IAAK,QAAO,YAAY,KAAK,SAAS;AAAA,EAChG;AAEA,SAAO;AACT;AAIA,SAAS,gBAAgB,IAAqB;AAC5C,MAAI;AAAE,QAAI,KAAK,eAAe,SAAS,EAAE,UAAU,GAAG,CAAC;AAAG,WAAO;AAAA,EAAM,QAAQ;AAAE,WAAO;AAAA,EAAO;AACjG;AACA,SAAS,eAAe,OAAoC;AAC1D,QAAM,IAAI,OAAO,UAAU,WAAW,MAAM,KAAK,IAAI,SAAS,OAAO,OAAO,KAAK,EAAE,KAAK,IAAI;AAC5F,SAAO,KAAK,gBAAgB,CAAC,IAAI,IAAI;AACvC;AACA,SAAS,aAAa,OAAoC;AACxD,QAAM,IAAI,OAAO,UAAU,WAAW,MAAM,KAAK,IAAI,SAAS,OAAO,OAAO,KAAK,EAAE,KAAK,IAAI;AAC5F,SAAO,KAAK;AACd;AACA,SAAS,eAAe,OAAoC;AAC1D,QAAM,IAAI,OAAO,UAAU,WAAW,MAAM,KAAK,EAAE,YAAY,IAAI;AACnE,SAAO,aAAa,KAAK,CAAC,IAAI,IAAI;AACpC;AA8DA,IAAM,oCAAoC;AAC1C,IAAM,2BAA2B,oBAAI,QAA+E;AAiBpH,eAAsB,2BAA2B,OAA8D;AAC7G,QAAM,EAAE,IAAI,UAAU,OAAO,IAAI;AACjC,QAAM,WAAW,GAAG,YAAY,EAAE,IAAI,UAAU,EAAE;AAClD,MAAI,MAAM,OAAO,OAAO,UAAU;AAChC,UAAM,MAAM,yBAAyB,IAAI,EAAE,GAAG,IAAI,QAAQ;AAC1D,QAAI,OAAO,IAAI,YAAY,KAAK,IAAI,EAAG,QAAO,IAAI;AAAA,EACpD;AAEA,QAAM,EAAE,OAAO,OAAO,IAAI,MAAM,mCAAmC,KAAK;AACxE,MAAI,UAAU,MAAM,OAAO,OAAO,UAAU;AAC1C,UAAM,SAAS,yBAAyB,IAAI,EAAE,KAAK,oBAAI,IAA8D;AACrH,WAAO,IAAI,UAAU,EAAE,OAAO,WAAW,KAAK,IAAI,IAAI,kCAAkC,CAAC;AACzF,6BAAyB,IAAI,IAAI,MAAM;AAAA,EACzC;AACA,SAAO;AACT;AAEA,eAAe,mCACb,OACyD;AACzD,QAAM,EAAE,IAAI,UAAU,UAAU,OAAO,IAAI;AAC3C,MAAI,SAAS;AACb,MAAI;AACF,QAAI,YAAY,OAAO,SAAS,QAAQ,YAAY;AAClD,YAAM,OAAO,EAAE,UAAU,OAAO;AAUhC,UAAI;AAAY,UAAI;AAAgB,UAAI;AACxC,UAAI,OAAO,SAAS,YAAY,YAAY;AAC1C,YAAI;AACF,gBAAM,OAAO,MAAM,SAAS,QAAQ,gBAAgB,CAAC,YAAY,UAAU,UAAU,GAAG,IAAI;AAC5F,kBAAQ,KAAK;AACb,sBAAY,KAAK;AACjB,wBAAc,KAAK;AAAA,QACrB,QAAQ;AACN,mBAAS;AAAA,QACX;AAAA,MACF,OAAO;AACL,SAAC,OAAO,WAAW,WAAW,IAAI,MAAM,QAAQ,IAAI;AAAA,UAClD,SAAS,IAAI,gBAAgB,YAAY,IAAI,EAAE,MAAM,MAAM;AACzD,qBAAS;AACT,mBAAO;AAAA,UACT,CAAC;AAAA,UACD,SAAS,IAAI,gBAAgB,UAAU,IAAI,EAAE,MAAM,MAAM;AACvD,qBAAS;AACT,mBAAO;AAAA,UACT,CAAC;AAAA,UACD,SAAS,IAAI,gBAAgB,YAAY,IAAI,EAAE,MAAM,MAAM;AACzD,qBAAS;AACT,mBAAO;AAAA,UACT,CAAC;AAAA,QACH,CAAC;AAAA,MACH;AACA,YAAM,KAAK,eAAe,OAAO,KAAK;AACtC,YAAM,SAAS,aAAa,WAAW,KAAK;AAC5C,YAAM,WAAW,eAAe,aAAa,KAAK;AAClD,UAAI,MAAM,UAAU,UAAU;AAC5B,eAAO,EAAE,OAAO,EAAE,UAAU,MAAM,OAAO,QAAQ,UAAU,SAAS,SAAS,GAAG,QAAQ,MAAM;AAAA,MAChG;AAAA,IACF;AAAA,EACF,QAAQ;AAEN,aAAS;AAAA,EACX;AASA,MAAI,OAAc,CAAC;AACnB,MAAI,MAAM,OAAO,GAAG,SAAS,YAAY;AACvC,QAAI;AACF,UAAI,SAAS,MAAM,GAAG,KAAK,eAAe;AAAA,QACxC,OAAO,EAAE,WAAW,gBAAgB,KAAK,EAAE,KAAK,CAAC,YAAY,UAAU,UAAU,EAAE,GAAG,OAAO,SAAS;AAAA,QACtG,OAAO;AAAA,QACP,SAAS,EAAE,UAAU,KAAK;AAAA,MAC5B,CAAC;AACD,UAAI,UAAW,OAAe,MAAO,UAAU,OAAe;AAC9D,aAAO,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC;AAAA,IAC3C,QAAQ;AACN,eAAS;AAAA,IACX;AAAA,EACF;AACA,QAAM,UAAU,CAAC,MAAc,KAAK,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG;AAC9D,SAAO;AAAA,IACL,OAAO;AAAA,MACL,UAAU,eAAe,QAAQ,UAAU,CAAC,KAAK;AAAA,MACjD,QAAQ,aAAa,QAAQ,QAAQ,CAAC,KAAK;AAAA,MAC3C,UAAU,eAAe,QAAQ,UAAU,CAAC;AAAA,IAC9C;AAAA,IACA;AAAA,EACF;AACF;;;AIvsBO,IAAM,iCAAiC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA8HA,SAAS,KAAK,QAAuD;AACnE,QAAM,MAA+B,CAAC;AACtC,aAAW,OAAO,gCAAgC;AAChD,UAAM,QAAQ,OAAO,GAAG;AACxB,QAAI,UAAU,OAAW,KAAI,GAAG,IAAI;AAAA,EACtC;AACA,SAAO;AACT;AAOA,SAAS,YACP,OACA,WAC6B;AAC7B,QAAM,EAAE,OAAO,OAAO,cAAc,eAAe,aAAa,SAAS,IAAI;AAW7E,QAAM,QAAQ,CAAC,aAAa,OAAO,WAAW,QAAQ;AAEtD,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAML,WAAW,QAAQ,CAAC,IAAI,YAAY,CAAC,OAAO,IAAI,MAAM;AAAA,IACtD,aAAa,QAAQ,MAAM,mBAAmB,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQpD,mBAAmB,QACf,MAAM,mBACH,MAAM,qBAAqB,CAAC,IAC7B,CAAC,IACH,MAAM;AAAA,IACV,UAAU;AAAA,IACV,eAAe,QAAQ,UAAU,YAAY,UAAU;AAAA,IACvD,YAAY,QAAQ,EAAE,QAAQ,MAAM,QAAS,eAAe,QAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMxE,UAAU;AAAA,IACV,QAAQ,MAAM;AAAA,IACd,UAAU,MAAM;AAAA,IAChB,OAAO,MAAM;AAAA,IACb;AAAA,IACA,gBAAgB,MAAM;AAAA;AAAA;AAAA;AAAA,IAItB,SAAS,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMf,UAAU,YAAY,SAAY;AAAA;AAAA,IAElC,cAAc,MAAM;AAAA;AAAA;AAAA,IAGpB,oBAAoB,MAAM;AAAA;AAAA;AAAA;AAAA,IAI1B,aAAa,SAAS,MAAM,WAAW,MAAM,SAAS,MAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,IAKrE,UAAU,YAAY,SAAY,cAAc;AAAA,IAChD,QAAQ,YAAY,SAAa,iBAAiB,cAAc;AAAA,IAChE,UAAU,YAAY,SAAY,cAAc;AAAA,EAClD;AACF;AAOO,SAAS,yBACd,OAC8B;AAC9B,MAAI,CAAC,MAAM,MAAM,OAAQ,QAAO;AAChC,SAAO,KAAK,YAAY,OAAO,KAAK,CAAC;AACvC;AAaO,SAAS,gCACd,OACkB;AAClB,SAAO,KAAK,YAAY,OAAO,CAAC,MAAM,MAAM,MAAM,CAAC;AACrD;;;AC/VA,IAAM,4BAA4B;AAe3B,SAAS,kBAAkB,aAAmC;AACnE,QAAM,OAAO,aAAa;AAC1B,MAAI,CAAC,QAAQ,OAAO,KAAK,SAAS,SAAU,QAAO;AACnD,SAAO;AAAA,IACL,MAAM,KAAK;AAAA,IACX,SACE,OAAO,KAAK,YAAY,YAAY,KAAK,UAAU,KAAK,UAAU;AAAA,EACtE;AACF;AAKA,IAAM,iBAAiB,CAAC,iBAAiB,cAAc,QAAQ;AAC/D,IAAM,iBAAiB,CAAC,WAAW,UAAU,cAAc,YAAY,kBAAkB;AAGlF,SAAS,sBAAsB,SAA6C;AACjF,MAAI,CAAC,QAAS,QAAO;AAGrB,MAAI,OAAO,QAAQ,MAAM,GAAG,EAAE,CAAC,KAAK;AACpC,MAAI,MAAM,KAAK;AACf,SAAO,MAAM,KAAK,KAAK,WAAW,MAAM,CAAC,MAAM,GAAI;AACnD,SAAO,KAAK,MAAM,GAAG,GAAG,KAAK;AAG7B,MAAI,KAAK,SAAS,QAAQ,EAAG,QAAO;AACpC,aAAW,KAAK,gBAAgB;AAC9B,QAAI,KAAK,WAAW,CAAC,KAAK,SAAS,EAAE,QAAQ,OAAO,EAAE,EAAG,QAAO;AAAA,EAClE;AACA,aAAW,KAAK,gBAAgB;AAC9B,QAAI,KAAK,SAAS,CAAC,EAAG,QAAO;AAAA,EAC/B;AACA,SAAO;AACT;AAOO,SAAS,iBAAiB,aAAkB,MAA+B;AAChF,QAAM,OAAO,kBAAkB,WAAW;AAC1C,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,sBAAsB,IAAI,EAAG,QAAO;AACxC,SAAO;AACT;;;AC9BO,IAAM,wBAAwB;AAE9B,IAAM,sBAAsB;AAE5B,IAAM,yBAAyB;AAsD/B,IAAM,sBAAsB;AAAA,EACjC,OAAO;AAAA,EACP,MAAM;AAAA,EACN,SAAS;AACX;AAsBO,SAAS,oBAAoB,OAAoC;AACtE,MAAI,OAAO,MAAM,WAAW,YAAY,MAAM,OAAO,YAAY,MAAM,WAAW;AAChF,WAAO;AAAA,EACT;AACA,MAAI,MAAM,UAAU,MAAM,SAAU,QAAO;AAQ3C,MAAI,OAAO,MAAM,SAAS,YAAY,MAAM,KAAK,SAAS,KAAK,sBAAsB,MAAM,IAAI,GAAG;AAChG,WAAO;AAAA,EACT;AACA,SAAO;AACT;;;AC5EO,IAAM,yBAAuE;AAAA,EAClF,oBAAoB;AAAA,IAClB,MAAM;AAAA,IACN,QACE;AAAA,IAGF,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,yBAAyB;AAAA,IACvB,MAAM;AAAA,IACN,QACE;AAAA,IAGF,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,YAAY;AAAA,IACV,MAAM;AAAA,IACN,QACE;AAAA,IAKF,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAU;AAAA,IACR,MAAM;AAAA,IACN,QACE;AAAA,EAIJ;AAAA,EAEA,mBAAmB;AAAA,IACjB,MAAM;AAAA,IACN,QACE;AAAA,EAKJ;AAAA,EAEA,cAAc;AAAA,IACZ,MAAM;AAAA,IACN,QACE;AAAA,EAEJ;AAAA,EAEA,6BAA6B;AAAA,IAC3B,MAAM;AAAA,IACN,QACE;AAAA,EAGJ;AACF;AAGO,SAAS,sBAAgC;AAC9C,SAAO,OAAO,QAAQ,sBAAsB,EACzC,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,SAAS,EACtC,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI,EACpB,KAAK;AACV;AAMO,SAAS,qBAAqB,OAA8C;AACjF,QAAM,QAAQ,uBAAuB,KAAK;AAC1C,SAAO,OAAO,SAAS,YAAY,MAAM,UAAU;AACrD;;;ACrKO,IAAM,uCAAsF;AAAA,EAC/F,SAAS;AAAA,EACT,WAAW;AAAA,EACX,WAAW;AACf;AAMO,IAAM,4CAA4C,OAAO;AAAA,EAC5D;AACJ;AAOO,IAAM,oCAAoC,CAC7C,UAEA,OAAO,UAAU,eAAe,KAAK,sCAAsC,KAAK;AAG7E,IAAM,gDAAgD,CAAC,UAC1D,0BAA0B,KAAK,6BAAwB,0CAA0C,KAAK,IAAI,CAAC;;;AC6BxG,IAAM,+BAA+B;AA6BrC,SAAS,4BAA4B,MAAiD;AAC3F,QAAM,MAA+B,CAAC;AACtC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,QAAI,IAAI,WAAW,4BAA4B,EAAG;AAClD,QAAI,GAAG,IAAI;AAAA,EACb;AACA,SAAO;AACT;;;ACwEA,kBAAiD;AAvJ1C,SAAS,kBAAkB,GAAS,IAA2B;AACpE,QAAM,QAAQ,IAAI,KAAK,eAAe,SAAS;AAAA,IAC7C,UAAU;AAAA,IACV,MAAM;AAAA,IACN,OAAO;AAAA,IACP,KAAK;AAAA,EACP,CAAC,EAAE,cAAc,CAAC;AAClB,QAAM,MAAM,CAAC,MAAc,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,KAAK;AACxE,SAAO,EAAE,MAAM,IAAI,MAAM,GAAG,OAAO,IAAI,OAAO,GAAG,KAAK,IAAI,KAAK,EAAE;AACnE;AAQO,SAAS,uBAAuB,GAAS,IAA4B;AAC1E,MAAI,MAAM,OAAO,OAAO;AACtB,QAAI;AACF,aAAO,kBAAkB,GAAG,EAAE;AAAA,IAChC,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AAAA,IACL,MAAM,EAAE,eAAe;AAAA,IACvB,OAAO,EAAE,YAAY,IAAI;AAAA,IACzB,KAAK,EAAE,WAAW;AAAA,EACpB;AACF;AAmBO,SAAS,sBAAsBC,MAAa,IAAqB;AACtE,QAAM,IAAI,4BAA4B,KAAKA,IAAG;AAC9C,MAAI,CAAC,EAAG,QAAO;AACf,SAAO;AAAA,IACL,EAAE,MAAM,OAAO,EAAE,CAAC,CAAC,GAAG,OAAO,OAAO,EAAE,CAAC,CAAC,GAAG,KAAK,OAAO,EAAE,CAAC,CAAC,EAAE;AAAA,IAC7D;AAAA,EACF;AACF;AAyCO,SAAS,sBAAsB,OAAuB,IAAqB;AAChF,QAAM,YAAY,KAAK;AAAA,IACrB,MAAM;AAAA,IACN,MAAM,QAAQ;AAAA,IACd,MAAM;AAAA,IACN,MAAM,QAAQ;AAAA,IACd,MAAM,UAAU;AAAA,IAChB,MAAM,UAAU;AAAA,IAChB,MAAM,eAAe;AAAA,EACvB;AACA,MAAI,CAAC,MAAM,OAAO,SAAS,OAAO,MAAM,SAAS,EAAG,QAAO;AAC3D,MAAI;AAOF,UAAM,WAAW,CAAC,MAAsB;AACtC,YAAM,QAAQ,KAAK,MAAM,IAAI,GAAI,IAAI;AACrC,YAAM,IAAI,IAAI,KAAK,eAAe,SAAS;AAAA,QACzC,UAAU;AAAA,QACV,WAAW;AAAA,QACX,MAAM;AAAA,QACN,OAAO;AAAA,QACP,KAAK;AAAA,QACL,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC,EAAE,cAAc,IAAI,KAAK,KAAK,CAAC;AAChC,YAAM,IAAI,CAAC,MAAc,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,KAAK;AAClE,aACE,KAAK,IAAI,EAAE,MAAM,GAAG,EAAE,OAAO,IAAI,GAAG,EAAE,KAAK,GAAG,EAAE,MAAM,GAAG,EAAE,QAAQ,GAAG,EAAE,QAAQ,CAAC,IAAI;AAAA,IAEzF;AAGA,UAAM,OAAO,SAAS,YAAY,SAAS,SAAS,CAAC;AACrD,WAAO,YAAY;AAAA,EACrB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AA4BA,SAAS,gBAAgB,GAAiB;AACxC,QAAM,SAAS,IAAI,KAAK,EAAE,QAAQ,CAAC;AACnC,QAAM,UAAU,OAAO,UAAU,IAAI,KAAK;AAC1C,SAAO,WAAW,OAAO,WAAW,IAAI,SAAS,CAAC;AAClD,QAAM,gBAAgB,IAAI,KAAK,KAAK,IAAI,OAAO,eAAe,GAAG,GAAG,CAAC,CAAC;AACtE,QAAM,SACJ,IACA,KAAK;AAAA,MACD,OAAO,QAAQ,IAAI,cAAc,QAAQ,KAAK,QAC9C,KACE,cAAc,UAAU,IAAI,KAAK,KACnC;AAAA,EACJ;AACF,SAAO,GAAG,OAAO,eAAe,CAAC,KAAK,OAAO,MAAM,EAAE,SAAS,GAAG,GAAG,CAAC;AACvE;AAuBO,SAAS,yBACd,KACA,aACuC;AACvC,MAAI,OAAO,QAAQ,YAAY,IAAI,WAAW,EAAG,QAAO;AACxD,QAAM,MAAM,CAAC,OACX,GAAG,OAAO,GAAG,eAAe,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,OAAO,GAAG,YAAY,IAAI,CAAC,EAAE;AAAA,IAC9E;AAAA,IACA;AAAA,EACF,CAAC,IAAI,OAAO,GAAG,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AAE/C,UAAQ,aAAa;AAAA,IACnB,KAAK,QAAQ;AACX,YAAM,IAAI,YAAY,KAAK,GAAG;AAC9B,UAAI,CAAC,EAAG,QAAO;AACf,YAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AACrB,aAAO,EAAE,OAAO,IAAI,IAAI,KAAK,KAAK,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE;AAAA,IAC9F;AAAA,IACA,KAAK,WAAW;AACd,YAAM,IAAI,qBAAqB,KAAK,GAAG;AACvC,UAAI,CAAC,EAAG,QAAO;AACf,YAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AACrB,YAAM,cAAc,OAAO,EAAE,CAAC,CAAC,IAAI,KAAK;AACxC,aAAO;AAAA,QACL,OAAO,IAAI,IAAI,KAAK,KAAK,IAAI,GAAG,YAAY,CAAC,CAAC,CAAC;AAAA,QAC/C,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,GAAG,aAAa,GAAG,CAAC,CAAC,CAAC;AAAA;AAAA,MACnD;AAAA,IACF;AAAA,IACA,KAAK,SAAS;AACZ,YAAM,IAAI,oBAAoB,KAAK,GAAG;AACtC,UAAI,CAAC,EAAG,QAAO;AACf,YAAM,KAAK,OAAO,EAAE,CAAC,CAAC;AACtB,UAAI,KAAK,KAAK,KAAK,GAAI,QAAO;AAC9B,YAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AACrB,aAAO;AAAA,QACL,OAAO,IAAI,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC;AAAA,QAC3C,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC;AAAA,MACvC;AAAA,IACF;AAAA,IACA,KAAK,OAAO;AACV,YAAM,IAAI,4BAA4B,KAAK,GAAG;AAC9C,UAAI,CAAC,EAAG,QAAO;AACf,YAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AACrB,YAAM,KAAK,OAAO,EAAE,CAAC,CAAC;AACtB,YAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AACrB,YAAM,QAAQ,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC;AAC7C,UAAI,IAAI,KAAK,MAAM,IAAK,QAAO;AAC/B,aAAO,EAAE,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE;AAAA,IACtE;AAAA,IACA,KAAK,QAAQ;AACX,YAAM,IAAI,qBAAqB,KAAK,GAAG;AACvC,UAAI,CAAC,EAAG,QAAO;AACf,YAAM,UAAU,OAAO,EAAE,CAAC,CAAC;AAC3B,YAAM,OAAO,OAAO,EAAE,CAAC,CAAC;AACxB,UAAI,OAAO,KAAK,OAAO,GAAI,QAAO;AAElC,YAAM,OAAO,IAAI,KAAK,KAAK,IAAI,SAAS,GAAG,CAAC,CAAC;AAC7C,YAAM,WAAW,KAAK,UAAU,IAAI,KAAK;AACzC,YAAM,QAAQ,IAAI,KAAK,KAAK,QAAQ,CAAC;AACrC,YAAM,WAAW,KAAK,WAAW,IAAI,WAAW,OAAO,KAAK,CAAC;AAC7D,UAAI,gBAAgB,KAAK,MAAM,IAAK,QAAO;AAC3C,YAAM,MAAM,IAAI,KAAK,MAAM,QAAQ,CAAC;AACpC,UAAI,WAAW,MAAM,WAAW,IAAI,CAAC;AACrC,aAAO,EAAE,OAAO,IAAI,KAAK,GAAG,KAAK,IAAI,GAAG,EAAE;AAAA,IAC5C;AAAA,IACA;AACE,aAAO;AAAA,EACX;AACF;;;AC1NA,IAAM,qBAAqB;AAC3B,IAAM,sBAAsB;AAC5B,IAAM,0BAA0B;AAQhC,IAAM,qBAA+B;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,kBAAkB;AAUxB,IAAM,yBAAmC;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,wBAAwB,KAAuB;AAC7D,QAAM,UAAW,KAAsC;AACvD,QAAM,OAAO,OAAO,YAAY,WAAW,UAAU,OAAO,OAAO,EAAE;AAErE,MAAI,uBAAuB,KAAK,CAAC,OAAO,GAAG,KAAK,IAAI,CAAC,EAAG,QAAO;AAC/D,QAAM,OAAQ,KAAmC;AACjD,MAAI,OAAO,SAAS,YAAY,gBAAgB,KAAK,IAAI,EAAG,QAAO;AACnE,SAAO,mBAAmB,KAAK,CAAC,OAAO,GAAG,KAAK,IAAI,CAAC;AACtD;AAEA,IAAM,eAAe,CAAC,OAA8B,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AASpG,eAAe,UAAa,IAAqC,MAAwC;AACvG,MAAI;AACJ,WAAS,UAAU,GAAG,WAAW,KAAK,YAAY,WAAW;AAC3D,QAAI;AACF,aAAO,MAAM,GAAG,OAAO;AAAA,IACzB,SAAS,KAAK;AACZ,kBAAY;AACZ,UAAI,WAAW,KAAK,cAAc,CAAC,KAAK,iBAAiB,GAAG,EAAG,OAAM;AACrE,YAAM,SAAS,KAAK,MAAM,KAAK,OAAO,IAAI,EAAE;AAC5C,YAAM,KAAK,MAAM,KAAK,gBAAgB,MAAM,UAAU,KAAK,MAAM;AAAA,IACnE;AAAA,EACF;AAGA,QAAM;AACR;AAQA,eAAsB,mBAAsB,IAAqC,OAAqB,CAAC,GAAe;AACpH,SAAO,UAAU,IAAI;AAAA,IACnB,YAAY,KAAK,IAAI,GAAG,KAAK,cAAc,mBAAmB;AAAA,IAC9D,eAAe,KAAK,iBAAiB;AAAA,IACrC,kBAAkB,KAAK,oBAAoB;AAAA,IAC3C,OAAO,KAAK,SAAS;AAAA,EACvB,CAAC;AACH;AAWA,eAAsB,UACpB,MACA,MACwC;AACxC,QAAM,YAAY,KAAK,IAAI,GAAG,KAAK,aAAa,kBAAkB;AAClE,QAAM,YAAkC;AAAA,IACtC,YAAY,KAAK,IAAI,GAAG,KAAK,cAAc,mBAAmB;AAAA,IAC9D,eAAe,KAAK,iBAAiB;AAAA,IACrC,kBAAkB,KAAK,oBAAoB;AAAA,IAC3C,OAAO,KAAK,SAAS;AAAA,EACvB;AAEA,QAAM,UAAyC,IAAI,MAAM,KAAK,MAAM;AAEpE,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,WAAW;AAC3D,UAAM,QAAQ,KAAK,MAAM,OAAO,QAAQ,SAAS;AACjD,QAAI;AAIF,UAAI,KAAK,mBAAmB;AAC1B,cAAM,WAAW,MAAM,UAAU,CAAC,YAAY,KAAK,kBAAmB,OAAO,EAAE,QAAQ,CAAC,GAAG,SAAS;AACpG,YAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,MAAM,QAAQ;AAChE,gBAAM,OAAO;AAAA,YACX,IAAI;AAAA,cACF,yCACE,MAAM,QAAQ,QAAQ,IAAI,GAAG,SAAS,MAAM,gBAAgB,OAAO,OAAO,QAAQ,CACpF,UAAU,MAAM,MAAM;AAAA,YACxB;AAAA,YACA,EAAE,MAAM,2BAA2B;AAAA,UACrC;AAAA,QACF;AACA,iBAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,gBAAM,IAAI,SAAS,CAAC;AACpB,kBAAQ,QAAQ,CAAC,IAAI,EAAE,KACnB,EAAE,OAAO,QAAQ,GAAG,IAAI,MAAM,QAAQ,EAAE,OAAO,IAC/C,EAAE,OAAO,QAAQ,GAAG,IAAI,OAAO,OAAO,EAAE,MAAM;AAAA,QACpD;AACA;AAAA,MACF;AACA,YAAM,UAAU,MAAM,UAAU,CAAC,YAAY,KAAK,WAAW,OAAO,EAAE,QAAQ,CAAC,GAAG,SAAS;AAU3F,UAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,QAAQ,WAAW,MAAM,QAAQ;AAC9D,cAAM,OAAO;AAAA,UACX,IAAI;AAAA,YACF,kCACE,MAAM,QAAQ,OAAO,IAAI,GAAG,QAAQ,MAAM,eAAe,OAAO,OAAO,OAAO,CAChF,UAAU,MAAM,MAAM;AAAA,UACxB;AAAA,UACA,EAAE,MAAM,2BAA2B;AAAA,QACrC;AAAA,MACF;AACA,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,gBAAQ,QAAQ,CAAC,IAAI,EAAE,OAAO,QAAQ,GAAG,IAAI,MAAM,QAAQ,QAAQ,CAAC,EAAE;AAAA,MACxE;AAAA,IACF,SAAS,UAAU;AAIjB,UAAI,MAAM,WAAW,GAAG;AACtB,gBAAQ,KAAK,IAAI,EAAE,OAAO,OAAO,IAAI,OAAO,OAAO,SAAS;AAC5D;AAAA,MACF;AAMA,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAM,MAAM,QAAQ;AACpB,YAAI;AACF,gBAAM,SAAS,MAAM,UAAU,CAAC,YAAY,KAAK,SAAS,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,GAAG,SAAS;AAC3F,kBAAQ,GAAG,IAAI,EAAE,OAAO,KAAK,IAAI,MAAM,OAAO;AAAA,QAChD,SAAS,KAAK;AACZ,kBAAQ,GAAG,IAAI,EAAE,OAAO,KAAK,IAAI,OAAO,OAAO,IAAI;AAAA,QACrD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;;;ACtLO,SAAS,mCAAmC,QAA2B;AAC5E,QAAM,SAAU,QAAgD;AAChE,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO,CAAC;AACnD,QAAM,MAAgB,CAAC;AACvB,aAAW,CAAC,MAAM,GAAG,KAAK,OAAO,QAAQ,MAAM,GAAG;AAChD,QAAI,OAAO,IAAI,aAAa,KAAM,KAAI,KAAK,IAAI;AAAA,EACjD;AACA,SAAO;AACT;AAgBO,SAAS,oCAAoC,QAAiB,SAAwB;AAC3F,MAAI,CAAC,QAAS;AACd,QAAM,iBAAiB,mCAAmC,MAAM;AAChE,MAAI,eAAe,WAAW,EAAG;AACjC,QAAM,OAAO,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC,OAAO;AACxD,aAAW,OAAO,MAAM;AACtB,QAAI,CAAC,OAAO,OAAO,QAAQ,SAAU;AACrC,eAAW,SAAS,eAAgB,QAAQ,IAAgC,KAAK;AAAA,EACnF;AACF;;;ACzFA,IAAAC,sBAAuC;AAEvC,IAAAC,iBAKO;AAGP,IAAM,aAAa,EAAE,UAAU,KAAK;AAGpC,IAAM,qBAAqB;AAuBpB,SAAS,kBAAqB,QAAgD;AACnF,QAAM,IAAI;AAKV,MAAI,OAAO,GAAG,gBAAgB,WAAY,QAAO;AACjD,QAAM,oBAAoB,EAAE,uBAAuB;AACnD,QAAM,gBAAgB,oBAAoB,EAAE,kBAAkB,iBAAiB,IAAI;AACnF,SAAO,CAAC,iBAAiB,OAAQ,cAAiD,qBAAqB;AACzG;AA0HO,IAAM,wBAAN,MAA6D;AAAA,EAA7D;AACL,SAAiB,QAAQ,oBAAI,IAA2B;AAAA;AAAA,EAExD,SAAS,MAA2B;AAClC,SAAK,MAAM,IAAI,KAAK,IAAI,IAAI;AAAA,EAC9B;AAAA,EAEA,IAAI,QAA2C;AAC7C,WAAO,KAAK,MAAM,IAAI,MAAM;AAAA,EAC9B;AAAA,EAEA,OAAwB;AACtB,WAAO,CAAC,GAAG,KAAK,MAAM,OAAO,CAAC;AAAA,EAChC;AACF;AAiBO,IAAM,0BAAN,cAAsC,MAAM;AAAA,EAEjD,YAAY,MAAc,SAAiB;AACzC,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AACF;AAKO,SAAS,WACd,MACA,WACA,YAAY,KAAK,aAAa,oBACZ;AAClB,QAAM,OAAO,KAAK,IAAI,GAAG,SAAS;AAClC,QAAM,SAA2B,CAAC;AAClC,OAAK,MAAM,QAAQ,CAAC,MAAM,cAAc;AACtC,UAAM,QAAQ,UAAU,SAAS,KAAK;AACtC,aAAS,SAAS,GAAG,SAAS,OAAO,UAAU,MAAM;AACnD,aAAO,KAAK;AAAA,QACV,OAAO,OAAO;AAAA,QACd;AAAA,QACA,UAAU,KAAK;AAAA,QACf;AAAA,QACA,QAAQ,KAAK,IAAI,MAAM,QAAQ,MAAM;AAAA,MACvC,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAUO,SAAS,kBAAkB,MAAqB,QAA2C;AAChG,QAAM,QAAQ,KAAK,UAAU;AAAA,IAC3B,IAAI,KAAK;AAAA,IACT,OAAO,KAAK,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,IACnC,QAAQ,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,CAAC;AAAA,EAC7D,CAAC;AACD,aAAO,gCAAW,QAAQ,EAAE,OAAO,OAAO,MAAM,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAC7E;AAYA,eAAe,YACb,QACA,OACA,aACe;AACf,QAAM,OAAO;AAAA,IACX;AAAA,IACA,EAAE,GAAG,OAAO,YAAY,MAAM,eAAc,oBAAI,KAAK,GAAE,YAAY,EAAE;AAAA,IACrE,EAAE,SAAS,eAAe,EAAE,GAAG,WAAW,EAAE;AAAA,EAC9C;AACF;AAWA,eAAsB,eACpB,QACA,OACkC;AAClC,QAAM,OAAQ,MAAM,OAAO;AAAA,IACzB;AAAA,IACA,EAAE,OAAO,EAAE,QAAQ,MAAM,EAAE;AAAA,IAC3B,EAAE,SAAS,EAAE,GAAG,WAAW,EAAE;AAAA,EAC/B;AACA,SAAO,CAAC,GAAI,QAAQ,CAAC,CAAE,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,EAAE,GAAG,IAAI,OAAO,EAAE,GAAG,CAAC;AACvE;AAGA,SAAS,WAAW,QAA0C,MAAyC;AACrG,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,KAAK,QAAQ;AACtB,QAAI,EAAE,SAAS,QAAQ,OAAO,EAAE,gBAAgB,SAAU,KAAI,IAAI,EAAE,WAAW;AAAA,EACjF;AACA,SAAO;AACT;AASA,eAAsB,oBAAoB,QAAoD;AAC5F,QAAM,UAAW,MAAM,OAAO;AAAA,IAC5B;AAAA,IACA,EAAE,OAAO,EAAE,MAAM,cAAc,EAAE;AAAA,IACjC,EAAE,SAAS,EAAE,GAAG,WAAW,EAAE;AAAA,EAC/B;AAEA,QAAM,MAAwB,CAAC;AAC/B,aAAW,SAAS,WAAW,CAAC,GAAG;AACjC,UAAM,SAAS,MAAM,eAAe,QAAQ,MAAM,MAAM;AACxD,QAAI,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,UAAU,EAAG;AAE/C,UAAM,YAAY,WAAW,QAAQ,YAAY;AACjD,UAAM,cAAc,WAAW,QAAQ,aAAa;AACpD,UAAM,cAAc,CAAC,GAAG,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,IAAI,CAAC,CAAC;AAGpE,QAAI,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,YAAY,KAAK,YAAY,WAAW,EAAG;AAE7E,UAAM,UAAU,CAAC,GAAG,WAAW,QAAQ,eAAe,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC;AACxF,QAAI,SAAS,MAAM;AACnB,QAAI;AACF,eAAS,MAAM,SAAU,KAAK,MAAM,MAAM,MAAM,EAAE,UAAU,MAAM,SAAU,MAAM;AAAA,IACpF,QAAQ;AAAA,IAGR;AACA,QAAI,KAAK;AAAA,MACP,OAAO,MAAM;AAAA,MACb;AAAA,MACA,UAAU,MAAM,aAAa;AAAA,MAC7B,aAAa,MAAM;AAAA,MACnB,WAAW,MAAM;AAAA,MACjB,iBAAiB,CAAC,GAAG,SAAS,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAAA,MACpD,eAAe,QAAQ,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAAA,MAC3C,mBAAmB,CAAC,GAAG,WAAW,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAAA,IAC1D,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAmBA,eAAe,SACb,QACA,MACA,WACqB;AACrB,QAAM,aAA0B,CAAC;AACjC,aAAW,QAAQ,KAAK,MAAO,YAAW,KAAM,MAAM,KAAK,KAAK,MAAM,KAAM,CAAC,CAAC;AAC9E,QAAM,SAAS,WAAW,MAAM,WAAW,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,KAAK,SAAS;AAC5F,SAAO,EAAE,QAAQ,UAAU,kBAAkB,MAAM,MAAM,GAAG,WAAW;AACzE;AASA,eAAsB,oBACpB,QACA,MACA,UAAsC,CAAC,GACV;AAC7B,QAAM,MAAM,QAAQ,QAAQ,OAAM,oBAAI,KAAK,GAAE,YAAY;AAMzD,MAAI,CAAC,kBAAkB,MAAM,GAAG;AAC9B,UAAM,IAAI;AAAA,MACR;AAAA,MACA,mBAAmB,KAAK,EAAE;AAAA,IAE5B;AAAA,EACF;AAEA,QAAM,EAAE,QAAQ,UAAU,WAAW,IAAI,MAAM,SAAS,QAAQ,MAAM,QAAQ,SAAS;AAGvF,QAAM,WAAW,QAAQ,QAAQ,KAAK;AACtC,QAAM,QAAQ,QAAQ,aAAS,gCAAW;AAC1C,MAAI,SAAkC,CAAC;AACvC,MAAI,MAAM;AACV,MAAI,YAAY,oBAAI,IAAY;AAChC,MAAI,cAAc,oBAAI,IAAY;AAClC,QAAM,kBAAkB,oBAAI,IAAoB;AAEhD,MAAI,UAAU;AACZ,aAAS,MAAM,eAAe,QAAQ,KAAK;AAC3C,QAAI,OAAO,WAAW,GAAG;AACvB,YAAM,IAAI,wBAAwB,eAAe,4BAA4B,KAAK,IAAI;AAAA,IACxF;AACA,UAAM,QAAQ,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,aAAa;AACzD,QAAI,OAAO,aAAa,MAAM,cAAc,UAAU;AAIpD,YAAM,IAAI;AAAA,QACR;AAAA,QACA,2BAA2B,KAAK,gBAAgB,QAAQ,iCAAiC,MAAM,SAAS;AAAA,MAE1G;AAAA,IACF;AACA,QAAI,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,UAAU,GAAG;AAC7C,aAAO;AAAA,QACL;AAAA,QAAO,QAAQ;AAAA,QAAa,aAAa,OAAO;AAAA,QAChD,iBAAiB,WAAW,QAAQ,YAAY,EAAE;AAAA,QAClD,mBAAmB,WAAW,QAAQ,aAAa,EAAE;AAAA,QAAM;AAAA,MAC7D;AAAA,IACF;AACA,UAAM,OAAO,OAAO,CAAC,GAAG,MAAM,KAAK,IAAI,GAAG,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC;AAC/D,gBAAY,WAAW,QAAQ,YAAY;AAC3C,kBAAc,WAAW,QAAQ,aAAa;AAC9C,eAAW,KAAK,QAAQ;AACtB,UAAI,EAAE,SAAS,mBAAmB,OAAO,EAAE,gBAAgB,UAAU;AACnE,wBAAgB,IAAI,EAAE,cAAc,gBAAgB,IAAI,EAAE,WAAW,KAAK,KAAK,CAAC;AAAA,MAClF;AAAA,IACF;AAAA,EACF;AAOA,aAAW,QAAQ,KAAK,OAAO;AAC7B,QAAI,CAAC,KAAK,UAAW;AACrB,QAAI;AACF,YAAM,KAAK,UAAU,MAAM;AAAA,IAC7B,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR;AAAA,QACA,mBAAmB,KAAK,EAAE,kCAAkC,KAAK,IAAI,aAAa,QAAQ,GAAG,CAAC;AAAA,MAChG;AAAA,IACF;AAAA,EACF;AAKA,MAAI,KAAK,YAAY,cAAc;AACjC,UAAM,UAAU,KAAK,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI;AACzE,QAAI,QAAQ,SAAS,GAAG;AACtB,YAAM,IAAI;AAAA,QACR;AAAA,QACA,mBAAmB,KAAK,EAAE,gDAAgD,QAAQ,KAAK,IAAI,CAAC;AAAA,MAC9F;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,CAAC,MAAiC,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM;AAC5G,QAAM,OAAO,MAAc;AAE3B,MAAI,CAAC,UAAU;AACb,UAAM,YAAY,QAAQ;AAAA,MACxB,QAAQ;AAAA,MAAO,KAAK,KAAK;AAAA,MAAG,MAAM;AAAA,MAAe,WAAW;AAAA,MAC5D,cAAc,KAAK;AAAA,MAAa,YAAY,IAAI;AAAA,MAChD,QAAQ,KAAK,UAAU;AAAA,QACrB,QAAQ,KAAK;AAAA,QACb,SAAS,KAAK,WAAW;AAAA,QACzB,QAAQ,OAAO,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,MAAM,EAAE,UAAU,QAAQ,EAAE,QAAQ,QAAQ,EAAE,OAAO,EAAE;AAAA,MAClG,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAIA,MAAI,YAAY,KAAK,YAAY,cAAc;AAC7C,WAAO,MAAM,OAAO,QAAQ,MAAM;AAAA,MAChC;AAAA,MAAO;AAAA,MAAU;AAAA,MAAQ;AAAA,MAAQ;AAAA,MAAM;AAAA,MACvC;AAAA,MAAW;AAAA,MAAa,aAAa,OAAO;AAAA,MAC5C,OAAO,IAAI,MAAM,QAAQ,KAAK,8DAA8D;AAAA,IAC9F,CAAC;AAAA,EACH;AAGA,aAAW,SAAS,QAAQ;AAC1B,QAAI,UAAU,IAAI,MAAM,KAAK,EAAG;AAChC,UAAM,WAAW,gBAAgB,IAAI,MAAM,KAAK,KAAK,KAAK;AAC1D,oBAAgB,IAAI,MAAM,OAAO,OAAO;AACxC,UAAM,OAAO,KAAK,MAAM,MAAM,SAAS;AACvC,UAAM,OAAO,OAAO,KAAK;AAIzB,UAAM,YAAY,QAAQ;AAAA,MACxB,QAAQ;AAAA,MAAO,KAAK,KAAK;AAAA,MAAG,MAAM;AAAA,MAClC,aAAa,MAAM;AAAA,MAAO;AAAA,MAAS,cAAc,KAAK;AAAA,MAAa,YAAY,IAAI;AAAA,IACrF,CAAC;AAED,QAAI;AACF,YAAM,OAAO,YAAY,OAAO,WAAoB;AAClD,cAAM,KAAK,QAAQ,MAAM,EAAE,OAAO,YAAY,MAAM,OAAO,SAAS,SAAS,OAAO,GAAG,MAAM;AAE7F,cAAM;AAAA,UACJ;AAAA,UACA;AAAA,YACE,QAAQ;AAAA,YAAO,KAAK,KAAK;AAAA,YAAG,MAAM;AAAA,YAClC,aAAa,MAAM;AAAA,YAAO;AAAA,YAAS,cAAc,KAAK;AAAA,YAAa,YAAY,IAAI;AAAA,UACrF;AAAA,UACA;AAAA,QACF;AAAA,MACF,GAAG,EAAE,GAAG,WAAW,CAAC;AACpB,gBAAU,IAAI,MAAM,KAAK;AAAA,IAC3B,SAAS,KAAK;AAGZ,aAAO,MAAM,OAAO,QAAQ,MAAM;AAAA,QAChC;AAAA,QAAO;AAAA,QAAU;AAAA,QAAQ;AAAA,QAAQ;AAAA,QAAM;AAAA,QACvC;AAAA,QAAW;AAAA,QAAa,aAAa,OAAO;AAAA,QAAQ,OAAO;AAAA,MAC7D,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,YAAY,QAAQ;AAAA,IACxB,QAAQ;AAAA,IAAO,KAAK,KAAK;AAAA,IAAG,MAAM;AAAA,IAAY,cAAc,KAAK;AAAA,IAAa,YAAY,IAAI;AAAA,EAChG,CAAC;AACD,SAAO;AAAA,IACL;AAAA,IAAO,QAAQ;AAAA,IAAa,aAAa,OAAO;AAAA,IAChD,iBAAiB,UAAU;AAAA,IAAM,mBAAmB,YAAY;AAAA,IAAM;AAAA,EACxE;AACF;AA2BA,eAAe,OACb,QACA,MACA,GAC6B;AAC7B,QAAM,QAAQ,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACnD,aAAW,SAAS,OAAO;AACzB,QAAI,EAAE,YAAY,IAAI,KAAK,EAAG;AAC9B,UAAM,QAAQ,EAAE,OAAO,KAAK;AAC5B,UAAM,OAAO,KAAK,MAAM,MAAM,SAAS;AAEvC,QAAI,CAAC,KAAK,YAAY;AAGpB,YAAM,YAAY,QAAQ;AAAA,QACxB,QAAQ,EAAE;AAAA,QAAO,KAAK,EAAE,KAAK;AAAA,QAAG,MAAM;AAAA,QACtC,aAAa;AAAA,QAAO,cAAc,KAAK;AAAA,QAAa,YAAY,EAAE,IAAI;AAAA,QACtE,QAAQ,KAAK,UAAU;AAAA,UACrB,OAAO;AAAA,UAAc,QAAQ;AAAA,UAC7B,MAAM,KAAK;AAAA,UAAM,OAAO,QAAQ,EAAE,KAAK;AAAA,QACzC,CAAC;AAAA,MACH,CAAC;AACD,aAAO;AAAA,QACL,OAAO,EAAE;AAAA,QAAO,QAAQ;AAAA,QAAU,aAAa,EAAE;AAAA,QACjD,iBAAiB,EAAE,UAAU;AAAA,QAAM,mBAAmB,EAAE,YAAY;AAAA,QACpE,UAAU,EAAE;AAAA,QAAU,OAAO,EAAE;AAAA,MACjC;AAAA,IACF;AAEA,UAAM,UAAU;AAChB,QAAI;AACF,YAAM,OAAO,YAAY,OAAO,WAAoB;AAClD,cAAM,KAAK,WAAY,EAAE,OAAO,KAAK,GAAG,EAAE,OAAO,EAAE,OAAO,YAAY,OAAO,SAAS,SAAS,OAAO,GAAG,MAAM;AAC/G,cAAM;AAAA,UACJ;AAAA,UACA;AAAA,YACE,QAAQ,EAAE;AAAA,YAAO,KAAK,EAAE,KAAK;AAAA,YAAG,MAAM;AAAA,YACtC,aAAa;AAAA,YAAO;AAAA,YAAS,cAAc,KAAK;AAAA,YAAa,YAAY,EAAE,IAAI;AAAA,UACjF;AAAA,UACA;AAAA,QACF;AAAA,MACF,GAAG,EAAE,GAAG,WAAW,CAAC;AACpB,QAAE,YAAY,IAAI,KAAK;AAAA,IACzB,SAAS,KAAK;AACZ,YAAM,YAAY,QAAQ;AAAA,QACxB,QAAQ,EAAE;AAAA,QAAO,KAAK,EAAE,KAAK;AAAA,QAAG,MAAM;AAAA,QACtC,aAAa;AAAA,QAAO,cAAc,KAAK;AAAA,QAAa,YAAY,EAAE,IAAI;AAAA,QACtE,QAAQ,KAAK,UAAU;AAAA,UACrB,OAAO;AAAA,UAAc,MAAM,KAAK;AAAA,UAChC,OAAO,QAAQ,GAAG;AAAA,UAAG,OAAO,QAAQ,EAAE,KAAK;AAAA,QAC7C,CAAC;AAAA,MACH,CAAC;AACD,aAAO;AAAA,QACL,OAAO,EAAE;AAAA,QAAO,QAAQ;AAAA,QAAU,aAAa,EAAE;AAAA,QACjD,iBAAiB,EAAE,UAAU;AAAA,QAAM,mBAAmB,EAAE,YAAY;AAAA,QACpE,UAAU,EAAE;AAAA,QAAU,OAAO;AAAA,MAC/B;AAAA,IACF;AAAA,EACF;AAEA,QAAM,YAAY,QAAQ;AAAA,IACxB,QAAQ,EAAE;AAAA,IAAO,KAAK,EAAE,KAAK;AAAA,IAAG,MAAM;AAAA,IACtC,cAAc,KAAK;AAAA,IAAa,YAAY,EAAE,IAAI;AAAA,IAClD,QAAQ,KAAK,UAAU,EAAE,OAAO,WAAW,OAAO,QAAQ,EAAE,KAAK,GAAG,aAAa,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC;AAAA,EAC7H,CAAC;AACD,SAAO;AAAA,IACL,OAAO,EAAE;AAAA,IAAO,QAAQ;AAAA,IAAe,aAAa,EAAE;AAAA,IACtD,iBAAiB,EAAE,UAAU;AAAA,IAAM,mBAAmB,EAAE,YAAY;AAAA,IACpE,UAAU,EAAE;AAAA,IAAU,OAAO,EAAE;AAAA,EACjC;AACF;AAGA,eAAsB,uBACpB,QACA,MACA,OACA,UAAqD,CAAC,GACzB;AAC7B,SAAO,oBAAoB,QAAQ,MAAM,EAAE,GAAG,SAAS,MAAM,CAAC;AAChE;AAEA,SAAS,QAAQ,KAAsB;AACrC,MAAI,eAAe,MAAO,QAAO,IAAI;AACrC,MAAI;AACF,WAAO,OAAO,GAAG;AAAA,EACnB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AC9oBA,IAAAC,eAIO;AAwCA,IAAM,0BAAN,cAAsC,MAAM;AAAA,EAMjD,YAAY,OAAe,YAAqB;AAC9C;AAAA,MACE,qCAAqC,KAAK,UACzC,aACG,kBAAkB,UAAU,SAC5B,0KAGJ;AAAA,IAEF;AAbF,SAAS,SAAS;AAClB,SAAS,OAAO;AAad,SAAK,OAAO;AACZ,SAAK,QAAQ;AACb,SAAK,aAAa;AAAA,EACpB;AACF;AAWO,IAAM,6BAAN,cAAyC,MAAM;AAAA,EAMpD,YAAY,OAAe,QAAgB;AACzC,UAAM,wBAAwB,KAAK,0BAA0B,MAAM,EAAE;AAJvE;AAAA,SAAS,SAAS;AAClB,SAAS,OAAO;AAId,SAAK,OAAO;AACZ,SAAK,QAAQ;AAAA,EACf;AACF;AAGA,SAAS,IAAI,MAAc,OAAe,KAAqB;AAC7D,QAAM,IAAI,CAAC,MAAc,OAAO,CAAC,EAAE,SAAS,GAAG,GAAG;AAClD,SAAO,GAAG,IAAI,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC;AACtC;AASA,SAAS,SAAS,KAAW,UAAyB;AACpD,QAAM,EAAE,MAAM,OAAO,IAAI,IAAI,uBAAuB,KAAK,QAAQ;AACjE,SAAO,IAAI,KAAK,KAAK,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC;AAChD;AAEA,IAAM,QAAQ,CAAC,MAAoB,IAAI,EAAE,eAAe,GAAG,EAAE,YAAY,IAAI,GAAG,EAAE,WAAW,CAAC;AAK9F,SAAS,cAAc,MAAkB,GAAe;AACtD,QAAM,IAAI,IAAI,KAAK,EAAE,QAAQ,CAAC;AAC9B,UAAQ,MAAM;AAAA,IACZ,KAAK,QAAQ;AACX,YAAM,OAAO,EAAE,UAAU,IAAI,KAAK;AAClC,QAAE,WAAW,EAAE,WAAW,IAAI,GAAG;AACjC,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AACH,aAAO,IAAI,KAAK,KAAK,IAAI,EAAE,eAAe,GAAG,EAAE,YAAY,GAAG,CAAC,CAAC;AAAA,IAClE,KAAK;AACH,aAAO,IAAI,KAAK,KAAK,IAAI,EAAE,eAAe,GAAG,KAAK,MAAM,EAAE,YAAY,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;AAAA,IACtF,KAAK;AACH,aAAO,IAAI,KAAK,KAAK,IAAI,EAAE,eAAe,GAAG,GAAG,CAAC,CAAC;AAAA,EACtD;AACF;AAGA,SAAS,YAAY,MAAc,OAAuB;AACxD,SAAO,IAAI,KAAK,KAAK,IAAI,MAAM,QAAQ,GAAG,CAAC,CAAC,EAAE,WAAW;AAC3D;AAWA,SAAS,iBAAiB,GAAS,GAAiB;AAClD,QAAM,OAAO,EAAE,eAAe;AAC9B,QAAM,QAAQ,EAAE,YAAY,IAAI;AAChC,QAAM,aAAa,OAAO,KAAK,MAAM,QAAQ,EAAE;AAC/C,QAAM,eAAgB,QAAQ,KAAM,MAAM;AAC1C,QAAM,MAAM,KAAK,IAAI,EAAE,WAAW,GAAG,YAAY,YAAY,WAAW,CAAC;AACzE,SAAO,IAAI,KAAK,KAAK;AAAA,IACnB;AAAA,IAAY;AAAA,IAAa;AAAA,IACzB,EAAE,YAAY;AAAA,IAAG,EAAE,cAAc;AAAA,IAAG,EAAE,cAAc;AAAA,IAAG,EAAE,mBAAmB;AAAA,EAC9E,CAAC;AACH;AAGA,SAAS,WAAW,MAAkB,GAAS,GAAiB;AAC9D,UAAQ,MAAM;AAAA,IACZ,KAAK,QAAQ;AACX,YAAM,IAAI,IAAI,KAAK,EAAE,QAAQ,CAAC;AAC9B,QAAE,WAAW,EAAE,WAAW,IAAI,IAAI,CAAC;AACnC,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AAAS,aAAO,iBAAiB,GAAG,CAAC;AAAA,IAC1C,KAAK;AAAW,aAAO,iBAAiB,GAAG,IAAI,CAAC;AAAA,IAChD,KAAK;AAAQ,aAAO,iBAAiB,GAAG,IAAI,EAAE;AAAA,EAChD;AACF;AAGA,SAAS,SAAS,MAAqB,GAAS,GAAiB;AAC/D,QAAM,IAAI,IAAI,KAAK,EAAE,QAAQ,CAAC;AAC9B,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAU,QAAE,cAAc,EAAE,cAAc,IAAI,CAAC;AAAG,aAAO;AAAA,IAC9D,KAAK;AAAQ,QAAE,YAAY,EAAE,YAAY,IAAI,CAAC;AAAG,aAAO;AAAA,IACxD,KAAK;AAAO,QAAE,WAAW,EAAE,WAAW,IAAI,CAAC;AAAG,aAAO;AAAA,IACrD,KAAK;AAAQ,QAAE,WAAW,EAAE,WAAW,IAAI,IAAI,CAAC;AAAG,aAAO;AAAA;AAAA,IAE1D,KAAK;AAAS,aAAO,iBAAiB,GAAG,CAAC;AAAA,IAC1C,KAAK;AAAQ,aAAO,iBAAiB,GAAG,IAAI,EAAE;AAAA,EAChD;AACF;AAOA,IAAM,YAAY;AAElB,SAAS,mBAAmB,OAAe,OAAiC;AAC1E,QAAM,IAAI,UAAU,KAAK,KAAK;AAC9B,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,MAAO,EAAE,CAAC,KAAK;AACrB,QAAM,OAAO,EAAE,CAAC;AAChB,QAAM,QAAQ,EAAE,CAAC;AACjB,QAAM,SAAS,QAAQ,SAAS,KAAK,QAAQ,SAAS,IAAI;AAM1D,QAAM,cAAc,cAAc,MAAM,WAAW,MAAM,cAAc,MAAM,KAAK,GAAG,MAAM,CAAC;AAC5F,MAAI,UAAU,QAAS,QAAO,MAAM,WAAW;AAG/C,QAAM,OAAO,WAAW,MAAM,aAAa,CAAC;AAC5C,OAAK,WAAW,KAAK,WAAW,IAAI,CAAC;AACrC,SAAO,MAAM,IAAI;AACnB;AAQO,SAAS,mBACd,OACA,MAAoC,CAAC,GAC5B;AACT,QAAM,MAAM,IAAI,OAAO,oBAAI,KAAK;AAGhC,MAAI,UAAU,mBAAmB;AAC/B,QAAI,CAAC,IAAI,QAAQ;AACf,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,MAGF;AAAA,IACF;AACA,WAAO,IAAI;AAAA,EACb;AACA,MAAI,UAAU,kBAAkB;AAC9B,QAAI,CAAC,IAAI,OAAO;AACd,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,MAEF;AAAA,IACF;AACA,WAAO,IAAI;AAAA,EACb;AAGA,QAAM,QAAQ,SAAS,KAAK,IAAI,QAAQ;AAExC,UAAQ,OAAO;AAAA,IACb,KAAK;AAAO,aAAO,IAAI,YAAY;AAAA,IACnC,KAAK;AAAS,aAAO,MAAM,KAAK;AAAA,IAChC,KAAK;AAAa,aAAO,MAAM,SAAS,OAAO,OAAO,EAAE,CAAC;AAAA,IACzD,KAAK;AAAY,aAAO,MAAM,SAAS,OAAO,OAAO,CAAC,CAAC;AAAA,EACzD;AAEA,QAAM,SAAS,mBAAmB,OAAO,KAAK;AAC9C,MAAI,WAAW,OAAW,QAAO;AAEjC,QAAM,YAAQ,kCAAoB,KAAK;AACvC,MAAI,OAAO;AACT,UAAM,OAAO,MAAM,cAAc,QAAQ,KAAK;AAI9C,QAAI,MAAM,SAAS,YAAY,MAAM,SAAS,QAAQ;AACpD,aAAO,SAAS,MAAM,MAAM,KAAK,OAAO,MAAM,CAAC,EAAE,YAAY;AAAA,IAC/D;AACA,WAAO,MAAM,SAAS,MAAM,MAAM,OAAO,OAAO,MAAM,CAAC,CAAC;AAAA,EAC1D;AAEA,SAAO;AACT;AAUA,SAAS,eAAe,MAAwB;AAC9C,MAAI,OAAO,SAAS,SAAU,YAAO,kCAAoB,IAAI,MAAM;AACnE,MAAI,MAAM,QAAQ,IAAI,EAAG,QAAO,KAAK,KAAK,cAAc;AACxD,MAAI,QAAQ,OAAO,SAAS,YAAY,EAAE,gBAAgB,OAAO;AAC/D,WAAO,OAAO,OAAO,IAA+B,EAAE,KAAK,cAAc;AAAA,EAC3E;AACA,SAAO;AACT;AAUO,SAAS,oBACd,QACA,MAAoC,CAAC,GAClC;AACH,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,CAAC,eAAe,MAAM,EAAG,QAAO;AAGpC,QAAM,SAAuC,EAAE,GAAG,KAAK,KAAK,IAAI,OAAO,oBAAI,KAAK,EAAE;AAElF,QAAM,OAAO,CAAC,SAA2B;AACvC,QAAI,OAAO,SAAS,UAAU;AAC5B,YAAM,UAAM,kCAAoB,IAAI;AACpC,UAAI,CAAC,IAAK,QAAO;AACjB,UAAI,IAAI,SAAS,UAAW,OAAM,IAAI,wBAAwB,IAAI,OAAO,IAAI,UAAU;AACvF,YAAM,WAAW,mBAAmB,IAAI,OAAO,MAAM;AAIrD,UAAI,aAAa,OAAW,OAAM,IAAI,wBAAwB,IAAI,KAAK;AACvE,aAAO;AAAA,IACT;AACA,QAAI,MAAM,QAAQ,IAAI,EAAG,QAAO,KAAK,IAAI,IAAI;AAC7C,QAAI,QAAQ,OAAO,SAAS,UAAU;AAEpC,UAAI,gBAAgB,KAAM,QAAO;AACjC,YAAM,MAA+B,CAAC;AACtC,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,IAA+B,EAAG,KAAI,CAAC,IAAI,KAAK,CAAC;AACrF,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAEA,SAAO,KAAK,MAAM;AACpB;AAaO,SAAS,uBACd,SACA,KAC8B;AAC9B,SAAO;AAAA,IACL;AAAA,IACA,UAAU,SAAS;AAAA,IACnB,QAAQ,SAAS;AAAA,IACjB,OAAO,SAAS;AAAA,EAClB;AACF;;;AChWA,IAAAC,eAAoC;AAa7B,SAAS,sBAAsB,WAAkD;AACtF,MAAI,cAAc,WAAY,QAAO;AACrC,MAAI,cAAc,OAAQ,QAAO;AACjC,MAAI,cAAc,OAAQ,QAAO;AACjC,SAAO;AACT;AAUA,SAAS,eAAe,GAAoB;AAC1C,MAAI,UAAU,KAAK,CAAC,EAAG,QAAO,OAAO,SAAS,IAAI,KAAK,OAAO,CAAC,CAAC,EAAE,QAAQ,CAAC;AAC3E,QAAM,MAAM,sBAAsB,KAAK,CAAC,IACpC,GAAG,CAAC,mBACJ,sDAAsD,KAAK,CAAC,IAC1D,GAAG,EAAE,QAAQ,KAAK,GAAG,CAAC,MACtB;AACN,SAAO,OAAO,SAAS,KAAK,MAAM,GAAG,CAAC;AACxC;AAaA,SAAS,mBAAmB,GAAoB;AAC9C,SAAO,qBAAqB,KAAK,CAAC;AACpC;AAOA,SAAS,iBAAiB,GAAoB;AAC5C,QAAM,IAAI,6CAA6C,KAAK,CAAC;AAC7D,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,OAAO,EAAE,CAAC,CAAC,KAAK,MAAM,OAAO,EAAE,CAAC,CAAC,KAAK,MAAM,OAAO,EAAE,CAAC,KAAK,GAAG,KAAK;AAC5E;AAiBO,SAAS,mCACd,MACA,OACS;AACT,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,IAAI,MAAM,KAAK;AAErB,MAAI,MAAM,GAAI,QAAO;AAErB,UAAI,kCAAoB,KAAK,MAAM,KAAM,QAAO;AAChD,MAAI,SAAS,WAAY,QAAO,CAAC,eAAe,CAAC;AACjD,MAAI,SAAS,OAAQ,QAAO,CAAC,mBAAmB,CAAC;AACjD,SAAO,EAAE,iBAAiB,CAAC,KAAK,eAAe,CAAC;AAClD;;;AC3GO,SAAS,oBAAoB,QAAgB,IAA4B;AAC5E,QAAM,MAAM,IAAI,MAAM,UAAU,EAAE,iBAAiB,MAAM,EAAE;AAK3D,MAAI,OAAO;AACX,MAAI,SAAS;AACb,MAAI,SAAS;AACb,SAAO;AACX;;;AC/CO,IAAM,sBAAN,MAA0B;AAAA,EAU/B,YAAY,QAAsB;AARlC,SAAQ,eAAe,oBAAI,IAAqC;AAChE,SAAQ,eAAe,oBAAI,IAAgC;AAC3D,SAAQ,gBAAgB,oBAAI,IAAgC;AAC5D,SAAQ,iBAAiB,oBAAI,IAA4B;AACzD,SAAQ,kBAAkB,oBAAI,IAAoB;AAClD,SAAQ,kBAAkB,oBAAI,IAAoB;AAClD,SAAQ,kBAAkB,oBAAI,IAAoB;AAGhD,SAAK,SAAS,OAAO,MAAM,EAAE,WAAW,gBAAgB,CAAC;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,YAAoB,QAAuC;AACxE,SAAK,aAAa,IAAI,YAAY,MAAM;AACxC,SAAK,aAAa,IAAI,YAAY,SAAS;AAC3C,SAAK,gBAAgB,IAAI,YAAY,CAAC;AACtC,SAAK,gBAAgB,IAAI,YAAY,CAAC;AACtC,SAAK,gBAAgB,IAAI,YAAY,CAAC;AAEtC,SAAK,OAAO,KAAK,2CAA2C;AAAA,MAC1D,QAAQ;AAAA,MACR,UAAU,OAAO;AAAA,IACnB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,YAAoB,QAAsB;AACxD,UAAM,SAAS,KAAK,aAAa,IAAI,UAAU;AAC/C,QAAI,CAAC,QAAQ;AACX,WAAK,OAAO,KAAK,mDAAmD,EAAE,QAAQ,WAAW,CAAC;AAC1F;AAAA,IACF;AAGA,SAAK,eAAe,UAAU;AAG9B,UAAM,WAAW,YAAY,MAAM;AACjC,WAAK,mBAAmB,YAAY,QAAQ,MAAM,EAAE,MAAM,WAAS;AACjE,aAAK,OAAO,MAAM,kCAAkC;AAAA,UAClD,QAAQ;AAAA,UACR;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAAA,IACH,GAAG,OAAO,QAAQ;AAElB,SAAK,eAAe,IAAI,YAAY,QAAQ;AAC5C,SAAK,OAAO,KAAK,6BAA6B,EAAE,QAAQ,WAAW,CAAC;AAGpE,SAAK,mBAAmB,YAAY,QAAQ,MAAM,EAAE,MAAM,WAAS;AACjE,WAAK,OAAO,MAAM,+BAA+B;AAAA,QAC/C,QAAQ;AAAA,QACR;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,YAA0B;AACvC,UAAM,WAAW,KAAK,eAAe,IAAI,UAAU;AACnD,QAAI,UAAU;AACZ,oBAAc,QAAQ;AACtB,WAAK,eAAe,OAAO,UAAU;AACrC,WAAK,OAAO,KAAK,6BAA6B,EAAE,QAAQ,WAAW,CAAC;AAAA,IACtE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,mBACZ,YACA,QACA,QACe;AACf,UAAM,YAAY,KAAK,IAAI;AAC3B,QAAI,SAA6B;AACjC,QAAI;AACJ,UAAM,SAA6F,CAAC;AAEpG,QAAI;AAEF,UAAI,OAAO,eAAe,OAAQ,OAAe,OAAO,WAAW,MAAM,YAAY;AACnF,cAAM,cAAc,MAAM,KAAK;AAAA,UAC5B,OAAe,OAAO,WAAW,EAAE;AAAA,UACpC,OAAO;AAAA,UACP,8BAA8B,OAAO,OAAO;AAAA,QAC9C;AAEA,YAAI,gBAAgB,SAAU,eAAe,YAAY,WAAW,aAAc;AAChF,mBAAS;AACT,oBAAU,aAAa,WAAW;AAClC,iBAAO,KAAK,EAAE,MAAM,OAAO,aAAa,QAAQ,UAAU,QAAQ,CAAC;AAAA,QACrE,OAAO;AACL,iBAAO,KAAK,EAAE,MAAM,OAAO,aAAa,QAAQ,SAAS,CAAC;AAAA,QAC5D;AAAA,MACF,OAAO;AAEL,eAAO,KAAK,EAAE,MAAM,iBAAiB,QAAQ,SAAS,CAAC;AAAA,MACzD;AAGA,UAAI,WAAW,WAAW;AACxB,aAAK,gBAAgB,IAAI,aAAa,KAAK,gBAAgB,IAAI,UAAU,KAAK,KAAK,CAAC;AACpF,aAAK,gBAAgB,IAAI,YAAY,CAAC;AAGtC,cAAM,gBAAgB,KAAK,aAAa,IAAI,UAAU;AACtD,YAAI,kBAAkB,eAAe,kBAAkB,YAAY;AACjE,gBAAM,eAAe,KAAK,gBAAgB,IAAI,UAAU,KAAK;AAC7D,cAAI,gBAAgB,OAAO,kBAAkB;AAC3C,iBAAK,aAAa,IAAI,YAAY,SAAS;AAC3C,iBAAK,OAAO,KAAK,qCAAqC,EAAE,QAAQ,WAAW,CAAC;AAAA,UAC9E,OAAO;AACL,iBAAK,aAAa,IAAI,YAAY,YAAY;AAAA,UAChD;AAAA,QACF,OAAO;AACL,eAAK,aAAa,IAAI,YAAY,SAAS;AAAA,QAC7C;AAAA,MACF,OAAO;AACL,aAAK,gBAAgB,IAAI,aAAa,KAAK,gBAAgB,IAAI,UAAU,KAAK,KAAK,CAAC;AACpF,aAAK,gBAAgB,IAAI,YAAY,CAAC;AAEtC,cAAM,eAAe,KAAK,gBAAgB,IAAI,UAAU,KAAK;AAC7D,YAAI,gBAAgB,OAAO,kBAAkB;AAC3C,eAAK,aAAa,IAAI,YAAY,WAAW;AAC7C,eAAK,OAAO,KAAK,8BAA8B;AAAA,YAC7C,QAAQ;AAAA,YACR,UAAU;AAAA,UACZ,CAAC;AAGD,cAAI,OAAO,aAAa;AACtB,kBAAM,KAAK,eAAe,YAAY,QAAQ,MAAM;AAAA,UACtD;AAAA,QACF,OAAO;AACL,eAAK,aAAa,IAAI,YAAY,UAAU;AAAA,QAC9C;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,eAAS;AACT,gBAAU,iBAAiB,QAAQ,MAAM,UAAU;AACnD,WAAK,gBAAgB,IAAI,aAAa,KAAK,gBAAgB,IAAI,UAAU,KAAK,KAAK,CAAC;AACpF,WAAK,aAAa,IAAI,YAAY,QAAQ;AAE1C,aAAO,KAAK;AAAA,QACV,MAAM;AAAA,QACN,QAAQ;AAAA,QACR;AAAA,MACF,CAAC;AAED,WAAK,OAAO,MAAM,0BAA0B;AAAA,QAC1C,QAAQ;AAAA,QACR;AAAA,MACF,CAAC;AAAA,IACH;AAGA,UAAM,SAA6B;AAAA,MACjC,QAAQ,KAAK,aAAa,IAAI,UAAU,KAAK;AAAA,MAC7C,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC;AAAA,MACA,SAAS;AAAA,QACP,QAAQ,KAAK,IAAI,IAAI;AAAA,MACvB;AAAA,MACA,QAAQ,OAAO,SAAS,IAAI,SAAS;AAAA,IACvC;AAEA,SAAK,cAAc,IAAI,YAAY,MAAM;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,eACZ,YACA,QACA,QACe;AACf,UAAM,WAAW,KAAK,gBAAgB,IAAI,UAAU,KAAK;AAEzD,QAAI,YAAY,OAAO,oBAAoB;AACzC,WAAK,OAAO,MAAM,2CAA2C;AAAA,QAC3D,QAAQ;AAAA,QACR;AAAA,MACF,CAAC;AACD,WAAK,aAAa,IAAI,YAAY,QAAQ;AAC1C;AAAA,IACF;AAEA,SAAK,gBAAgB,IAAI,YAAY,WAAW,CAAC;AAGjD,UAAM,QAAQ,KAAK,iBAAiB,UAAU,OAAO,cAAc;AAEnE,SAAK,OAAO,KAAK,6BAA6B;AAAA,MAC5C,QAAQ;AAAA,MACR,SAAS,WAAW;AAAA,MACpB;AAAA,IACF,CAAC;AAED,UAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,KAAK,CAAC;AAEvD,QAAI;AAEF,UAAI,OAAO,SAAS;AAClB,cAAM,OAAO,QAAQ;AAAA,MACvB;AAIA,WAAK,OAAO,KAAK,oBAAoB,EAAE,QAAQ,WAAW,CAAC;AAG3D,WAAK,gBAAgB,IAAI,YAAY,CAAC;AACtC,WAAK,gBAAgB,IAAI,YAAY,CAAC;AACtC,WAAK,aAAa,IAAI,YAAY,YAAY;AAAA,IAChD,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,yBAAyB;AAAA,QACzC,QAAQ;AAAA,QACR;AAAA,MACF,CAAC;AACD,WAAK,aAAa,IAAI,YAAY,QAAQ;AAAA,IAC5C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiB,SAAiB,UAAsD;AAC9F,UAAM,YAAY;AAElB,YAAQ,UAAU;AAAA,MAChB,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO,aAAa,UAAU;AAAA,MAChC,KAAK;AACH,eAAO,YAAY,KAAK,IAAI,GAAG,OAAO;AAAA,MACxC;AACE,eAAO;AAAA,IACX;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,YAAoD;AAClE,WAAO,KAAK,aAAa,IAAI,UAAU;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,YAAoD;AAClE,WAAO,KAAK,cAAc,IAAI,UAAU;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,uBAAwD;AACtD,WAAO,IAAI,IAAI,KAAK,YAAY;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,WAAiB;AAEf,eAAW,cAAc,KAAK,eAAe,KAAK,GAAG;AACnD,WAAK,eAAe,UAAU;AAAA,IAChC;AAEA,SAAK,aAAa,MAAM;AACxB,SAAK,aAAa,MAAM;AACxB,SAAK,cAAc,MAAM;AACzB,SAAK,gBAAgB,MAAM;AAC3B,SAAK,gBAAgB,MAAM;AAC3B,SAAK,gBAAgB,MAAM;AAE3B,SAAK,OAAO,KAAK,kCAAkC;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBA,MAAc,iBACZ,OACA,IACA,SACY;AACZ,QAAI;AAEJ,UAAM,iBAAiB,IAAI,QAAe,CAAC,GAAG,WAAW;AACvD,cAAQ,WAAW,MAAM;AACvB,eAAO,IAAI,MAAM,OAAO,CAAC;AAAA,MAC3B,GAAG,EAAE;AAAA,IACP,CAAC;AAED,QAAI;AACF,aAAO,MAAM,QAAQ,KAAK,CAAC,OAAO,cAAc,CAAC;AAAA,IACnD,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF;AACF;;;AC7VA,IAAAC,sBAA2B;AAU3B,IAAM,eAAe,MAAM;AACzB,MAAI,OAAO,WAAW,eAAe,OAAO,YAAY;AACtD,WAAO,OAAO,WAAW;AAAA,EAC3B;AAEA,SAAO,uCAAuC,QAAQ,SAAS,SAAS,GAAG;AACzE,UAAM,IAAI,KAAK,OAAO,IAAI,KAAK;AAC/B,UAAM,IAAI,MAAM,MAAM,IAAK,IAAI,IAAM;AACrC,WAAO,EAAE,SAAS,EAAE;AAAA,EACtB,CAAC;AACH;AAOA,IAAM,qBAAN,MAAyB;AAAA,EAKvB,YAAY,QAAsB;AAHlC,SAAQ,iBAAiB,oBAAI,IAAiC;AAC9D,SAAQ,cAAc,oBAAI,IAAiB;AAGzC,SAAK,SAAS,OAAO,MAAM,EAAE,WAAW,eAAe,CAAC;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UACJ,UACA,SACA,OACA,QACiB;AACjB,UAAM,WAAgC;AAAA,MACpC;AAAA,MACA;AAAA,MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC;AAAA,MACA,UAAU;AAAA,QACR,UAAU,KAAK,kBAAkB,KAAK;AAAA,QACtC,YAAY;AAAA,MACd;AAAA,IACF;AAEA,UAAM,aAAa,aAAa;AAEhC,YAAQ,OAAO,eAAe;AAAA,MAC5B,KAAK;AACH,aAAK,YAAY,IAAI,YAAY,QAAQ;AACzC,aAAK,OAAO,MAAM,yBAAyB,EAAE,UAAU,WAAW,CAAC;AACnE;AAAA,MAEF,KAAK;AAGH,aAAK,YAAY,IAAI,YAAY,QAAQ;AACzC,aAAK,OAAO,MAAM,yCAAyC,EAAE,UAAU,WAAW,CAAC;AACnF;AAAA,MAEF,KAAK;AAGH,aAAK,YAAY,IAAI,YAAY,QAAQ;AACzC,aAAK,OAAO,MAAM,sDAAsD;AAAA,UACtE;AAAA,UACA;AAAA,QACF,CAAC;AACD;AAAA,MAEF,KAAK;AACH,aAAK,OAAO,MAAM,8BAA8B,EAAE,SAAS,CAAC;AAC5D;AAAA,IACJ;AAEA,SAAK,eAAe,IAAI,UAAU,QAAQ;AAC1C,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aACJ,UACA,YAC0C;AAE1C,QAAI;AAEJ,QAAI,YAAY;AACd,iBAAW,KAAK,YAAY,IAAI,UAAU;AAAA,IAC5C,OAAO;AACL,iBAAW,KAAK,eAAe,IAAI,QAAQ;AAAA,IAC7C;AAEA,QAAI,CAAC,UAAU;AACb,WAAK,OAAO,KAAK,2BAA2B,EAAE,UAAU,WAAW,CAAC;AACpE,aAAO;AAAA,IACT;AAGA,QAAI,SAAS,UAAU,UAAU;AAC/B,YAAM,kBAAkB,KAAK,kBAAkB,SAAS,KAAK;AAC7D,UAAI,oBAAoB,SAAS,SAAS,UAAU;AAClD,aAAK,OAAO,MAAM,mDAAmD;AAAA,UACnE;AAAA,UACA,UAAU,SAAS,SAAS;AAAA,UAC5B,QAAQ;AAAA,QACV,CAAC;AACD,eAAO;AAAA,MACT;AAAA,IACF;AAEA,SAAK,OAAO,MAAM,kBAAkB,EAAE,UAAU,SAAS,SAAS,QAAQ,CAAC;AAC3E,WAAO,SAAS;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,UAAwB;AACjC,SAAK,eAAe,OAAO,QAAQ;AAEnC,SAAK,OAAO,MAAM,iBAAiB,EAAE,SAAS,CAAC;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAkB,OAAoC;AAC5D,UAAM,WAAW,KAAK,UAAU,KAAK;AACrC,eAAO,gCAAW,QAAQ,EAAE,OAAO,QAAQ,EAAE,OAAO,KAAK;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA,EAKA,WAAiB;AACf,SAAK,eAAe,MAAM;AAC1B,SAAK,YAAY,MAAM;AACvB,SAAK,OAAO,KAAK,iCAAiC;AAAA,EACpD;AACF;AAOO,IAAM,mBAAN,MAAuB;AAAA,EAO5B,YAAY,QAAsB;AAJlC,SAAQ,gBAAgB,oBAAI,IAAmC;AAC/D,SAAQ,eAAe,oBAAI,IAAiB;AAC5C,SAAQ,eAAe,oBAAI,IAA4B;AAGrD,SAAK,SAAS,OAAO,MAAM,EAAE,WAAW,YAAY,CAAC;AACrD,SAAK,eAAe,IAAI,mBAAmB,MAAM;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,YAAoB,QAAqC;AACtE,QAAI,CAAC,OAAO,SAAS;AACnB,WAAK,OAAO,MAAM,kCAAkC,EAAE,QAAQ,WAAW,CAAC;AAC1E;AAAA,IACF;AAEA,SAAK,cAAc,IAAI,YAAY,MAAM;AACzC,SAAK,OAAO,KAAK,oCAAoC;AAAA,MACnD,QAAQ;AAAA,MACR,eAAe,OAAO;AAAA,MACtB,eAAe,OAAO;AAAA,IACxB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,YAA0B;AACtC,UAAM,SAAS,KAAK,cAAc,IAAI,UAAU;AAChD,QAAI,CAAC,UAAU,CAAC,OAAO,SAAS;AAC9B;AAAA,IACF;AAIA,SAAK,OAAO,KAAK,yBAAyB;AAAA,MACxC,QAAQ;AAAA,MACR,UAAU,OAAO;AAAA,IACnB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,YAA0B;AACrC,UAAM,SAAS,KAAK,aAAa,IAAI,UAAU;AAC/C,QAAI,QAAQ;AAEV,WAAK,aAAa,OAAO,UAAU;AACnC,WAAK,OAAO,KAAK,yBAAyB,EAAE,QAAQ,WAAW,CAAC;AAAA,IAClE;AAGA,UAAM,QAAQ,KAAK,aAAa,IAAI,UAAU;AAC9C,QAAI,OAAO;AACT,mBAAa,KAAK;AAClB,WAAK,aAAa,OAAO,UAAU;AAAA,IACrC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aACJ,YACA,QACA,SACA,gBACA,oBACkB;AAClB,UAAM,SAAS,KAAK,cAAc,IAAI,UAAU;AAChD,QAAI,CAAC,QAAQ;AACX,WAAK,OAAO,KAAK,yCAAyC,EAAE,QAAQ,WAAW,CAAC;AAChF,aAAO;AAAA,IACT;AAEA,SAAK,OAAO,KAAK,uBAAuB,EAAE,QAAQ,WAAW,CAAC;AAE9D,QAAI;AAEF,UAAI,OAAO,cAAc;AACvB,aAAK,OAAO,MAAM,iCAAiC;AAAA,UACjD,QAAQ;AAAA,UACR,OAAO,OAAO;AAAA,QAChB,CAAC;AAAA,MAEH;AAGA,UAAI;AACJ,UAAI,OAAO,iBAAiB,OAAO,kBAAkB,QAAQ;AAC3D,cAAM,QAAQ,eAAe;AAC7B,qBAAa,MAAM,KAAK,aAAa;AAAA,UACnC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,aAAK,OAAO,MAAM,sBAAsB,EAAE,QAAQ,YAAY,WAAW,CAAC;AAAA,MAC5E;AAGA,UAAI,OAAO,SAAS;AAClB,aAAK,OAAO,MAAM,qBAAqB,EAAE,QAAQ,WAAW,CAAC;AAE7D,cAAM,KAAK;AAAA,UACT,OAAO,QAAQ;AAAA,UACf,OAAO;AAAA,UACP;AAAA,QACF;AACA,aAAK,OAAO,MAAM,iCAAiC,EAAE,QAAQ,WAAW,CAAC;AAAA,MAC3E;AAIA,WAAK,OAAO,MAAM,wCAAwC,EAAE,QAAQ,WAAW,CAAC;AAGhF,UAAI,cAAc,OAAO,eAAe;AACtC,cAAM,gBAAgB,MAAM,KAAK,aAAa,aAAa,YAAY,UAAU;AACjF,YAAI,eAAe;AACjB,6BAAmB,aAAa;AAChC,eAAK,OAAO,MAAM,yBAAyB,EAAE,QAAQ,WAAW,CAAC;AAAA,QACnE;AAAA,MACF;AAGA,UAAI,OAAO,aAAa;AACtB,aAAK,OAAO,MAAM,gCAAgC;AAAA,UAChD,QAAQ;AAAA,UACR,OAAO,OAAO;AAAA,QAChB,CAAC;AAAA,MAEH;AAEA,WAAK,OAAO,KAAK,qCAAqC,EAAE,QAAQ,WAAW,CAAC;AAC5E,aAAO;AAAA,IACT,SAAS,OAAO;AACd,WAAK,OAAO,MAAM,qBAAqB;AAAA,QACrC,QAAQ;AAAA,QACR;AAAA,MACF,CAAC;AACD,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBA,MAAc,oBACZ,UACA,SACA,SACY;AACZ,QAAI;AAEJ,UAAM,iBAAiB,IAAI,QAAe,CAAC,GAAG,WAAW;AACvD,cAAQ,WAAW,MAAM;AACvB,eAAO,IAAI,MAAM,OAAO,CAAC;AAAA,MAC3B,GAAG,OAAO;AAAA,IACZ,CAAC;AAED,QAAI;AACF,aAAO,MAAM,QAAQ,KAAK,CAAC,UAAU,cAAc,CAAC;AAAA,IACtD,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,eACE,YACA,UACM;AACN,UAAM,SAAS,KAAK,cAAc,IAAI,UAAU;AAChD,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AAGA,UAAM,gBAAgB,KAAK,aAAa,IAAI,UAAU;AACtD,QAAI,eAAe;AACjB,mBAAa,aAAa;AAAA,IAC5B;AAGA,UAAM,QAAQ,WAAW,MAAM;AAC7B,WAAK,OAAO,MAAM,6CAA6C;AAAA,QAC7D,QAAQ;AAAA,MACV,CAAC;AACD,eAAS,EAAE,MAAM,WAAS;AACxB,aAAK,OAAO,MAAM,2BAA2B;AAAA,UAC3C,QAAQ;AAAA,UACR;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AACD,WAAK,aAAa,OAAO,UAAU;AAAA,IACrC,GAAG,OAAO,aAAa;AAEvB,SAAK,aAAa,IAAI,YAAY,KAAK;AACvC,SAAK,OAAO,MAAM,kCAAkC;AAAA,MAClD,QAAQ;AAAA,MACR,OAAO,OAAO;AAAA,IAChB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAsC;AACpC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,WAAiB;AAEf,eAAW,cAAc,KAAK,aAAa,KAAK,GAAG;AACjD,WAAK,aAAa,UAAU;AAAA,IAC9B;AAGA,eAAW,SAAS,KAAK,aAAa,OAAO,GAAG;AAC9C,mBAAa,KAAK;AAAA,IACpB;AAEA,SAAK,cAAc,MAAM;AACzB,SAAK,aAAa,MAAM;AACxB,SAAK,aAAa,MAAM;AACxB,SAAK,aAAa,SAAS;AAE3B,SAAK,OAAO,KAAK,sCAAsC;AAAA,EACzD;AACF;;;ACvZO,IAAM,yBAAN,MAA6B;AAAA;AAAA;AAAA;AAAA,EAIlC,OAAO,MAAM,YAAqC;AAEhD,UAAM,eAAe,WAAW,QAAQ,MAAM,EAAE;AAGhD,UAAM,QAAQ,aAAa;AAAA,MACzB;AAAA,IACF;AAEA,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,MAAM,6BAA6B,UAAU,EAAE;AAAA,IAC3D;AAEA,WAAO;AAAA,MACL,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAAA,MAC5B,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAAA,MAC5B,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAAA,MAC5B,YAAY,MAAM,CAAC;AAAA,MACnB,OAAO,MAAM,CAAC;AAAA,IAChB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,SAAS,SAAkC;AAChD,QAAI,MAAM,GAAG,QAAQ,KAAK,IAAI,QAAQ,KAAK,IAAI,QAAQ,KAAK;AAC5D,QAAI,QAAQ,YAAY;AACtB,aAAO,IAAI,QAAQ,UAAU;AAAA,IAC/B;AACA,QAAI,QAAQ,OAAO;AACjB,aAAO,IAAI,QAAQ,KAAK;AAAA,IAC1B;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,QAAQ,GAAoB,GAA4B;AAE7D,QAAI,EAAE,UAAU,EAAE,MAAO,QAAO,EAAE,QAAQ,EAAE;AAC5C,QAAI,EAAE,UAAU,EAAE,MAAO,QAAO,EAAE,QAAQ,EAAE;AAC5C,QAAI,EAAE,UAAU,EAAE,MAAO,QAAO,EAAE,QAAQ,EAAE;AAG5C,QAAI,EAAE,cAAc,CAAC,EAAE,WAAY,QAAO;AAC1C,QAAI,CAAC,EAAE,cAAc,EAAE,WAAY,QAAO;AAG1C,QAAI,EAAE,cAAc,EAAE,YAAY;AAChC,aAAO,EAAE,WAAW,cAAc,EAAE,UAAU;AAAA,IAChD;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,UAAU,SAA0B,YAAwC;AACjF,UAAM,gBAAgB;AAGtB,QAAI,kBAAkB,OAAO,kBAAkB,UAAU;AACvD,aAAO;AAAA,IACT;AAGA,QAAI,WAAW,KAAK,aAAa,GAAG;AAClC,YAAM,QAAQ,KAAK,MAAM,aAAa;AACtC,aAAO,KAAK,QAAQ,SAAS,KAAK,MAAM;AAAA,IAC1C;AAGA,QAAI,cAAc,WAAW,GAAG,GAAG;AACjC,YAAM,OAAO,KAAK,MAAM,cAAc,MAAM,CAAC,CAAC;AAC9C,aACE,QAAQ,UAAU,KAAK,SACvB,KAAK,QAAQ,SAAS,IAAI,KAAK;AAAA,IAEnC;AAGA,QAAI,cAAc,WAAW,GAAG,GAAG;AACjC,YAAM,OAAO,KAAK,MAAM,cAAc,MAAM,CAAC,CAAC;AAC9C,aACE,QAAQ,UAAU,KAAK,SACvB,QAAQ,UAAU,KAAK,SACvB,KAAK,QAAQ,SAAS,IAAI,KAAK;AAAA,IAEnC;AAGA,QAAI,cAAc,WAAW,IAAI,GAAG;AAClC,YAAM,OAAO,KAAK,MAAM,cAAc,MAAM,CAAC,CAAC;AAC9C,aAAO,KAAK,QAAQ,SAAS,IAAI,KAAK;AAAA,IACxC;AAGA,QAAI,cAAc,WAAW,GAAG,GAAG;AACjC,YAAM,OAAO,KAAK,MAAM,cAAc,MAAM,CAAC,CAAC;AAC9C,aAAO,KAAK,QAAQ,SAAS,IAAI,IAAI;AAAA,IACvC;AAGA,QAAI,cAAc,WAAW,IAAI,GAAG;AAClC,YAAM,OAAO,KAAK,MAAM,cAAc,MAAM,CAAC,CAAC;AAC9C,aAAO,KAAK,QAAQ,SAAS,IAAI,KAAK;AAAA,IACxC;AAGA,QAAI,cAAc,WAAW,GAAG,GAAG;AACjC,YAAM,OAAO,KAAK,MAAM,cAAc,MAAM,CAAC,CAAC;AAC9C,aAAO,KAAK,QAAQ,SAAS,IAAI,IAAI;AAAA,IACvC;AAGA,UAAM,aAAa,cAAc,MAAM,2BAA2B;AAClE,QAAI,YAAY;AACd,YAAM,MAAM,KAAK,MAAM,WAAW,CAAC,CAAC;AACpC,YAAM,MAAM,KAAK,MAAM,WAAW,CAAC,CAAC;AACpC,aAAO,KAAK,QAAQ,SAAS,GAAG,KAAK,KAAK,KAAK,QAAQ,SAAS,GAAG,KAAK;AAAA,IAC1E;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,sBAAsB,MAAuB,IAAyC;AAC3F,UAAM,MAAM,KAAK,QAAQ,MAAM,EAAE;AAGjC,QAAI,QAAQ,GAAG;AACb,aAAO;AAAA,IACT;AAGA,QAAI,KAAK,UAAU,GAAG,OAAO;AAC3B,aAAO;AAAA,IACT;AAGA,QAAI,KAAK,QAAQ,GAAG,OAAO;AACzB,aAAO;AAAA,IACT;AAGA,QAAI,KAAK,QAAQ,GAAG,OAAO;AACzB,aAAO;AAAA,IACT;AAGA,WAAO;AAAA,EACT;AACF;AAOO,IAAM,qBAAN,MAAyB;AAAA,EAG9B,YAAY,QAAsB;AAChC,SAAK,SAAS,OAAO,MAAM,EAAE,WAAW,qBAAqB,CAAC;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKA,QACE,SACU;AACV,UAAM,QAAQ,oBAAI,IAAsB;AACxC,UAAM,WAAW,oBAAI,IAAoB;AAGzC,eAAW,CAAC,YAAY,UAAU,KAAK,SAAS;AAC9C,UAAI,CAAC,MAAM,IAAI,UAAU,GAAG;AAC1B,cAAM,IAAI,YAAY,CAAC,CAAC;AACxB,iBAAS,IAAI,YAAY,CAAC;AAAA,MAC5B;AAEA,YAAM,OAAO,WAAW,gBAAgB,CAAC;AACzC,iBAAW,OAAO,MAAM;AAEtB,YAAI,CAAC,QAAQ,IAAI,GAAG,GAAG;AACrB,gBAAM,IAAI,MAAM,uBAAuB,UAAU,aAAa,GAAG,EAAE;AAAA,QACrE;AAGA,YAAI,CAAC,MAAM,IAAI,GAAG,GAAG;AACnB,gBAAM,IAAI,KAAK,CAAC,CAAC;AACjB,mBAAS,IAAI,KAAK,CAAC;AAAA,QACrB;AACA,cAAM,IAAI,GAAG,EAAG,KAAK,UAAU;AAC/B,iBAAS,IAAI,aAAa,SAAS,IAAI,UAAU,KAAK,KAAK,CAAC;AAAA,MAC9D;AAAA,IACF;AAGA,UAAM,QAAkB,CAAC;AACzB,UAAM,SAAmB,CAAC;AAG1B,eAAW,CAAC,MAAM,MAAM,KAAK,UAAU;AACrC,UAAI,WAAW,GAAG;AAChB,cAAM,KAAK,IAAI;AAAA,MACjB;AAAA,IACF;AAEA,WAAO,MAAM,SAAS,GAAG;AACvB,YAAM,OAAO,MAAM,MAAM;AACzB,aAAO,KAAK,IAAI;AAGhB,YAAM,aAAa,MAAM,IAAI,IAAI,KAAK,CAAC;AACvC,iBAAW,aAAa,YAAY;AAClC,cAAM,aAAa,SAAS,IAAI,SAAS,KAAK,KAAK;AACnD,iBAAS,IAAI,WAAW,SAAS;AAEjC,YAAI,cAAc,GAAG;AACnB,gBAAM,KAAK,SAAS;AAAA,QACtB;AAAA,MACF;AAAA,IACF;AAGA,QAAI,OAAO,WAAW,QAAQ,MAAM;AAClC,YAAM,YAAY,MAAM,KAAK,QAAQ,KAAK,CAAC,EAAE,OAAO,OAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAC5E,WAAK,OAAO,MAAM,gCAAgC,EAAE,UAAU,CAAC;AAC/D,YAAM,IAAI,MAAM,uCAAuC,UAAU,KAAK,IAAI,CAAC,EAAE;AAAA,IAC/E;AAEA,SAAK,OAAO,MAAM,yBAAyB,EAAE,OAAO,OAAO,CAAC;AAC5D,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,gBACE,SACsB;AACtB,UAAM,YAAkC,CAAC;AACzC,UAAM,sBAAsB,oBAAI,IAA4C;AAG5E,eAAW,CAAC,YAAY,UAAU,KAAK,SAAS;AAC9C,UAAI,CAAC,WAAW,aAAc;AAE9B,iBAAW,CAAC,SAAS,UAAU,KAAK,OAAO,QAAQ,WAAW,YAAY,GAAG;AAC3E,YAAI,CAAC,oBAAoB,IAAI,OAAO,GAAG;AACrC,8BAAoB,IAAI,SAAS,oBAAI,IAAI,CAAC;AAAA,QAC5C;AACA,4BAAoB,IAAI,OAAO,EAAG,IAAI,YAAY,UAAU;AAAA,MAC9D;AAAA,IACF;AAGA,eAAW,CAAC,SAAS,YAAY,KAAK,qBAAqB;AACzD,YAAM,UAAU,QAAQ,IAAI,OAAO;AACnC,UAAI,CAAC,QAAS;AAEd,YAAM,aAAa,uBAAuB,MAAM,QAAQ,OAAO;AAC/D,YAAM,cAA4D,CAAC;AAEnE,iBAAW,CAAC,iBAAiB,UAAU,KAAK,cAAc;AACxD,YAAI,CAAC,uBAAuB,UAAU,YAAY,UAAU,GAAG;AAC7D,sBAAY,KAAK;AAAA,YACf,UAAU;AAAA,YACV,SAAS;AAAA,UACX,CAAC;AAAA,QACH;AAAA,MACF;AAEA,UAAI,YAAY,SAAS,GAAG;AAC1B,kBAAU,KAAK;AAAA,UACb,MAAM;AAAA,UACN,UAAU;AAAA,UACV,aAAa,wBAAwB,OAAO,cAAc,YAAY,MAAM;AAAA,UAC5E,SAAS;AAAA,YACP,EAAE,UAAU,SAAS,SAAS,QAAQ,QAAQ;AAAA,YAC9C,GAAG;AAAA,UACL;AAAA,UACA,aAAa,CAAC;AAAA,YACZ,UAAU;AAAA,YACV,aAAa,WAAW,OAAO;AAAA,YAC/B,eAAe,CAAC,OAAO;AAAA,YACvB,WAAW;AAAA,UACb,CAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF;AAGA,QAAI;AACF,WAAK,QAAQ,IAAI;AAAA,QACf,MAAM,KAAK,QAAQ,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,IAAI,MAAM;AAAA,UAClD;AAAA,UACA,EAAE,SAAS,KAAK,SAAS,cAAc,KAAK,eAAe,OAAO,KAAK,KAAK,YAAY,IAAI,CAAC,EAAE;AAAA,QACjG,CAAC;AAAA,MACH,CAAC;AAAA,IACH,SAAS,OAAO;AACd,UAAI,iBAAiB,SAAS,MAAM,QAAQ,SAAS,qBAAqB,GAAG;AAC3E,kBAAU,KAAK;AAAA,UACb,MAAM;AAAA,UACN,UAAU;AAAA,UACV,aAAa,MAAM;AAAA,UACnB,SAAS,CAAC;AAAA;AAAA,UACV,aAAa,CAAC;AAAA,YACZ,UAAU;AAAA,YACV,aAAa;AAAA,YACb,WAAW;AAAA,UACb,CAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,gBACE,mBACA,aACoB;AAEpB,UAAM,WAAW,kBACd,IAAI,QAAM,EAAE,KAAK,GAAG,QAAQ,uBAAuB,MAAM,CAAC,EAAE,EAAE,EAC9D,KAAK,CAAC,GAAG,MAAM,CAAC,uBAAuB,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;AAGrE,eAAW,WAAW,UAAU;AAC9B,YAAM,eAAe,YAAY;AAAA,QAAM,gBACrC,uBAAuB,UAAU,QAAQ,QAAQ,UAAU;AAAA,MAC7D;AAEA,UAAI,cAAc;AAChB,eAAO,QAAQ;AAAA,MACjB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,cAA8C;AACtD,QAAI;AACF,YAAM,UAAU,IAAI;AAAA,QAClB,MAAM,KAAK,aAAa,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,IAAI,MAAM;AAAA,UACvD;AAAA,UACA,EAAE,cAAc,KAAK;AAAA,QACvB,CAAC;AAAA,MACH;AACA,WAAK,QAAQ,OAAO;AACpB,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;AChVO,IAAM,oBAAN,MAAwB;AAAA,EAI7B,YAAY,QAAsB;AAFlC,SAAQ,WAAwC,oBAAI,IAAI;AAGtD,SAAK,SAAS,OAAO,MAAM,EAAE,WAAW,oBAAoB,CAAC;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,WAAmB,YAA4B;AACtD,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,eAAW,MAAM,YAAY;AAC3B,UAAI,KAAK,SAAS,IAAI,EAAE,GAAG;AACzB,cAAM,WAAW,KAAK,SAAS,IAAI,EAAE;AACrC,YAAI,SAAS,cAAc,WAAW;AACpC,eAAK,OAAO,KAAK,+BAA+B,EAAE,WAAW,IAAI,UAAU,SAAS,WAAW,UAAU,UAAU,CAAC;AAAA,QACtH;AAAA,MACF;AACA,WAAK,SAAS,IAAI,IAAI,EAAE,WAAW,IAAI,WAAW,cAAc,IAAI,CAAC;AACrE,WAAK,OAAO,MAAM,wBAAwB,EAAE,WAAW,IAAI,UAAU,CAAC;AAAA,IACxE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,WAA6B;AACtC,UAAM,UAAoB,CAAC;AAC3B,eAAW,CAAC,IAAI,KAAK,KAAK,KAAK,UAAU;AACvC,UAAI,MAAM,cAAc,WAAW;AACjC,aAAK,SAAS,OAAO,EAAE;AACvB,gBAAQ,KAAK,EAAE;AAAA,MACjB;AAAA,IACF;AACA,SAAK,OAAO,MAAM,2BAA2B,EAAE,WAAW,OAAO,QAAQ,OAAO,CAAC;AACjF,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,WAAmB,YAA4C;AAC/E,UAAM,YAAiC,CAAC;AACxC,UAAM,cAAsC,CAAC;AAE7C,eAAW,MAAM,YAAY;AAC3B,YAAM,WAAW,KAAK,SAAS,IAAI,EAAE;AACrC,UAAI,YAAY,SAAS,cAAc,WAAW;AAChD,cAAM,aAAa,KAAK,mBAAmB,IAAI,SAAS;AACxD,kBAAU,KAAK;AAAA,UACb,WAAW;AAAA,UACX,mBAAmB,SAAS;AAAA,UAC5B,mBAAmB;AAAA,UACnB;AAAA,QACF,CAAC;AACD,oBAAY,EAAE,IAAI;AAAA,MACpB;AAAA,IACF;AAEA,WAAO;AAAA,MACL,WAAW,UAAU,WAAW;AAAA,MAChC;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,QAA2C;AAC3D,UAAM,aAAuB,CAAC;AAC9B,UAAM,aAAa;AAAA,MACjB;AAAA,MAAW;AAAA,MAAS;AAAA,MAAS;AAAA,MAAS;AAAA,MACtC;AAAA,MAAQ;AAAA,MAAc;AAAA,MAAW;AAAA,MAAW;AAAA,IAC9C;AAEA,eAAW,YAAY,YAAY;AACjC,YAAM,QAAQ,OAAO,QAAQ;AAC7B,UAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,mBAAW,QAAQ,OAAO;AACxB,gBAAM,OAAQ,MAAkC;AAChD,cAAI,OAAO,SAAS,UAAU;AAC5B,uBAAW,KAAK,GAAG,QAAQ,IAAI,IAAI,EAAE;AAAA,UACvC;AAAA,QACF;AAAA,MACF,WAAW,SAAS,OAAO,UAAU,UAAU;AAC7C,mBAAW,OAAO,OAAO,KAAK,KAAe,GAAG;AAC9C,qBAAW,KAAK,GAAG,QAAQ,IAAI,GAAG,EAAE;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,cAAmD;AACjD,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAqB,WAA6B;AAChD,UAAM,aAAuB,CAAC;AAC9B,eAAW,CAAC,IAAI,KAAK,KAAK,KAAK,UAAU;AACvC,UAAI,MAAM,cAAc,WAAW;AACjC,mBAAW,KAAK,EAAE;AAAA,MACpB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAmB,IAAY,WAA2B;AAEhE,UAAM,YAAY,UACf,QAAQ,aAAa,EAAE,EACvB,QAAQ,YAAY,EAAE,EACtB,QAAQ,MAAM,GAAG;AAEpB,UAAM,QAAQ,GAAG,MAAM,GAAG;AAC1B,QAAI,MAAM,UAAU,GAAG;AAErB,aAAO,GAAG,MAAM,CAAC,CAAC,IAAI,SAAS,IAAI,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC;AAAA,IAC7D;AACA,WAAO,GAAG,SAAS,IAAI,EAAE;AAAA,EAC3B;AACF;;;AlDtFA,uBAAwC;","names":["nodePath","import_system","cryptoSign","cryptoVerify","ServiceLifecycle","import_system","nodePath","import_node_crypto","import_security","safeJsonParse","ymd","import_node_crypto","import_system","import_data","import_data","import_node_crypto"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/plugin-order.ts","../src/hook-dispatch.ts","../src/kernel-base.ts","../src/logger.ts","../src/kernel.ts","../src/security/plugin-artifact-signature.ts","../src/service-not-registered.ts","../src/plugin-loader.ts","../src/utils/env.ts","../src/fallbacks/memory-cache.ts","../src/fallbacks/memory-queue.ts","../src/fallbacks/memory-i18n.ts","../src/metadata-service-contract.ts","../src/fallbacks/memory-metadata.ts","../src/fallbacks/memory-job.ts","../src/fallbacks/authored-translation-sync.ts","../src/fallbacks/index.ts","../src/plugin-registration.ts","../src/timeout-guard.ts","../src/artifact-packages.ts","../src/lite-kernel.ts","../src/qa/index.ts","../src/qa/runner.ts","../src/qa/http-adapter.ts","../src/security/plugin-signature-verifier.ts","../src/security/plugin-artifact-integrity.ts","../src/security/plugin-permission-enforcer.ts","../src/security/permission-manager.ts","../src/security/sandbox-runtime.ts","../src/security/security-scanner.ts","../src/security/api-key.ts","../src/security/authz-store-unavailable.ts","../src/security/resolve-authz-context.ts","../src/security/grant-validity.ts","../src/security/authz-cache-posture.ts","../src/security/resolve-user-grants-cache.ts","../src/security/platform-admin.ts","../src/security/posture-ladder.ts","../src/security/row-active.ts","../src/security/assemble-execution-context.ts","../src/security/auth-gate.ts","../src/security/anonymous-deny.ts","../src/security/admin-standing-surface.ts","../src/security/audience-binding-suggestion-status.ts","../src/security/operation-private-keys.ts","../src/security/authz-invalidation-channel.ts","../src/utils/datetime.ts","../src/utils/bulk-write.ts","../src/utils/internal-write-response.ts","../src/utils/migration-journal.ts","../src/utils/filter-tokens.ts","../src/utils/temporal-comparand.ts","../src/utils/metadata-activation-store.ts","../src/utils/record-not-found.ts","../src/health-monitor.ts","../src/hot-reload.ts","../src/dependency-resolver.ts","../src/namespace-resolver.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * @objectstack/core\n * \n * Core runtime for ObjectStack microkernel architecture.\n * Provides plugin system, dependency injection, and lifecycle management.\n */\n\nexport * from './kernel-base.js';\nexport * from './kernel.js';\nexport * from './plugin-order.js';\n// ADR-0130 D4/D5 — the ONE reader of a release artifact's `packages[]`, and the\n// ONE place it is ordered. It lives here rather than beside a reader because it\n// has two of them in packages that cannot import each other (`@objectstack/\n// objectql`'s load path and `@objectstack/metadata`'s artifact door), and\n// because the ordering it performs is `resolvePluginOrder` directly above.\n// `@objectstack/objectql` re-exports it, so its published surface is unchanged.\nexport * from './artifact-packages.js';\nexport * from './lite-kernel.js';\nexport * from './types.js';\nexport * from './logger.js';\nexport * from './plugin-loader.js';\n\n// [#13905] The async service-resolution discriminator — the two symbols a\n// CONSUMER needs to tell \"nothing ever registered this service\" from \"the\n// service IS registered and could not be built\", now that\n// `Kernel.getServiceAsync` no longer answers both with one bare `Error`.\n// Named rather than `export *` on purpose: the construction site is\n// `PluginLoader.getService` alone, so the factory stays package-internal and\n// the published increment is exactly this predicate and its code.\nexport { SERVICE_NOT_REGISTERED_CODE, isServiceNotRegisteredError } from './service-not-registered.js';\n// `./api-registry.js` + `./api-registry-plugin.js` were RETIRED in #4939\n// (ADR-0049 enforce-or-remove). `createApiRegistryPlugin()` registered an\n// `api-registry` service that only `packages/core/examples/` ever composed —\n// no runtime, CLI or example app mounted it, and a real 47-plugin boot had no\n// such service — so the ~500-line `ApiRegistry` and the whole\n// `ApiEndpointRegistration` schema family it served were zero-execution.\n// Declarative endpoints have ONE shape now: `ApiEndpointSchema`\n// (`@objectstack/spec/api`), whose executor is tracked by #5040.\nexport * as QA from './qa/index.js';\n\n// Export security utilities\nexport * from './security/index.js';\n\n// Export environment utilities\nexport * from './utils/env.js';\n\n// Export timezone-aware calendar utilities (ADR-0053 Phase 2)\nexport * from './utils/datetime.js';\n\n// Export the shared batched-write helper (framework#2678)\nexport * from './utils/bulk-write.js';\n\n// Export the shared write-response `internal: true` strip (#7823, #8497) — the\n// ONE helper every write mouth that answers an external caller runs its\n// records through. It lives here, on the floor all three transports share,\n// because the mouths are not all on the protocol class: `rest` and `mcp` both\n// reach the engine directly and neither depends on `@objectstack/metadata-\n// protocol` (which re-exports these two names unchanged).\nexport * from './utils/internal-write-response.js';\n\n// Export the migration-journal runner (ADR-0119 D2, #4617) — chunk-atomic\n// migrations with durable recovery, plus the shared `engineCanRollBack` gate\n// that `@objectstack/metadata-protocol`'s atomic `batchData` also uses.\nexport * from './utils/migration-journal.js';\n\n// Export the runtime filter-placeholder resolver (framework#3582)\nexport * from './utils/filter-tokens.js';\n\n// [#8690] Can a temporal column's storage rule read this comparand? The VALUE\n// half of the field-typed judgement behind the engine's temporal-comparand door\n// and the analytics raw-SQL decline — one rule, two packages that do not depend\n// on each other.\nexport * from './utils/temporal-comparand.js';\n\n// [#12350 / ADR-0126 §4] THE activation-ledger row contract, parameterized by\n// `metadata_type`. Same reason as the two entries above: its consumers —\n// `@objectstack/objectql` (packaged actions) and\n// `@objectstack/service-automation` (packaged flows) — cannot import each\n// other in either direction, and this is the package both already depend on.\n// The ledger's OBJECT stays declared in `@objectstack/platform-objects`; only\n// the row contract lives here, reached by table NAME.\nexport * from './utils/metadata-activation-store.js';\n\n// Export the shared single-record 404 (#4435/#5138, moved down here in #7867) —\n// the one `RECORD_NOT_FOUND` envelope `protocol.updateData`/`deleteData`,\n// `callData`'s ObjectQL fallback and the engine's own by-id write gate answer\n// with. `@objectstack/metadata-protocol` re-exports it from its original home.\nexport * from './utils/record-not-found.js';\n\n// Export in-memory fallbacks for core-criticality services\nexport * from './fallbacks/index.js';\n\n// [#7378] The IMetadataService register/read argument contract (three-cell\n// maintainer ruling, 2026-08-12) — shared by every shipped implementation so\n// the refusals and the canonical type fold have one home instead of three.\nexport * from './metadata-service-contract.js';\n\n// Export Phase 2 components - Advanced lifecycle management\nexport * from './health-monitor.js';\nexport * from './hot-reload.js';\nexport * from './dependency-resolver.js';\n\n// Export Phase 3 components - Package lifecycle management\nexport * from './namespace-resolver.js';\n\n// Re-export contracts from @objectstack/spec for backward compatibility\nexport type { \n Logger,\n IHttpServer,\n IHttpRequest,\n IHttpResponse,\n RouteHandler,\n Middleware,\n HttpResponseObservation,\n HttpResponseObserver,\n IDataEngine,\n IObjectQLEngine,\n EngineSchemaRegistryView,\n EngineTransactionOptions,\n EngineTransactionInfo,\n IDataDriver,\n} from '@objectstack/spec/contracts';\n// The reserved route label for unrouted requests on the `afterResponse`\n// observation seam (#9835) — a VALUE, so it rides beside the type block above.\nexport { UNMATCHED_ROUTE_PATTERN } from '@objectstack/spec/contracts';\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Plugin ordering + init-service contract (ADR-0116, #4131).\n *\n * The kernel resolves BOTH init and start order from the plugin dependency\n * graph, so `kernel.use()` registration order proves nothing. Twice a plugin\n * relied on list position anyway and shipped a boot that dies inside init —\n * the first cut of DefaultDatasourcePlugin (started after boot schema-sync;\n * server with no tables) and AppPlugin (#4085: `manifest` grabbed in init\n * before ObjectQLPlugin registered it). Both times the fix existed only as a\n * convention: put the plugin in the right slot, write a comment. This module\n * is the enforced form of that contract, shared by ObjectKernel and\n * LiteKernel so there is exactly one ordering semantic:\n *\n * - `dependencies` — hard: hoisted ahead, missing ⇒ boot error (unchanged).\n * - `optionalDependencies` — order-if-present: hoisted ahead when composed,\n * silently skipped when absent. For plugins that DEGRADE without the\n * dependency but must never init before it (AppPlugin on an engine-less\n * metadata-only kernel).\n * - `requiresServices` — services a plugin resolves SYNCHRONOUSLY during\n * `init()`. Validated before Phase 1 (provable misordering ⇒ named error\n * instead of a crash inside init) and again immediately before each init\n * (authoritative: the service is either registered by now or init dies).\n * - `providesServices` — services a plugin's `init()` UNCONDITIONALLY\n * registers. Powers the pre-Phase-1 check and the named diagnostics.\n * Declare only unconditional registrations: a conditional service (e.g.\n * one gated behind an option) would indict this plugin for orderings it\n * cannot actually satisfy.\n *\n * Declaring is NOT voluntary (#4471). Everything above can only enforce what\n * a plugin declares — a plugin that resolves `getService('X')` during init()\n * and declares nothing was invisible to all of it, failing only under\n * unlucky composition orders (#4085, and #4420 at data-consistency cost).\n * `scripts/check-init-service-contract.mjs` (CI: `check:init-service-contract`)\n * closes that gap: it walks every plugin's init() call graph and errors on\n * any init-reachable getService of a workspace-provided service that no\n * declaration covers. Best-effort tolerance is declared IN the plugin via\n * `optionalDependencies`, never exempted in the checker.\n */\n\n/**\n * The ordering-relevant surface of a kernel plugin. Structural on purpose:\n * ObjectKernel sorts `PluginMetadata`, LiteKernel sorts `Plugin`, and both\n * satisfy this shape.\n */\nexport interface OrderablePlugin {\n name: string;\n /** Hard dependencies — hoisted ahead; missing ⇒ boot error. */\n dependencies?: string[];\n /** Soft dependencies — hoisted ahead when composed, skipped when absent. */\n optionalDependencies?: string[];\n /** Services resolved synchronously during init(). */\n requiresServices?: string[];\n /** Services init() unconditionally registers. */\n providesServices?: string[];\n}\n\n/**\n * Topologically order plugins: every plugin's `dependencies` (throw when\n * missing) and `optionalDependencies` (skip when missing) init before it.\n * Insertion order is preserved for plugins with no edges between them.\n * Cycles through either edge kind throw — an optional dependency is a real\n * edge whenever both sides are composed.\n */\nexport function resolvePluginOrder<P extends OrderablePlugin>(plugins: Map<string, P>): P[] {\n const resolved: P[] = [];\n const visited = new Set<string>();\n const visiting = new Set<string>();\n\n const visit = (pluginName: string) => {\n if (visited.has(pluginName)) return;\n\n if (visiting.has(pluginName)) {\n throw new Error(`[Kernel] Circular dependency detected: ${pluginName}`);\n }\n\n const plugin = plugins.get(pluginName);\n if (!plugin) {\n throw new Error(`[Kernel] Plugin '${pluginName}' not found`);\n }\n\n visiting.add(pluginName);\n\n for (const dep of plugin.dependencies ?? []) {\n if (!plugins.has(dep)) {\n throw new Error(\n `[Kernel] Dependency '${dep}' not found for plugin '${pluginName}'`\n );\n }\n visit(dep);\n }\n for (const dep of plugin.optionalDependencies ?? []) {\n if (plugins.has(dep)) visit(dep);\n }\n\n visiting.delete(pluginName);\n visited.add(pluginName);\n resolved.push(plugin);\n };\n\n for (const pluginName of plugins.keys()) {\n visit(pluginName);\n }\n\n return resolved;\n}\n\n/**\n * Pre-Phase-1 check: walk the resolved order and prove no plugin requires a\n * service whose only declared provider initializes AFTER it. A violation is\n * the exact #4085 class — misplaced composition — reported as a named,\n * structural boot error BEFORE any init side effects, instead of a bare\n * \"Service not found\" thrown from inside the victim's init.\n *\n * Deliberately does NOT fail when a required service has no declared provider\n * and is not yet registered: an earlier plugin may register it without\n * declaring `providesServices`. That case is settled authoritatively by\n * {@link assertInitServiceRequirements} immediately before the requiring\n * plugin's init runs.\n */\nexport function validateInitServiceContract<P extends OrderablePlugin>(\n ordered: P[],\n isServiceRegistered: (name: string) => boolean,\n): void {\n const providerSlot = new Map<string, { plugin: string; slot: number }>();\n ordered.forEach((plugin, slot) => {\n for (const service of plugin.providesServices ?? []) {\n if (!providerSlot.has(service)) {\n providerSlot.set(service, { plugin: plugin.name, slot });\n }\n }\n });\n\n const violations: string[] = [];\n ordered.forEach((plugin, slot) => {\n for (const service of plugin.requiresServices ?? []) {\n if (isServiceRegistered(service)) continue;\n const provider = providerSlot.get(service);\n if (provider && provider.slot > slot) {\n violations.push(\n `'${plugin.name}' requires service '${service}' during init, but '${service}' is ` +\n `provided by '${provider.plugin}', which initializes later (slot ${provider.slot} vs ${slot}). ` +\n `Registration order is not a contract — declare '${provider.plugin}' in ` +\n `'${plugin.name}'.dependencies (hard) or .optionalDependencies (order-if-present) ` +\n `so the kernel hoists it.`\n );\n }\n }\n });\n\n if (violations.length > 0) {\n throw new Error(\n `[Kernel] Plugin ordering contract violated (#4131):\\n - ${violations.join('\\n - ')}`\n );\n }\n}\n\n/**\n * Diagnosis suffix for a getService miss that happens WHILE a plugin's\n * init() is running: names the initializing plugin and — when a composed\n * plugin declares the service — the provider and the directive to declare\n * the ordering. Returns '' when no plugin is initializing, so non-boot\n * error messages stay byte-identical. Shared by both kernels.\n */\nexport function describeInitOrderFault(\n currentlyInitializing: string | undefined,\n plugins: Iterable<OrderablePlugin>,\n serviceName: string,\n): string {\n if (!currentlyInitializing) return '';\n let providerHint = '';\n for (const plugin of plugins) {\n if (plugin.providesServices?.includes(serviceName)) {\n providerHint = ` '${serviceName}' is provided by composed plugin '${plugin.name}', which has ` +\n `not initialized yet — declare it in the requiring plugin's dependencies/optionalDependencies.`;\n break;\n }\n }\n return ` (while plugin '${currentlyInitializing}' was initializing — a composition/` +\n `ordering fault, #4131.${providerHint})`;\n}\n\n/**\n * Just-before-init check: every service in `requiresServices` must be\n * registered at the moment the plugin's init() is about to run. At this\n * point the verdict is authoritative — Phase 1 runs sequentially, so a\n * service absent now is absent for this init, and the init would die on a\n * bare \"Service not found\" anyway. This turns that crash into a named\n * composition error.\n */\nexport function assertInitServiceRequirements(\n plugin: OrderablePlugin,\n isServiceRegistered: (name: string) => boolean,\n): void {\n for (const service of plugin.requiresServices ?? []) {\n if (isServiceRegistered(service)) continue;\n throw new Error(\n `[Kernel] Plugin '${plugin.name}' requires service '${service}' at init, but no such ` +\n `service is registered at this point of the boot. No composed plugin that initializes ` +\n `earlier provides it — compose a provider (and, if it initializes later without declaring ` +\n `'${service}' in providesServices, order it ahead via this plugin's dependencies/` +\n `optionalDependencies) (#4131).`\n );\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Shared lifecycle-hook dispatchers (#5282).\n *\n * ## Why this module exists\n *\n * `ObjectKernel` does not extend `ObjectKernelBase` — it is a standalone\n * production kernel with its own `plugins`/`services`/`hooks`/`state`/`logger`\n * fields, and only `LiteKernel` extends the base. Making it inherit was\n * considered and **rejected** (#5282, option A: an inheritance refactor of a\n * 800-line production kernel, risk out of proportion to the benefit). What was\n * ruled instead is this module (option B): the two *dispatch flavours* become\n * module-level functions both kernels call, so a change to how a hook is\n * dispatched is written once and lands on both kernels.\n *\n * Before this, dispatch existed twice with no shared code path — the base's\n * `triggerHook` / `triggerHookOrThrow` / `context.trigger` on one side, and\n * `ObjectKernel`'s private `triggerShutdownHookIsolating` / `context.trigger`\n * on the other. The two isolating loops printed the same log line **because\n * someone typed it twice**. That hand-mirroring is the structural seam three\n * consecutive bugs grew on, every one of them the same shape — one hook name\n * meaning opposite things on the two kernels:\n *\n * - #5170 — `kernel:ready`: LiteKernel swallowed a throwing handler while\n * ObjectKernel failed the boot.\n * - #5257 — `kernel:bootstrapped` / `kernel:listening`: same, and the ugliest\n * specimen — `HonoServerPlugin` awaits `server.listen(port)` inside\n * `kernel:listening`, so on LiteKernel a failed listen was swallowed and\n * the process printed \"✅ Bootstrap complete\" with nothing listening.\n * - #5274 — `kernel:shutdown`, in the opposite direction: ObjectKernel's\n * propagating dispatch let one bad handler skip every `destroy()` and\n * `process.exit(1)`.\n *\n * The **storage** is deliberately still two maps (out of the ruling's scope);\n * only the dispatch is shared. The paired-pin gate\n * (`scripts/check-kernel-hook-pairs.mjs`, option C of the same ruling) covers\n * the residue: every `kernel:*` hook dispatched in `packages/core/src` must\n * carry a named pin in BOTH `kernel.test.ts` and `lite-kernel.test.ts`, so a\n * fifth lifecycle hook cannot arrive paired on one kernel only.\n *\n * ## Choosing a flavour\n *\n * The choice is per hook, not per kernel, and it is a judgement recorded at\n * each dispatch site:\n *\n * - **Boot path** (`kernel:ready`, `kernel:bootstrapped`, `kernel:listening`)\n * ⇒ {@link dispatchHookPropagating}. Everything dispatched before\n * \"✅ Bootstrap complete\" is a precondition of that claim; swallowing a\n * throw there does not rescue the boot, it only hides the failure behind a\n * process reporting success.\n * - **Teardown path** (`kernel:shutdown`) ⇒ {@link dispatchHookIsolating}.\n * There is no \"refuse to proceed\" left to buy: what is queued behind a\n * failing handler is the rest of the cleanup — the other subscribers, then\n * each plugin's `destroy()` — which is what flushes buffers, closes\n * connections and releases locks.\n *\n * ⛔ Internal module: not exported from `packages/core/src/index.ts`. These are\n * kernel internals, not a public dispatch API.\n */\n\n/** A registered lifecycle-hook handler, exactly as `PluginContext.hook` stores it. */\nexport type HookHandler = (...args: any[]) => void | Promise<void>;\n\n/**\n * The slice of the logger contract a dispatcher uses. Structural on purpose:\n * both `ObjectLogger` (what `ObjectKernel` holds) and the spec `Logger`\n * contract (what `ObjectKernelBase` holds) satisfy it without either kernel\n * importing the other's logger type.\n */\nexport interface HookDispatchLogger {\n debug(message: string, meta?: Record<string, any>): void;\n error(message: string, error?: Error, meta?: Record<string, any>): void;\n}\n\n/**\n * The trace line every *logged* dispatch emits before running handlers.\n *\n * Kept in one place because it is asserted verbatim on both kernels. Note the\n * handler count is read here, before the loop — the same instant both\n * hand-written copies read it.\n */\nfunction traceDispatch(name: string, handlers: readonly HookHandler[], logger: HookDispatchLogger): void {\n logger.debug(`Triggering hook: ${name}`, {\n hook: name,\n handlerCount: handlers.length,\n });\n}\n\n/**\n * Dispatch a hook ISOLATING failures: a handler that throws is logged as\n * `Hook handler failed: <name>` and the remaining handlers still run.\n *\n * Used by `ObjectKernelBase.triggerHook` and by `ObjectKernel`'s own\n * `kernel:shutdown` dispatch — the two sites that until #5282 were separate\n * hand-written loops printing the same line.\n *\n * ⛔ Wrong dispatcher for anything on the BOOT path — see the module docs and\n * {@link dispatchHookPropagating}.\n *\n * `handlers` is taken by reference, never copied: a handler that subscribes\n * another handler to the same hook mid-dispatch is picked up by this loop, and\n * that has always been true of both originals.\n *\n * @param name - Hook name\n * @param handlers - The hook's registered handlers, in registration order\n * @param logger - Receives the dispatch trace and one error line per failure\n * @param args - Arguments to pass to each handler\n */\nexport async function dispatchHookIsolating(\n name: string,\n handlers: readonly HookHandler[],\n logger: HookDispatchLogger,\n args: readonly any[] = [],\n): Promise<void> {\n traceDispatch(name, handlers, logger);\n\n for (const handler of handlers) {\n try {\n await handler(...args);\n } catch (error) {\n logger.error(`Hook handler failed: ${name}`, error as Error);\n // Continue with other handlers even if one fails\n }\n }\n}\n\n/**\n * Dispatch a hook PROPAGATING the first failure: the remaining handlers do not\n * run and the original error reaches the caller unwrapped.\n *\n * Used by `ObjectKernelBase.triggerHookOrThrow` (the boot-path hooks on\n * `LiteKernel`) and by BOTH kernels' `PluginContext.trigger`.\n *\n * `logger` is optional, and its absence is the one behavioural difference\n * between those callers: `context.trigger` has never emitted the\n * `Triggering hook: <name>` trace, so it passes no logger. Handing it one would\n * add a debug line to a path that has never had one — a behaviour change, and\n * #5282 preserves every call path's semantics exactly.\n *\n * @param name - Hook name\n * @param handlers - The hook's registered handlers, in registration order\n * @param logger - When given, receives the dispatch trace; omit to stay silent\n * @param args - Arguments to pass to each handler\n */\nexport async function dispatchHookPropagating(\n name: string,\n handlers: readonly HookHandler[],\n logger: HookDispatchLogger | undefined,\n args: readonly any[] = [],\n): Promise<void> {\n if (logger) traceDispatch(name, handlers, logger);\n\n for (const handler of handlers) {\n await handler(...args);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from './types.js';\nimport type { Logger } from '@objectstack/spec/contracts';\nimport type { IServiceRegistry } from '@objectstack/spec/contracts';\nimport {\n resolvePluginOrder,\n validateInitServiceContract,\n assertInitServiceRequirements,\n describeInitOrderFault,\n} from './plugin-order.js';\nimport { dispatchHookIsolating, dispatchHookPropagating } from './hook-dispatch.js';\n\n/**\n * Kernel state machine\n */\nexport type KernelState = 'idle' | 'initializing' | 'running' | 'stopping' | 'stopped';\n\n/**\n * ObjectKernelBase - Abstract Base Class for Microkernel\n * \n * Provides common functionality for ObjectKernel and LiteKernel:\n * - Plugin management (Map storage)\n * - Dependency resolution (topological sort)\n * - Hook/Event system\n * - Context creation\n * - State validation\n * \n * This eliminates code duplication between the implementations.\n */\nexport abstract class ObjectKernelBase {\n protected plugins: Map<string, Plugin> = new Map();\n protected services: IServiceRegistry | Map<string, any> = new Map();\n protected hooks: Map<string, Array<(...args: any[]) => void | Promise<void>>> = new Map();\n protected state: KernelState = 'idle';\n protected logger: Logger;\n protected context!: PluginContext;\n /**\n * Name of the plugin whose init() is currently executing (Phase 1 runs\n * sequentially, so there is at most one). Lets a getService miss during\n * init name the structural fault (#4131) instead of only the symptom.\n */\n protected currentlyInitializing?: string;\n\n constructor(logger: Logger) {\n this.logger = logger;\n }\n\n /**\n * Validate kernel state\n * @param requiredState - Required state for the operation\n * @throws Error if current state doesn't match\n */\n protected validateState(requiredState: KernelState): void {\n if (this.state !== requiredState) {\n throw new Error(\n `[Kernel] Invalid state: expected '${requiredState}', got '${this.state}'`\n );\n }\n }\n\n /**\n * Validate kernel is in idle state (for plugin registration)\n */\n protected validateIdle(): void {\n if (this.state !== 'idle') {\n throw new Error('[Kernel] Cannot register plugins after bootstrap has started');\n }\n }\n\n /**\n * Create the plugin context\n * Subclasses can override to customize context creation\n */\n protected createContext(): PluginContext {\n return {\n registerService: (name, service) => {\n if (this.services instanceof Map) {\n if (this.services.has(name)) {\n throw new Error(`[Kernel] Service '${name}' already registered`);\n }\n this.services.set(name, service);\n } else {\n // IServiceRegistry implementation\n this.services.register(name, service);\n }\n this.logger.info(`Service '${name}' registered`, { service: name });\n },\n getService: <T>(name: string): T => {\n if (this.services instanceof Map) {\n const service = this.services.get(name);\n if (!service) {\n throw new Error(\n `[Kernel] Service '${name}' not found${this.describeInitOrderFault(name)}`\n );\n }\n return service as T;\n } else {\n // IServiceRegistry implementation\n return this.services.get<T>(name);\n }\n },\n replaceService: <T>(name: string, implementation: T): void => {\n if (this.services instanceof Map) {\n if (!this.services.has(name)) {\n throw new Error(`[Kernel] Service '${name}' not found. Use registerService() to add new services.`);\n }\n this.services.set(name, implementation);\n } else {\n // IServiceRegistry implementation\n if (!this.services.has(name)) {\n throw new Error(`[Kernel] Service '${name}' not found. Use registerService() to add new services.`);\n }\n this.services.register(name, implementation);\n }\n this.logger.info(`Service '${name}' replaced`, { service: name });\n },\n hook: (name, handler) => {\n if (!this.hooks.has(name)) {\n this.hooks.set(name, []);\n }\n this.hooks.get(name)!.push(handler);\n },\n // PROPAGATING dispatch, and deliberately WITHOUT the trace line the\n // kernel's own dispatch sites emit — `context.trigger` has never\n // logged one, so no logger is handed over (#5282).\n trigger: async (name, ...args) => {\n await dispatchHookPropagating(name, this.hooks.get(name) || [], undefined, args);\n },\n getServices: () => {\n if (this.services instanceof Map) {\n return new Map(this.services);\n } else {\n // For IServiceRegistry, we need to return the underlying Map\n // This is a compatibility method\n return new Map();\n }\n },\n logger: this.logger,\n getKernel: () => this as any,\n registerServiceFactory: (_name, _factory, _lifecycle, _dependencies) => {\n throw new Error('[KernelBase] registerServiceFactory not supported — use ObjectKernel');\n },\n getServiceScoped: async <T>(_name: string, _scopeId: string): Promise<T> => {\n throw new Error('[KernelBase] getServiceScoped not supported — use ObjectKernel');\n },\n };\n }\n\n /**\n * Resolve plugin dependencies using topological sort — `dependencies`\n * hard, `optionalDependencies` order-if-present (ADR-0116, #4131). One\n * implementation shared with ObjectKernel via `plugin-order.ts`.\n * @returns Ordered list of plugins (dependencies first)\n */\n protected resolveDependencies(): Plugin[] {\n return resolvePluginOrder(this.plugins);\n }\n\n /**\n * Whether a service is registered on this kernel right now. Backs the\n * init-service contract checks (#4131).\n */\n protected hasRegisteredService(name: string): boolean {\n // Both the plain Map and IServiceRegistry expose `has`.\n return this.services.has(name);\n }\n\n /**\n * Pre-Phase-1 ordering validation (ADR-0116, #4131): a plugin whose\n * `requiresServices` names a service provided only by a LATER plugin is\n * a named boot error before any init side effects.\n */\n protected validateInitServices(ordered: Plugin[]): void {\n validateInitServiceContract(ordered, (name) => this.hasRegisteredService(name));\n }\n\n /**\n * When a getService miss happens while a plugin's init() is running,\n * append the structural diagnosis (#4131): which plugin was initializing,\n * and — when a composed plugin declares the service — who provides it.\n * Empty string outside Phase 1, so non-boot messages stay unchanged.\n */\n protected describeInitOrderFault(serviceName: string): string {\n return describeInitOrderFault(this.currentlyInitializing, this.plugins.values(), serviceName);\n }\n\n /**\n * Run plugin init phase\n * @param plugin - Plugin to initialize\n */\n protected async runPluginInit(plugin: Plugin): Promise<void> {\n const pluginName = plugin.name;\n this.logger.info(`Initializing plugin: ${pluginName}`);\n\n // Authoritative init-service check (#4131): Phase 1 is sequential,\n // so a required service absent NOW is absent for this init.\n assertInitServiceRequirements(plugin, (name) => this.hasRegisteredService(name));\n\n this.currentlyInitializing = pluginName;\n try {\n await plugin.init(this.context);\n this.logger.info(`Plugin initialized: ${pluginName}`);\n } catch (error) {\n this.logger.error(`Plugin init failed: ${pluginName}`, error as Error);\n throw error;\n } finally {\n this.currentlyInitializing = undefined;\n }\n }\n\n /**\n * Run plugin start phase\n * @param plugin - Plugin to start\n */\n protected async runPluginStart(plugin: Plugin): Promise<void> {\n if (!plugin.start) return;\n \n const pluginName = plugin.name;\n this.logger.info(`Starting plugin: ${pluginName}`);\n \n try {\n await plugin.start(this.context);\n this.logger.info(`Plugin started: ${pluginName}`);\n } catch (error) {\n this.logger.error(`Plugin start failed: ${pluginName}`, error as Error);\n throw error;\n }\n }\n\n /**\n * Run plugin destroy phase\n * @param plugin - Plugin to destroy\n */\n protected async runPluginDestroy(plugin: Plugin): Promise<void> {\n if (!plugin.destroy) return;\n \n const pluginName = plugin.name;\n this.logger.info(`Destroying plugin: ${pluginName}`);\n \n try {\n await plugin.destroy();\n this.logger.info(`Plugin destroyed: ${pluginName}`);\n } catch (error) {\n this.logger.error(`Plugin destroy failed: ${pluginName}`, error as Error);\n throw error;\n }\n }\n\n /**\n * Trigger a hook with all registered handlers, ISOLATING failures: a\n * handler that throws is logged and the remaining handlers still run.\n *\n * Use this for hooks where one subscriber's failure must not deny the\n * others their turn — notification-style hooks, and `kernel:shutdown`,\n * where the handlers still queued behind the failing one are the cleanup\n * that flushes buffers and releases resources (#5257).\n *\n * It is the WRONG dispatcher for anything on the BOOT path. Every hook\n * dispatched before \"✅ Bootstrap complete\" is a precondition of that\n * claim, so swallowing a throw there does not rescue the boot — it only\n * hides the failure behind a process that reports success. Those hooks\n * (`kernel:ready`, `kernel:bootstrapped`, `kernel:listening`) use\n * {@link triggerHookOrThrow} (#5170, #5257).\n *\n * The loop itself lives in {@link dispatchHookIsolating} — one\n * implementation shared with `ObjectKernel`'s own `kernel:shutdown`\n * dispatch, which cannot inherit this method (`ObjectKernel` does not\n * extend this class) and used to hand-mirror it (#5282).\n *\n * @param name - Hook name\n * @param args - Arguments to pass to handlers\n */\n protected async triggerHook(name: string, ...args: any[]): Promise<void> {\n await dispatchHookIsolating(name, this.hooks.get(name) || [], this.logger, args);\n }\n\n /**\n * Trigger a hook with all registered handlers, PROPAGATING the first\n * failure: the remaining handlers do not run and the original error\n * reaches the caller unwrapped.\n *\n * This is the dispatch semantics `ObjectKernel` has always had for every\n * lifecycle hook (its `context.trigger` is a bare awaited loop that never\n * catches). `LiteKernel` used the isolating {@link triggerHook} for all of\n * them, so one hook name meant two opposite things depending on which\n * kernel booted the same plugin code (#5170).\n *\n * `LiteKernel` now uses this dispatcher for all three BOOT-path hooks:\n *\n * - `kernel:ready` (#5170) — the only correct moment for a plugin to\n * assert that the preconditions it declared were actually met (the\n * registries are still filling during `init()`), so \"declared but not\n * deliverable ⇒ refuse to boot\" gates live there. On LiteKernel, which\n * is what vitest/serverless/edge run, they were downgraded to an error\n * log while the process carried on serving traffic without the\n * guarantee it claimed.\n * - `kernel:bootstrapped` and `kernel:listening` (#5257) — the same\n * argument one hook later. `kernel:listening` is where HTTP server\n * plugins open their socket, so a swallowed failure there produced the\n * worst shape available: a live process printing \"✅ Bootstrap complete\"\n * with nothing listening. `kernel:bootstrapped` carries reconcile and\n * audit passes whose silent failure is a quieter version of the same\n * lie.\n *\n * Deliberately NOT applied to `kernel:shutdown`, which keeps\n * {@link triggerHook}: on the teardown path a failing handler must not\n * block the cleanup queued behind it. That is a per-hook judgement\n * recorded at the dispatch site in `lite-kernel.ts`, not an inherited\n * default — and it is the reason this dispatcher is chosen per hook rather\n * than swapped in wholesale.\n *\n * The loop itself lives in {@link dispatchHookPropagating} — the same\n * function `PluginContext.trigger` runs on both kernels, so \"propagating\"\n * means one thing repo-wide (#5282).\n *\n * @param name - Hook name\n * @param args - Arguments to pass to handlers\n */\n protected async triggerHookOrThrow(name: string, ...args: any[]): Promise<void> {\n await dispatchHookPropagating(name, this.hooks.get(name) || [], this.logger, args);\n }\n\n /**\n * Get current kernel state\n */\n getState(): KernelState {\n return this.state;\n }\n\n /**\n * Get all registered plugins\n */\n getPlugins(): Map<string, Plugin> {\n return new Map(this.plugins);\n }\n\n /**\n * Abstract methods to be implemented by subclasses\n */\n abstract use(plugin: Plugin): this | Promise<this>;\n abstract bootstrap(): Promise<void>;\n abstract destroy(): Promise<void>;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { LoggerConfig, LogLevel } from '@objectstack/spec/system';\nimport type { Logger } from '@objectstack/spec/contracts';\n\n// Re-export the contract type so consumers can do\n// `import type { Logger } from '@objectstack/core/logger'` without also\n// pulling `@objectstack/spec` into their bundle graph manually.\nexport type { Logger };\n\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n fatal: 4,\n silent: 5,\n};\n\nconst LEVEL_COLORS: Record<LogLevel, string> = {\n debug: '\\x1b[36m',\n info: '\\x1b[32m',\n warn: '\\x1b[33m',\n error: '\\x1b[31m',\n fatal: '\\x1b[35m',\n silent: '',\n};\n\nconst RESET = '\\x1b[0m';\n\n/**\n * Split a field name into lowercase words on camelCase, `snake_case`,\n * `kebab-case`, dot and letter/digit boundaries.\n *\n * `apiKey` / `api_key` / `API_KEY` / `x-api-key` all tokenize to\n * `['api','key']`, while `monkey`, `keyword` and `tokenizer` stay a single\n * word. That difference is the whole point: it is what makes the redactor a\n * **word-boundary** matcher instead of the substring matcher it used to be\n * (#5573) — a plain `keys` field no longer reads as a secret.\n */\nfunction tokenizeFieldName(name: string): string[] {\n return name\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2') // apiKey -> api Key\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2') // APIKey -> API Key\n .replace(/([a-zA-Z])([0-9])/g, '$1 $2') // key2 -> key 2\n .split(/[^A-Za-z0-9]+/) // _ - . / space\n .filter(Boolean)\n .map((word) => word.toLowerCase());\n}\n\n/**\n * Singular form of the plural spellings the redact vocabulary actually meets\n * (`keys`, `tokens`, `secrets`, `passwords`, `passes`). Deliberately not a\n * general inflector — it only has to be right for words that end up next to a\n * redact word, and it must never turn `address`/`status` into a new word.\n */\nfunction singularizeWord(word: string): string {\n if (/(?:ss|us|is)$/.test(word)) return word; // address / status / axis\n if (/(?:ch|sh|s|x|z)es$/.test(word)) return word.slice(0, -2); // passes / boxes\n if (/[a-z0-9]s$/.test(word)) return word.slice(0, -1); // keys / tokens\n return word;\n}\n\n/**\n * Words that mark the *secret* sense of a redact word when they are glued to\n * it with no boundary to split on: `apikey`, `accesstoken`, `clientsecret`.\n *\n * Word-boundary matching covers every field name spelled the way this repo\n * spells names (camelCase config keys / snake_case machine names — Prime\n * Directive #3), but an all-lowercase concatenation has no boundary at all, so\n * `apikey` would tokenize to one word and stop being redacted. A bare\n * \"ends with `key`\" rule cannot be used to rescue it, because `monkey`,\n * `turkey` and `whiskey` end with `key` too — the exact false positives #5573\n * exists to remove. So the rescue is scoped to this explicit qualifier list:\n * `<qualifier><redact word>` is a secret, anything else glued to a redact word\n * is not.\n *\n * Consequences, on purpose:\n * - Only a **suffix** concatenation counts. `secretary` and `keyword` start\n * with a redact word and stay clear.\n * - An unlisted qualifier (`foobarkey`) is not redacted. The fix is to spell\n * the field `fooBarKey` / `foo_bar_key`, which matches generically — or to\n * add the word here.\n */\nconst CONCATENATED_SECRET_QUALIFIERS = new Set([\n 'access',\n 'account',\n 'admin',\n 'api',\n 'app',\n 'auth',\n 'bearer',\n 'client',\n 'csrf',\n 'db',\n 'database',\n 'encryption',\n 'id',\n 'jwt',\n 'master',\n 'oauth',\n 'private',\n 'public',\n 'refresh',\n 'root',\n 'secret',\n 'service',\n 'session',\n 'shared',\n 'sign',\n 'signing',\n 'ssh',\n 'token',\n 'user',\n 'webhook',\n 'xsrf',\n]);\n\n/** `apikey`/`apikeys` vs `key` — see {@link CONCATENATED_SECRET_QUALIFIERS}. */\nfunction isQualifiedConcatenation(word: string, redactWord: string): boolean {\n for (const base of [word, singularizeWord(word)]) {\n if (base.length <= redactWord.length || !base.endsWith(redactWord)) continue;\n if (CONCATENATED_SECRET_QUALIFIERS.has(base.slice(0, base.length - redactWord.length))) return true;\n }\n return false;\n}\n\n/** Does `words` contain `run` as a consecutive sub-sequence? */\nfunction containsWordRun(words: string[], run: string[]): boolean {\n for (let i = 0; i + run.length <= words.length; i++) {\n if (run.every((word, offset) => words[i + offset] === word)) return true;\n }\n return false;\n}\n\n/**\n * Word-boundary match of one configured redact pattern against one field name,\n * both already tokenized by {@link tokenizeFieldName}.\n *\n * The plural rule is the one subtlety, and it is the maintainer's ruling on\n * #5573 made consistent with itself: a **bare** plural names a collection or a\n * count, not a secret (`keys` on a Zod `unrecognized_keys` issue, `tokens` on\n * an LLM usage record), so it is left alone; a plural **inside a compound**\n * still names the secret (`apiKeys: ['sk-…']`, `refresh_tokens`) and is\n * redacted. Singular words match everywhere, compound or not.\n */\nfunction fieldWordsMatchPattern(nameWords: string[], patternWords: string[]): boolean {\n if (patternWords.length === 0 || nameWords.length === 0) return false;\n\n // A multi-word pattern (`apiKey`, `api_key`) matches a consecutive run of\n // the same words, or those words written as one concatenated token.\n if (patternWords.length > 1) {\n const glued = patternWords.join('');\n return (\n containsWordRun(nameWords, patternWords) ||\n nameWords.some((word) => word === glued || singularizeWord(word) === glued)\n );\n }\n\n const redactWord = patternWords[0];\n const isCompound = nameWords.filter((word) => /[a-z]/.test(word)).length > 1;\n return nameWords.some(\n (word) =>\n word === redactWord ||\n (isCompound && singularizeWord(word) === redactWord) ||\n isQualifiedConcatenation(word, redactWord),\n );\n}\n\n/**\n * Whether ANSI color may be written to the given stream.\n *\n * Follows the https://no-color.org convention: a non-empty `NO_COLOR` env var\n * disables color regardless of TTY, and non-TTY destinations (pipes, CI logs,\n * redirected output) always get plain text so plain-text log scanners see\n * uncolored level tags. Browser bundles have no `process`/TTY → plain text.\n */\nfunction colorEnabled(stream: { isTTY?: boolean } | undefined): boolean {\n if (typeof process !== 'undefined') {\n const noColor = (process as any).env?.NO_COLOR;\n if (noColor !== undefined && noColor !== '') return false;\n }\n return Boolean(stream?.isTTY);\n}\n\n/**\n * Resolve a Node builtin without putting it in this module's import graph.\n *\n * This entry is deliberately browser-safe — `@objectstack/client` bundles it —\n * so `fs`/`path` must never be imported statically. A lazy `require()` used to\n * meet that bar, but esbuild rewrites it to the `__require` shim in the ESM\n * output, which throws `Dynamic require of \"fs\" is not supported`. Every Node\n * ESM consumer (`os serve`, `os dev`) therefore lost file logging (#3110).\n * `process.getBuiltinModule` is a plain method call — opaque to bundlers — and\n * works in both module systems.\n */\nfunction loadNodeBuiltin<T>(id: string): T | undefined {\n if (typeof process === 'undefined') return undefined;\n\n const getBuiltinModule = (process as { getBuiltinModule?: (id: string) => unknown }).getBuiltinModule;\n if (typeof getBuiltinModule === 'function') {\n try {\n return getBuiltinModule.call(process, `node:${id}`) as T;\n } catch {\n return undefined;\n }\n }\n\n // Node < 20.16 / < 22.3 predates `getBuiltinModule`. Real `require` still\n // resolves in the CJS build; in the ESM build this is the shim that throws,\n // which the caller now reports rather than swallows.\n try {\n return require(id) as T;\n } catch {\n return undefined;\n }\n}\n\nexport class ObjectLogger implements Logger {\n private config: Required<Omit<LoggerConfig, 'file' | 'rotation' | 'name'>> & {\n file?: string;\n rotation?: { maxSize: string; maxFiles: number };\n name?: string;\n };\n private bindings: Record<string, any>;\n /** `config.redact`, tokenized once — see {@link fieldWordsMatchPattern}. */\n private redactPatterns: string[][];\n private fileStream?: any;\n /** Only the logger that opened the stream may close it — children share it. */\n private ownsFileStream = false;\n private fileLoggingDisabled = false;\n\n constructor(config: Partial<LoggerConfig> = {}, bindings: Record<string, any> = {}) {\n this.config = {\n name: config.name,\n level: config.level ?? 'info',\n format: config.format ?? 'pretty',\n redact: config.redact ?? ['password', 'token', 'secret', 'key'],\n sourceLocation: config.sourceLocation ?? false,\n file: config.file,\n // Per-key, because `LoggerConfig` is the AUTHOR state (ADR-0122): the\n // schema defaults `maxSize`/`maxFiles` *inside* `rotation`, so a caller\n // may legitimately write `{ rotation: { maxSize: '5m' } }` and this\n // constructor — which does not parse — has to fill the other half the\n // same way `LoggerConfigSchema.parse` would.\n rotation: {\n maxSize: config.rotation?.maxSize ?? '10m',\n maxFiles: config.rotation?.maxFiles ?? 5,\n },\n };\n this.bindings = bindings;\n this.redactPatterns = this.config.redact.map(tokenizeFieldName).filter((words) => words.length > 0);\n\n if (this.config.file && typeof process !== 'undefined') {\n this.openFileStream(this.config.file);\n }\n }\n\n private openFileStream(path: string) {\n const fs = loadNodeBuiltin<typeof import('node:fs')>('fs');\n const nodePath = loadNodeBuiltin<typeof import('node:path')>('path');\n if (!fs || !nodePath) {\n this.disableFileLogging(path, 'no filesystem access in this runtime');\n return;\n }\n\n try {\n fs.mkdirSync(nodePath.dirname(path), { recursive: true });\n const stream = fs.createWriteStream(path, { flags: 'a' });\n // `createWriteStream` reports open failures (EACCES, EISDIR, …)\n // asynchronously. An 'error' event with no listener is fatal to the\n // process, so file logging must degrade here rather than take the\n // host down.\n stream.on('error', (err: Error) => this.disableFileLogging(path, err.message));\n this.fileStream = stream;\n this.ownsFileStream = true;\n } catch (err) {\n this.disableFileLogging(path, (err as Error).message);\n }\n }\n\n /**\n * Report — once — that an explicitly configured `file` destination is not\n * being written, and stop trying.\n *\n * Deliberately not routed through `write()`: this says the logger cannot\n * honour its own config, so `level` must not filter it. The bare `catch {}`\n * this replaces is exactly how #3110 stayed hidden.\n */\n private disableFileLogging(path: string, reason: string) {\n this.fileStream = undefined;\n this.ownsFileStream = false;\n if (this.fileLoggingDisabled) return;\n this.fileLoggingDisabled = true;\n\n const label = this.config.name ? `[${this.config.name}] ` : '';\n const notice = `${label}logger: file logging disabled — cannot write to ${path}: ${reason}`;\n if (typeof process !== 'undefined' && (process as any).stderr) {\n (process as any).stderr.write(notice + '\\n');\n } else if (typeof console !== 'undefined') {\n console.warn(notice);\n }\n }\n\n private isEnabled(level: LogLevel): boolean {\n return LEVEL_ORDER[level] >= LEVEL_ORDER[this.config.level];\n }\n\n /**\n * Whether a meta field name names one of the configured secrets.\n *\n * Until #5573 this was `lower.includes(pattern)`, which redacted every\n * field whose name merely *contained* a redact word — `keys`, `keyword`,\n * `tokens`, `monkey`, `secretary` — and replaced its value with\n * `***REDACTED***`, so the reader lost the fact AND was told a secret had\n * been withheld. Matching is now on word boundaries: `key` matches\n * `apiKey` / `api_key`, not `keys` / `monkey` / `keyword`.\n */\n private isRedactedFieldName(key: string): boolean {\n const nameWords = tokenizeFieldName(key);\n return this.redactPatterns.some((pattern) => fieldWordsMatchPattern(nameWords, pattern));\n }\n\n private redactSensitive(obj: any): any {\n if (!obj || typeof obj !== 'object') return obj;\n const redacted = Array.isArray(obj) ? [...obj] : { ...obj };\n for (const key in redacted) {\n if (this.isRedactedFieldName(key)) {\n redacted[key] = '***REDACTED***';\n } else if (typeof redacted[key] === 'object' && redacted[key] !== null) {\n redacted[key] = this.redactSensitive(redacted[key]);\n }\n }\n return redacted;\n }\n\n private write(level: LogLevel, message: string, meta?: Record<string, any>, error?: Error) {\n if (!this.isEnabled(level)) return;\n\n const context = this.redactSensitive({\n ...this.bindings,\n ...meta,\n ...(error ? { error: { message: error.message, stack: error.stack } } : {}),\n });\n\n const hasContext = Object.keys(context).length > 0;\n const ts = new Date().toISOString();\n\n const isErrorLevel = level === 'error' || level === 'fatal';\n const proc = typeof process !== 'undefined' ? (process as any) : undefined;\n const stream = proc ? (isErrorLevel ? proc.stderr : proc.stdout) : undefined;\n\n let line: string; // console output — may carry ANSI color\n let plainLine: string; // file output — never colored\n\n if (this.config.format === 'json') {\n line = plainLine = JSON.stringify({\n time: ts,\n level,\n ...(this.config.name ? { name: this.config.name } : {}),\n msg: message,\n ...context,\n });\n } else if (this.config.format === 'text') {\n const parts = [ts, level.toUpperCase(), message];\n if (hasContext) parts.push(JSON.stringify(context));\n line = plainLine = parts.join(' | ');\n } else {\n // pretty\n const label = this.config.name ? `[${this.config.name}] ` : '';\n const head = `${ts} ${level.toUpperCase()}`;\n let tail = ` ${label}${message}`;\n if (hasContext) tail += ` ${JSON.stringify(context)}`;\n plainLine = head + tail;\n const color = LEVEL_COLORS[level] || '';\n line = color && colorEnabled(stream) ? `${color}${head}${RESET}${tail}` : plainLine;\n }\n\n // Browser-safe output: prefer process streams when available, otherwise\n // fall back to console. `process` may be missing entirely (browsers) or\n // present without stdio streams (bundler shims) — both fall through to\n // console. The previous unguarded `process.stderr?.write` threw\n // `ReferenceError: process is not defined` in browsers because\n // `process` itself is the missing global, not just its `stderr` field.\n if (stream) {\n stream.write(line + '\\n');\n } else if (typeof console !== 'undefined') {\n const fn =\n level === 'error' || level === 'fatal' ? console.error\n : level === 'warn' ? console.warn\n : level === 'debug' ? console.debug\n : console.log;\n fn(line);\n }\n\n if (this.fileStream) {\n this.fileStream.write(plainLine + '\\n');\n }\n }\n\n debug(message: string, meta?: Record<string, any>): void {\n this.write('debug', message, meta);\n }\n\n info(message: string, meta?: Record<string, any>): void {\n this.write('info', message, meta);\n }\n\n warn(message: string, meta?: Record<string, any>): void {\n this.write('warn', message, meta);\n }\n\n error(message: string, errorOrMeta?: Error | Record<string, any>, meta?: Record<string, any>): void {\n this.writeErrorLike('error', message, errorOrMeta, meta);\n }\n\n fatal(message: string, errorOrMeta?: Error | Record<string, any>, meta?: Record<string, any>): void {\n this.writeErrorLike('fatal', message, errorOrMeta, meta);\n }\n\n /**\n * `error`/`fatal` dispatch — the two levels whose contract has an `Error`\n * slot in front of `meta`.\n *\n * The `Logger` contract declares `error(message, error?: Error, meta?)`, and\n * `ObjectLogger` additionally tolerates a **meta object** in the `error`\n * slot because many in-repo call sites write `logger.error(msg, { … })`.\n * That tolerance is fine; dropping a parameter the contract *declares* is\n * not, and that is what the previous dispatch did:\n *\n * if (errorOrMeta instanceof Error) this.write(level, message, meta, errorOrMeta);\n * else this.write(level, message, errorOrMeta);\n *\n * With `error === undefined` the `else` branch passed `undefined` as the\n * meta and **never read the third argument**, so every contract-shaped\n * `logger.error(msg, undefined, { … })` call rendered a bare message with\n * its diagnostics silently gone — ~15 such call sites across `metadata`,\n * `metadata-protocol`, `client` and `core/security`, plus the connector\n * reconcile seam that found this (#5575). The contract's two sibling\n * implementations (`ConsoleLogger`/`JsonLogger` in `@objectstack/observability`)\n * both honour the slot, so the contract was right and this class was the\n * outlier — declared ≠ enforced, Prime Directive #10.\n *\n * All three shapes are now honoured. When both slots carry meta, `meta`\n * (the later, more specific argument) wins on a key collision.\n */\n private writeErrorLike(\n level: 'error' | 'fatal',\n message: string,\n errorOrMeta?: Error | Record<string, any>,\n meta?: Record<string, any>,\n ): void {\n if (errorOrMeta instanceof Error) {\n this.write(level, message, meta, errorOrMeta);\n return;\n }\n const merged = errorOrMeta && meta ? { ...errorOrMeta, ...meta } : (errorOrMeta ?? meta);\n this.write(level, message, merged);\n }\n\n log(message: string, ...args: any[]): void {\n this.info(message, args.length > 0 ? { args } : undefined);\n }\n\n child(context: Record<string, any>): ObjectLogger {\n // Construct without `file`, then share the parent's stream: the\n // constructor opens eagerly, so passing `file` through would open a\n // second stream per child and immediately orphan it. That leak was\n // unreachable while #3110 kept the ESM open path dead.\n const child = new ObjectLogger({ ...this.config, file: undefined }, { ...this.bindings, ...context });\n child.config.file = this.config.file;\n child.fileStream = this.fileStream;\n return child;\n }\n\n withTrace(traceId: string, spanId?: string): ObjectLogger {\n return this.child({ traceId, spanId });\n }\n\n async destroy(): Promise<void> {\n const stream = this.fileStream;\n this.fileStream = undefined;\n // Children share the opener's stream; if they closed it too, one child's\n // teardown would end file logging for the parent and every sibling,\n // whose writes then land on a closed stream and only trip the 'error'\n // handler above.\n if (!stream || !this.ownsFileStream) return;\n this.ownsFileStream = false;\n await new Promise<void>((resolve) => stream.end(resolve));\n }\n}\n\nexport function createLogger(config?: Partial<LoggerConfig>): ObjectLogger {\n return new ObjectLogger(config);\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Plugin, PluginContext } from './types.js';\nimport { createLogger, ObjectLogger } from './logger.js';\nimport type { LoggerConfig } from '@objectstack/spec/system';\nimport { ServiceRequirementDef } from '@objectstack/spec/system';\nimport { PluginLoader, PluginMetadata, ServiceLifecycle, ServiceFactory, PluginStartupResult } from './plugin-loader.js';\nimport { isNode, safeExit } from './utils/env.js';\nimport { CORE_FALLBACK_FACTORIES } from './fallbacks/index.js';\nimport {\n resolvePluginOrder,\n validateInitServiceContract,\n assertInitServiceRequirements,\n describeInitOrderFault,\n} from './plugin-order.js';\nimport { dispatchHookIsolating, dispatchHookPropagating } from './hook-dispatch.js';\nimport { registerPluginByName } from './plugin-registration.js';\nimport { raceWithTimeout } from './timeout-guard.js';\n\n/**\n * Enhanced Kernel Configuration\n */\nexport interface ObjectKernelConfig {\n logger?: Partial<LoggerConfig>;\n \n /** Default plugin startup timeout in milliseconds */\n defaultStartupTimeout?: number;\n \n /** Whether to enable graceful shutdown */\n gracefulShutdown?: boolean;\n \n /** Graceful shutdown timeout in milliseconds */\n shutdownTimeout?: number;\n \n /** Whether to rollback on startup failure */\n rollbackOnFailure?: boolean;\n \n /** Whether to skip strict system requirement validation (Critical for testing) */\n skipSystemValidation?: boolean;\n}\n\n/**\n * Enhanced ObjectKernel with Advanced Plugin Management\n * \n * Extends the basic ObjectKernel with:\n * - Async plugin loading with validation\n * - Version compatibility checking\n * - Plugin signature verification\n * - Configuration validation (Zod)\n * - Factory-based dependency injection\n * - Service lifecycle management (singleton/transient/scoped)\n * - Circular dependency detection\n * - Lazy loading services\n * - Graceful shutdown\n * - Plugin startup timeout control\n * - Startup failure rollback\n * - Plugin health checks\n */\nexport class ObjectKernel {\n private plugins: Map<string, PluginMetadata> = new Map();\n private services: Map<string, any> = new Map();\n private hooks: Map<string, Array<(...args: any[]) => void | Promise<void>>> = new Map();\n private state: 'idle' | 'initializing' | 'running' | 'stopping' | 'stopped' = 'idle';\n private logger: ObjectLogger;\n private context: PluginContext;\n private pluginLoader: PluginLoader;\n private config: ObjectKernelConfig;\n private startedPlugins: Set<string> = new Set();\n private pluginStartTimes: Map<string, number> = new Map();\n private shutdownHandlers: Array<() => Promise<void>> = [];\n /**\n * Name of the plugin whose init() is currently executing (Phase 1 is\n * sequential, so at most one). Lets a getService miss during init name\n * the structural fault (#4131) instead of only the symptom.\n */\n private currentlyInitializing?: string;\n\n constructor(config: ObjectKernelConfig = {}) {\n this.config = {\n defaultStartupTimeout: 30000, // 30 seconds\n gracefulShutdown: true,\n shutdownTimeout: 60000, // 60 seconds\n rollbackOnFailure: true,\n ...config,\n };\n\n this.logger = createLogger(config.logger);\n this.pluginLoader = new PluginLoader(this.logger);\n \n // Initialize context\n this.context = {\n registerService: (name, service) => {\n this.registerService(name, service);\n },\n registerServiceFactory: (name, factory, lifecycle, dependencies) => {\n this.registerServiceFactory(name, factory, lifecycle, dependencies);\n },\n getService: <T>(name: string) => {\n // 1. Try direct service map first (synchronous cache)\n const service = this.services.get(name);\n if (service) {\n return service as T;\n }\n\n // 2. Try to get from plugin loader cache (Sync access to factories)\n const loaderService = this.pluginLoader.getServiceInstance<T>(name);\n if (loaderService) {\n // Cache it locally for faster next access\n this.services.set(name, loaderService);\n return loaderService;\n }\n\n // 3. Neither sync map has it. Two very different faults share\n // this branch and MUST NOT share one message (#4085):\n // (a) nothing ever registered `name` — a composition /\n // ordering fault at the CALLER (e.g. a plugin reaching\n // for `manifest` in init() before the engine plugin\n // registered it);\n // (b) `name` IS registered, as a factory that has not been\n // instantiated yet — the caller merely used the wrong\n // accessor and needs `getServiceAsync`.\n // `pluginLoader.getService` is an `async` method, so its\n // return value is ALWAYS a Promise and its internal\n // \"not found\" rejection can never surface synchronously.\n // Reading (a) off that Promise therefore reported every\n // missing service as \"is async - use await\" — the wrong fix,\n // pointing at the wrong layer. Decide from the registry\n // instead, which is synchronous and authoritative.\n if (!this.pluginLoader.hasService(name)) {\n throw new Error(\n `[Kernel] Service '${name}' not found${this.describeInitOrderFault(name)}`\n );\n }\n\n // Registered but not instantiated ⇒ factory-backed. Message\n // kept verbatim: callers that tolerate an async-only service\n // (console static assets, the HTTP dispatcher) match on\n // `is async`.\n throw new Error(`Service '${name}' is async - use await`);\n },\n replaceService: <T>(name: string, implementation: T): void => {\n const hasService = this.services.has(name) || this.pluginLoader.hasService(name);\n if (!hasService) {\n throw new Error(`[Kernel] Service '${name}' not found. Use registerService() to add new services.`);\n }\n this.services.set(name, implementation);\n this.pluginLoader.replaceService(name, implementation);\n this.logger.info(`Service '${name}' replaced`, { service: name });\n },\n hook: (name, handler) => {\n if (!this.hooks.has(name)) {\n this.hooks.set(name, []);\n }\n this.hooks.get(name)!.push(handler);\n },\n // PROPAGATING dispatch — the same shared loop `LiteKernel`'s\n // context.trigger runs, and deliberately WITHOUT a trace line:\n // `context.trigger` has never emitted one on either kernel, so no\n // logger is handed over (#5282).\n trigger: async (name, ...args) => {\n await dispatchHookPropagating(name, this.hooks.get(name) || [], undefined, args);\n },\n getServices: () => {\n return new Map(this.services);\n },\n getServiceScoped: <T>(name: string, scopeId: string): Promise<T> => {\n return this.pluginLoader.getService<T>(name, scopeId);\n },\n logger: this.logger,\n getKernel: () => this as any, // Type compatibility\n };\n\n this.pluginLoader.setContext(this.context);\n\n // Register shutdown handler\n if (this.config.gracefulShutdown) {\n this.registerShutdownSignals();\n }\n }\n\n /**\n * Register a plugin with enhanced validation\n *\n * Duplicate names OVERWRITE, with one `warn` naming both versions — the\n * declared contract in `plugin-registration.ts`, applied identically by\n * `LiteKernel.use()` (#9864, maintainer ruling 2026-08-19). The overwrite\n * itself is unchanged: it is what lets an app config's `plugins` entry\n * supersede a plugin the CLI auto-registered earlier in the same boot\n * (#9863). What changes is that it is no longer silent, and no longer\n * disagrees with the other kernel.\n */\n async use(plugin: Plugin): Promise<this> {\n if (this.state !== 'idle') {\n throw new Error('[Kernel] Cannot register plugins after bootstrap has started');\n }\n\n // Load plugin through enhanced loader\n const result = await this.pluginLoader.loadPlugin(plugin);\n\n if (!result.success || !result.plugin) {\n throw new Error(`Failed to load plugin: ${plugin.name} - ${result.error?.message}`);\n }\n\n const pluginMeta = result.plugin;\n const superseded = registerPluginByName(this.plugins, pluginMeta, this.logger);\n\n // [#9864] Suppressed for a superseding registration, deliberately. The\n // defect the ruling names is that this line printed TWICE for one\n // surviving plugin and so read as two plugins running; the `warn`\n // `registerPluginByName` just emitted says everything this line would\n // and says which instance survived. Suppressing it here makes the\n // count of `Plugin registered:` lines in a boot log equal the number\n // of plugins that will actually boot.\n if (superseded === undefined) {\n this.logger.info(`Plugin registered: ${pluginMeta.name}@${pluginMeta.version}`, {\n plugin: pluginMeta.name,\n version: pluginMeta.version,\n });\n }\n\n return this;\n }\n\n /**\n * Register a service instance directly\n */\n registerService<T>(name: string, service: T): this {\n if (this.services.has(name)) {\n throw new Error(`[Kernel] Service '${name}' already registered`);\n }\n this.services.set(name, service);\n this.pluginLoader.registerService(name, service);\n this.logger.info(`Service '${name}' registered`, { service: name });\n return this;\n }\n\n /**\n * Register a service factory with lifecycle management\n */\n registerServiceFactory<T>(\n name: string,\n factory: ServiceFactory<T>,\n lifecycle: ServiceLifecycle = ServiceLifecycle.SINGLETON,\n dependencies?: string[]\n ): this {\n this.pluginLoader.registerServiceFactory({\n name,\n factory,\n lifecycle,\n dependencies,\n });\n return this;\n }\n\n /**\n * Pre-inject in-memory fallbacks for 'core' services that were not registered\n * by plugins during Phase 1. Called before Phase 2 so that all core services\n * (e.g. 'metadata', 'cache', 'queue') are resolvable via ctx.getService()\n * when plugin start() methods execute.\n */\n private preInjectCoreFallbacks() {\n if (this.config.skipSystemValidation) return;\n for (const [serviceName, criticality] of Object.entries(ServiceRequirementDef)) {\n if (criticality !== 'core') continue;\n const hasService = this.services.has(serviceName) || this.pluginLoader.hasService(serviceName);\n if (!hasService) {\n const factory = CORE_FALLBACK_FACTORIES[serviceName];\n if (factory) {\n const fallback = factory();\n this.registerService(serviceName, fallback);\n this.logger.debug(`[Kernel] Pre-injected in-memory fallback for '${serviceName}' before Phase 2`);\n }\n }\n }\n }\n\n /**\n * Validate Critical System Requirements\n */\n private validateSystemRequirements() {\n if (this.config.skipSystemValidation) {\n this.logger.debug('System requirement validation skipped');\n return;\n }\n\n this.logger.debug('Validating system service requirements...');\n const missingServices: string[] = [];\n const missingCoreServices: string[] = [];\n \n // Iterate through all defined requirements\n for (const [serviceName, criticality] of Object.entries(ServiceRequirementDef)) {\n const hasService = this.services.has(serviceName) || this.pluginLoader.hasService(serviceName);\n \n if (!hasService) {\n if (criticality === 'required') {\n this.logger.error(`CRITICAL: Required service missing: ${serviceName}`);\n missingServices.push(serviceName);\n } else if (criticality === 'core') {\n // Auto-inject in-memory fallback if available\n const factory = CORE_FALLBACK_FACTORIES[serviceName];\n if (factory) {\n const fallback = factory();\n this.registerService(serviceName, fallback);\n this.logger.warn(`Service '${serviceName}' not provided — using in-memory fallback`);\n } else {\n this.logger.warn(`CORE: Core service missing, functionality may be degraded: ${serviceName}`);\n missingCoreServices.push(serviceName);\n }\n } else {\n this.logger.info(`Info: Optional service not present: ${serviceName}`);\n }\n }\n }\n\n if (missingServices.length > 0) {\n const errorMsg = `System failed to start. Missing critical services: ${missingServices.join(', ')}`;\n this.logger.error(errorMsg);\n throw new Error(errorMsg);\n }\n\n if (missingCoreServices.length > 0) {\n this.logger.warn(`System started with degraded capabilities. Missing core services: ${missingCoreServices.join(', ')}`);\n }\n \n this.logger.info('System requirement check passed');\n }\n\n /**\n * Bootstrap the kernel with enhanced features\n */\n async bootstrap(): Promise<void> {\n if (this.state !== 'idle') {\n throw new Error('[Kernel] Kernel already bootstrapped');\n }\n\n this.state = 'initializing';\n this.logger.info('Bootstrap started');\n\n try {\n // Check for circular dependencies\n const cycles = this.pluginLoader.detectCircularDependencies();\n if (cycles.length > 0) {\n this.logger.warn('Circular service dependencies detected:', { cycles });\n }\n\n // Resolve plugin dependencies\n const orderedPlugins = this.resolveDependencies();\n\n // Pre-Phase-1 ordering contract (ADR-0116, #4131): a plugin that\n // requires a service provided only by a later plugin fails HERE,\n // named, before any init side effects.\n validateInitServiceContract(orderedPlugins, (name) => this.hasAnyService(name));\n\n // Phase 1: Init - Plugins register services\n this.logger.info('Phase 1: Init plugins');\n for (const plugin of orderedPlugins) {\n await this.initPluginWithTimeout(plugin);\n }\n\n // Pre-inject in-memory fallbacks for 'core' services that were not\n // registered by any plugin during Phase 1. This ensures services like\n // 'metadata', 'cache', 'queue', etc. are always available when plugins\n // call ctx.getService() during their start() methods.\n this.preInjectCoreFallbacks();\n\n // Phase 2: Start - Plugins execute business logic\n this.logger.info('Phase 2: Start plugins');\n this.state = 'running';\n \n for (const plugin of orderedPlugins) {\n const result = await this.startPluginWithTimeout(plugin);\n \n if (!result.success) {\n this.logger.error(`Plugin startup failed: ${plugin.name}`, result.error);\n const origMsg = result.error instanceof Error ? result.error.message : String(result.error);\n const origStack = result.error instanceof Error ? result.error.stack : '';\n console.error(`[Kernel] Plugin startup failed: ${plugin.name}`, origMsg, origStack);\n\n if (this.config.rollbackOnFailure) {\n this.logger.warn('Rolling back started plugins...');\n await this.rollbackStartedPlugins();\n // Propagate the original cause through the thrown error\n // so callers (e.g. cloud auth-proxy) can surface the\n // real failure instead of an opaque \"rollback complete\"\n // string. Without this, every kernel-boot failure looks\n // identical from the outside.\n const err: any = new Error(\n `Plugin ${plugin.name} failed to start - rollback complete: ${origMsg}`,\n );\n if (result.error instanceof Error) {\n err.cause = result.error;\n err.originalStack = origStack;\n }\n throw err;\n }\n }\n }\n\n // Phase 3: Trigger kernel:ready hook\n this.validateSystemRequirements(); // Final check before ready\n this.logger.debug('Triggering kernel:ready hook');\n await this.context.trigger('kernel:ready');\n\n // Phase 3.5: Trigger kernel:bootstrapped AFTER every kernel:ready\n // handler has settled — the \"all synchronous bootstrap has settled\"\n // anchor. Reconcile/backfill work that consumes data produced by a\n // later-starting plugin's kernel:ready handler belongs here, not in\n // kernel:ready (where handler order would race the data). NOTE: this\n // does NOT guarantee background app seed data has settled (an inline\n // seed that overruns OS_INLINE_SEED_BUDGET_MS finishes later) —\n // subscribe `app:seeded` for that. See\n // packages/spec/src/contracts/plugin-lifecycle-events.ts.\n this.logger.debug('Triggering kernel:bootstrapped hook');\n await this.context.trigger('kernel:bootstrapped');\n\n // Phase 4: Trigger kernel:listening hook AFTER all kernel:ready\n // handlers have completed. This is the cue for HTTP server\n // plugins to actually open the listening socket — by now every\n // other plugin has finished registering routes/middleware.\n // See `kernel:listening` docs in\n // packages/spec/src/contracts/plugin-lifecycle-events.ts\n // for the race-condition rationale.\n this.logger.debug('Triggering kernel:listening hook');\n await this.context.trigger('kernel:listening');\n\n this.logger.info('✅ Bootstrap complete');\n } catch (error) {\n this.state = 'stopped';\n throw error;\n }\n }\n\n /**\n * Graceful shutdown with timeout\n */\n async shutdown(): Promise<void> {\n if (this.state === 'stopped' || this.state === 'stopping') {\n this.logger.warn('Kernel already stopped or stopping');\n return;\n }\n\n if (this.state !== 'running') {\n throw new Error('[Kernel] Kernel not running');\n }\n\n this.state = 'stopping';\n this.logger.info('Graceful shutdown started');\n\n // The ONE rejection that means \"teardown hung\". Created here so the\n // catch below can discriminate by IDENTITY (#5274): only this\n // `setTimeout` can produce this exact object, so no message match, no\n // `instanceof`, and nothing a plugin throws can ever impersonate it —\n // not even a handler throwing `new Error('Shutdown timeout exceeded')`.\n // That discrimination is the whole point: the catch used to be reached\n // by BOTH the timer and any exception escaping `performShutdown()`, and\n // it treated them identically — `process.exit(1)` under a log line\n // reading \"Shutdown timed out\" when nothing had timed out.\n const shutdownTimeoutError = new Error('Shutdown timeout exceeded');\n\n try {\n // Same guard as the startup races (#10604). It used to be hand-rolled\n // here, and the two copies had already drifted into doing opposite\n // halves of the same job: this one `unref()`d its timer and never\n // cleared it, so a won race left it armed to fire against a kernel\n // that was already 'stopped', while the startup site cleared and\n // never unref'd. `raceWithTimeout` does both halves, once.\n //\n // Dropping the `unref()` is the point, not a casualty of the merge:\n // an unref'd guard stops being a guard (#4813). If `performShutdown()`\n // hangs and nothing else keeps the loop alive, an unref'd timer lets\n // Node exit *silently* — no 'Shutdown timed out', no `exit(1)`, the\n // one branch that hard-exit was ever right for never reached.\n // Clearing on settle keeps it ref'd exactly while the race is\n // undecided, which is the property this timeout needs.\n await raceWithTimeout(\n this.performShutdown(),\n this.config.shutdownTimeout!,\n () => shutdownTimeoutError,\n );\n\n this.state = 'stopped';\n this.logger.info('✅ Graceful shutdown complete');\n } catch (error) {\n this.state = 'stopped';\n\n if (error === shutdownTimeoutError) {\n // GENUINE timeout: `performShutdown()` is still running and has\n // stopped making progress, so the process would otherwise hang\n // holding whatever it failed to release. Hard-exit stays — it\n // is the only branch it was ever right for.\n this.logger.error('Shutdown timed out — forcing exit', error as Error);\n // Flush logger then hard-exit; the process would otherwise hang\n await this.logger.destroy();\n process.exit(1);\n } else {\n // NOT a timeout. `performShutdown()` isolates every teardown\n // step it owns (hook dispatch, each destroy(), each shutdown\n // handler), so reaching here means something outside those\n // loops failed — the teardown is over either way, and there is\n // nothing hung to escape from. Killing the host process here\n // would take away the embedding host's (cloud auth-proxy, CLI,\n // a test runner) chance to do its own cleanup, over a fault\n // that did not require it. Log and return down the normal\n // path; `shutdown()` still never rejects.\n this.logger.error(\n 'Shutdown finished with an unexpected teardown error — the kernel is stopped and the process is NOT being exited; some cleanup may not have run',\n error as Error,\n );\n }\n } finally {\n await this.logger.destroy();\n }\n }\n\n /**\n * Check health of a specific plugin\n */\n async checkPluginHealth(pluginName: string): Promise<any> {\n return await this.pluginLoader.checkPluginHealth(pluginName);\n }\n\n /**\n * Check health of all plugins\n */\n async checkAllPluginsHealth(): Promise<Map<string, any>> {\n const results = new Map();\n \n for (const pluginName of this.plugins.keys()) {\n const health = await this.checkPluginHealth(pluginName);\n results.set(pluginName, health);\n }\n \n return results;\n }\n\n /**\n * Get plugin startup metrics\n */\n getPluginMetrics(): Map<string, number> {\n return new Map(this.pluginStartTimes);\n }\n\n /**\n * Whether a plugin with the given name has been registered on this kernel.\n *\n * Registration happens synchronously in `use()` before any plugin's\n * `start()` runs, so a plugin may use this during its own start() to make\n * composition-dependent decisions deterministically — e.g. the dispatcher\n * bridge cedes `${prefix}/discovery` to `com.objectstack.rest.api` when\n * both are mounted (ADR-0076 D11: single owner per route, not\n * first-registration-wins).\n */\n hasPlugin(name: string): boolean {\n return this.plugins.has(name);\n }\n\n /**\n * Get a service (sync helper)\n */\n getService<T>(name: string): T {\n return this.context.getService<T>(name);\n }\n\n /**\n * Get a service asynchronously (supports factories)\n */\n async getServiceAsync<T>(name: string, scopeId?: string): Promise<T> {\n return await this.pluginLoader.getService<T>(name, scopeId);\n }\n\n /**\n * Clear all scoped service instances for a given scope (e.g., environmentId).\n * Releases driver connections and metadata caches for idle projects.\n */\n clearScope(scopeId: string): void {\n this.pluginLoader.clearScope(scopeId);\n }\n\n /**\n * Check if kernel is running\n */\n isRunning(): boolean {\n return this.state === 'running';\n }\n\n /**\n * Get kernel state\n */\n getState(): string {\n return this.state;\n }\n\n // Private methods\n\n private async initPluginWithTimeout(plugin: PluginMetadata): Promise<void> {\n const timeout = plugin.startupTimeout || this.config.defaultStartupTimeout!;\n\n this.logger.debug(`Init: ${plugin.name}`, { plugin: plugin.name });\n\n // Authoritative init-service check (#4131): Phase 1 is sequential,\n // so a required service absent NOW is absent for this init.\n assertInitServiceRequirements(plugin, (name) => this.hasAnyService(name));\n\n this.currentlyInitializing = plugin.name;\n try {\n await this.raceStartupTimeout(\n plugin.init(this.context),\n timeout,\n `Plugin ${plugin.name} init timeout after ${timeout}ms`\n );\n } finally {\n this.currentlyInitializing = undefined;\n }\n }\n\n /**\n * Race a plugin lifecycle hook against its startup-timeout guard, and\n * reclaim the guard the moment the race settles (#4813).\n *\n * The guard used to be armed and then abandoned: when the plugin won the\n * race, its `setTimeout` stayed ref'd in the event loop for the full\n * `startupTimeout`, so every process idled that long after its work was\n * done. One `os migrate` finished in 3s and then sat for 120s\n * (`ObjectQLPlugin.startupTimeout`), held open by 8 orphaned guards — one\n * per init plus one per start.\n *\n * Clearing on settle rather than `unref()`-ing at arm time is deliberate.\n * An unref'd guard also stops pinning the loop, but it stops being a guard\n * as well: if the hook never settles and nothing else keeps the loop alive,\n * Node exits before the timer can fire and the timeout is never reported.\n * The guard has to stay ref'd exactly as long as the race is undecided,\n * which is what clearing on settle expresses.\n *\n * Clearing the timer was only half of it, though (#10604): the promise the\n * race still holds a reaction on has to SETTLE, or it and that reaction are\n * retained past the end of the run — two leaking promises per boot, which\n * is what `vitest --detectAsyncLeaks` names here. Both halves now live in\n * `TimeoutGuard.reclaim()`, shared with `shutdown()`, so the two sites\n * cannot drift into doing one half each again.\n */\n private async raceStartupTimeout<T>(\n operation: T | PromiseLike<T>,\n timeout: number,\n message: string\n ): Promise<T> {\n return raceWithTimeout(operation, timeout, () => new Error(message));\n }\n\n /**\n * Whether a service is resolvable on this kernel right now — direct\n * registration or a loader-registered factory. Backs the init-service\n * contract checks (#4131).\n */\n private hasAnyService(name: string): boolean {\n return this.services.has(name) || this.pluginLoader.hasService(name);\n }\n\n /**\n * When a getService miss happens while a plugin's init() is running,\n * append the structural diagnosis (#4131): which plugin was initializing,\n * and — when a composed plugin declares the service — who provides it.\n * Empty string outside Phase 1, so non-boot messages stay unchanged.\n */\n private describeInitOrderFault(serviceName: string): string {\n return describeInitOrderFault(this.currentlyInitializing, this.plugins.values(), serviceName);\n }\n\n private async startPluginWithTimeout(plugin: PluginMetadata): Promise<PluginStartupResult> {\n if (!plugin.start) {\n return { success: true, pluginName: plugin.name };\n }\n\n const timeout = plugin.startupTimeout || this.config.defaultStartupTimeout!;\n const startTime = Date.now();\n \n this.logger.debug(`Start: ${plugin.name}`, { plugin: plugin.name });\n \n try {\n await this.raceStartupTimeout(\n plugin.start(this.context),\n timeout,\n `Plugin ${plugin.name} start timeout after ${timeout}ms`\n );\n\n const duration = Date.now() - startTime;\n this.startedPlugins.add(plugin.name);\n this.pluginStartTimes.set(plugin.name, duration);\n \n this.logger.debug(`Plugin started: ${plugin.name} (${duration}ms)`);\n \n return {\n success: true,\n pluginName: plugin.name,\n startTime: duration,\n };\n } catch (error) {\n const duration = Date.now() - startTime;\n const isTimeout = (error as Error).message.includes('timeout');\n \n return {\n success: false,\n pluginName: plugin.name,\n error: error as Error,\n startTime: duration,\n timedOut: isTimeout,\n };\n }\n }\n\n private async rollbackStartedPlugins(): Promise<void> {\n const pluginsToRollback = Array.from(this.startedPlugins).reverse();\n \n for (const pluginName of pluginsToRollback) {\n const plugin = this.plugins.get(pluginName);\n if (plugin?.destroy) {\n try {\n this.logger.debug(`Rollback: ${pluginName}`);\n await plugin.destroy();\n } catch (error) {\n this.logger.error(`Rollback failed for ${pluginName}`, error as Error);\n }\n }\n }\n \n this.startedPlugins.clear();\n }\n\n /**\n * Dispatch `kernel:shutdown`, ISOLATING failures: a handler that throws is\n * logged and the remaining handlers still run (#5274).\n *\n * This is a per-hook judgement, deliberately NOT the bare awaited loop\n * `context.trigger` runs for every other hook — the boot-path hooks\n * (`kernel:ready`, `kernel:bootstrapped`, `kernel:listening`) keep\n * propagating, because everything dispatched before \"✅ Bootstrap complete\"\n * is a precondition of that claim and swallowing a throw there only hides\n * the failure behind a process reporting success (#5170, #5257).\n *\n * On the teardown path there is no \"refuse to proceed\" left to buy. What is\n * queued behind a failing shutdown handler is the rest of the cleanup —\n * every other subscriber, then each plugin's `destroy()` in reverse order —\n * which is what flushes buffers, closes connections and releases locks. So\n * one bad handler must not amplify into leaked resources and unflushed\n * writes. Same reasoning, same wording, same `Hook handler failed:\n * kernel:shutdown` log line as `LiteKernel`'s dispatch site, which reaches\n * the isolating dispatcher through `ObjectKernelBase.triggerHook` (#5257).\n *\n * Until #5282 \"same wording\" was literally that — the loop was typed out a\n * second time here, because `ObjectKernel` does not extend\n * `ObjectKernelBase` (only `LiteKernel` does) and owns its own `hooks` map,\n * so the base's `protected triggerHook` is out of reach. The loop now lives\n * in {@link dispatchHookIsolating}, which BOTH sides call: the storage is\n * still two maps (deliberately — unifying it was out of #5282's scope), but\n * \"isolating\" is one implementation, so it can no longer drift on one\n * kernel while the other keeps the old shape. That drift is exactly the bug\n * #5170 / #5257 / #5274 each closed one hook at a time, and the paired-pin\n * gate (`scripts/check-kernel-hook-pairs.mjs`) covers the residue.\n */\n private async triggerShutdownHookIsolating(): Promise<void> {\n await dispatchHookIsolating('kernel:shutdown', this.hooks.get('kernel:shutdown') || [], this.logger);\n }\n\n private async performShutdown(): Promise<void> {\n // Trigger shutdown hook — ISOLATING dispatch, see the method's own\n // rationale. The two loops below already isolate per plugin and per\n // handler; before #5274 this line was the one teardown step that did\n // not, so a single throwing subscriber skipped BOTH of them.\n await this.triggerShutdownHookIsolating();\n\n // Destroy plugins in reverse order\n const orderedPlugins = Array.from(this.plugins.values()).reverse();\n for (const plugin of orderedPlugins) {\n if (plugin.destroy) {\n this.logger.debug(`Destroy: ${plugin.name}`, { plugin: plugin.name });\n try {\n await plugin.destroy();\n } catch (error) {\n this.logger.error(`Error destroying plugin ${plugin.name}`, error as Error);\n }\n }\n }\n\n // Execute custom shutdown handlers\n for (const handler of this.shutdownHandlers) {\n try {\n await handler();\n } catch (error) {\n this.logger.error('Shutdown handler error', error as Error);\n }\n }\n }\n\n /**\n * Topological order over `dependencies` (hard) + `optionalDependencies`\n * (order-if-present) — ADR-0116, #4131. One implementation shared with\n * LiteKernel via `plugin-order.ts`.\n */\n private resolveDependencies(): PluginMetadata[] {\n return resolvePluginOrder(this.plugins);\n }\n\n private registerShutdownSignals(): void {\n const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGQUIT'];\n let shutdownInProgress = false;\n \n const handleShutdown = async (signal: string) => {\n if (shutdownInProgress) {\n this.logger.warn(`Shutdown already in progress, ignoring ${signal}`);\n return;\n }\n \n shutdownInProgress = true;\n this.logger.info(`Received ${signal} - initiating graceful shutdown`);\n \n try {\n await this.shutdown();\n safeExit(0);\n } catch (error) {\n this.logger.error('Shutdown failed', error as Error);\n safeExit(1);\n }\n };\n \n if (isNode) {\n for (const signal of signals) {\n process.on(signal, () => handleShutdown(signal));\n }\n }\n }\n\n /**\n * Register a custom shutdown handler\n */\n onShutdown(handler: () => Promise<void>): void {\n this.shutdownHandlers.push(handler);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Plugin artifact signing & verification (ADR-0025 §3.4–§3.7, framework F3).\n *\n * This is the CANONICAL Ed25519 detached-signature contract shared by the\n * whole plugin distribution pipeline. It is intentionally byte-for-byte\n * compatible with the cloud control plane's `package-signing.ts` so the\n * two never drift:\n *\n * - signature string format: `ed25519:<keyId>:<base64url(signature)>`\n * - publisher signature: Ed25519 over the raw `.osplugin` artifact bytes,\n * produced by `os plugin sign`, verified by cloud at publish time and by\n * the runtime when it materializes the artifact.\n * - platform counter-signature: Ed25519 over {@link counterSignPayload}\n * (the version identity), produced by cloud at approval, verified by the\n * runtime at load time as the marketplace's \"reviewed + approved\" attest.\n *\n * Algorithm: Ed25519 via node:crypto (`sign(null, …)` / `verify(null, …)`):\n * short, deterministic, no padding ambiguity. The `keyId` is an opaque\n * rotation handle used to resolve the verifying public key.\n *\n * The two trust chains the runtime checks before loading a third-party\n * plugin are combined in {@link verifyPluginArtifact}.\n */\n\nimport {\n sign as cryptoSign,\n verify as cryptoVerify,\n createPublicKey,\n createPrivateKey,\n generateKeyPairSync,\n type KeyObject,\n} from 'node:crypto';\n\nexport const SIGNATURE_ALG = 'ed25519';\nconst SIG_PREFIX = 'ed25519:';\n\nexport type KeyInput = string | KeyObject;\n\nfunction toPrivateKey(key: KeyInput): KeyObject {\n return typeof key === 'string' ? createPrivateKey(key) : key;\n}\nfunction toPublicKey(key: KeyInput): KeyObject {\n return typeof key === 'string' ? createPublicKey(key) : key;\n}\nfunction toBytes(payload: string | Uint8Array): Uint8Array {\n return typeof payload === 'string' ? new TextEncoder().encode(payload) : payload;\n}\n\n/** Generate an Ed25519 keypair as PEM strings (publisher bootstrap / tests). */\nexport function generateEd25519KeyPair(): { publicKeyPem: string; privateKeyPem: string } {\n const { publicKey, privateKey } = generateKeyPairSync('ed25519');\n return {\n publicKeyPem: publicKey.export({ type: 'spki', format: 'pem' }).toString(),\n privateKeyPem: privateKey.export({ type: 'pkcs8', format: 'pem' }).toString(),\n };\n}\n\n/**\n * Sign `payload` with an Ed25519 private key, returning the formatted\n * signature string `ed25519:<keyId>:<base64url(sig)>`.\n */\nexport function signPayload(\n payload: string | Uint8Array,\n privateKey: KeyInput,\n keyId = 'default',\n): string {\n if (keyId.includes(':')) throw new Error('keyId must not contain \":\"');\n const sig = cryptoSign(null, toBytes(payload), toPrivateKey(privateKey));\n return `${SIG_PREFIX}${keyId}:${sig.toString('base64url')}`;\n}\n\nexport interface ParsedSignature {\n alg: 'ed25519';\n keyId: string;\n signature: Uint8Array;\n}\n\n/** Parse an `ed25519:<keyId>:<base64url>` signature string. Returns null if malformed. */\nexport function parseSignature(s: string | undefined | null): ParsedSignature | null {\n if (typeof s !== 'string' || !s.startsWith(SIG_PREFIX)) return null;\n const rest = s.slice(SIG_PREFIX.length);\n const idx = rest.indexOf(':');\n if (idx <= 0) return null;\n const keyId = rest.slice(0, idx);\n const b64 = rest.slice(idx + 1);\n if (!keyId || !b64) return null;\n try {\n return { alg: 'ed25519', keyId, signature: new Uint8Array(Buffer.from(b64, 'base64url')) };\n } catch {\n return null;\n }\n}\n\n/** Verify a formatted signature string over `payload` with the given public key. */\nexport function verifyPayload(\n payload: string | Uint8Array,\n signature: string,\n publicKey: KeyInput,\n): boolean {\n const parsed = parseSignature(signature);\n if (!parsed) return false;\n try {\n return cryptoVerify(null, toBytes(payload), toPublicKey(publicKey), parsed.signature);\n } catch {\n return false;\n }\n}\n\n/**\n * Canonical payload the platform counter-signs at approval. Binds the\n * attestation to the version identity + artifact location + the publisher\n * signature (which itself binds the artifact bytes). MUST match the cloud\n * control plane's `counterSignPayload` exactly.\n */\nexport function counterSignPayload(version: {\n package_id: string;\n version: string;\n blob_key?: string | null;\n signature?: string | null;\n}): string {\n return [\n version.package_id,\n version.version,\n version.blob_key ?? '',\n version.signature ?? '',\n ].join('\\n');\n}\n\nexport interface PublisherVerifyResult {\n /** Whether loading may proceed on signature grounds. */\n ok: boolean;\n /** True when a signature was present AND cryptographically verified. */\n verified: boolean;\n reason?: string;\n}\n\n/**\n * Verify a publisher signature over the raw artifact bytes. `getPublicKey`\n * resolves the verifying key from the signature's embedded keyId.\n *\n * Mirrors cloud's publish-time policy:\n * - no signature → ok, verified=false (caller decides via trust tier).\n * - malformed / fails verification → NOT ok.\n * - unknown keyId → NOT ok (never silently trust).\n */\nexport async function verifyPublisherSignature(\n args: { artifact: Uint8Array; signature?: string | null },\n getPublicKey?: (keyId: string) => Promise<KeyInput | null> | KeyInput | null,\n): Promise<PublisherVerifyResult> {\n const sig = args.signature;\n if (!sig) return { ok: true, verified: false, reason: 'no signature supplied' };\n\n const parsed = parseSignature(sig);\n if (!parsed) return { ok: false, verified: false, reason: 'signature is malformed' };\n\n if (!getPublicKey) {\n return { ok: true, verified: false, reason: 'no publisher key registry configured' };\n }\n\n const pub = await getPublicKey(parsed.keyId);\n if (!pub) return { ok: false, verified: false, reason: `unknown publisher key '${parsed.keyId}'` };\n\n return verifyPayload(args.artifact, sig, pub)\n ? { ok: true, verified: true }\n : { ok: false, verified: false, reason: 'publisher signature does not match artifact' };\n}\n\n/** Verify a platform counter-signature against the version identity + platform public key. */\nexport function verifyPlatformSignature(\n version: {\n package_id: string;\n version: string;\n blob_key?: string | null;\n signature?: string | null;\n platform_signature?: string | null;\n },\n platformPublicKey: KeyInput,\n): boolean {\n if (!version.platform_signature) return false;\n return verifyPayload(counterSignPayload(version), version.platform_signature, platformPublicKey);\n}\n\nexport interface PluginArtifactVerifyResult {\n /** Overall verdict: both required chains satisfied under the given policy. */\n ok: boolean;\n publisherVerified: boolean;\n platformVerified: boolean;\n reason?: string;\n}\n\n/**\n * Verify both trust chains for a downloaded plugin artifact at load time\n * (ADR-0025 §3.7). The platform counter-signature is the authoritative\n * marketplace attestation; the publisher signature additionally binds the\n * exact bytes. `requirePlatform` (default true) rejects artifacts that lack\n * a valid platform counter-sign — set false for first-party / local builds.\n */\nexport async function verifyPluginArtifact(\n input: {\n artifact: Uint8Array;\n version: {\n package_id: string;\n version: string;\n blob_key?: string | null;\n signature?: string | null;\n platform_signature?: string | null;\n };\n },\n keys: {\n platformPublicKey?: KeyInput;\n getPublisherPublicKey?: (keyId: string) => Promise<KeyInput | null> | KeyInput | null;\n requirePlatform?: boolean;\n },\n): Promise<PluginArtifactVerifyResult> {\n const requirePlatform = keys.requirePlatform ?? true;\n\n const publisher = await verifyPublisherSignature(\n { artifact: input.artifact, signature: input.version.signature },\n keys.getPublisherPublicKey,\n );\n if (!publisher.ok) {\n return { ok: false, publisherVerified: false, platformVerified: false, reason: publisher.reason };\n }\n\n let platformVerified = false;\n if (keys.platformPublicKey) {\n platformVerified = verifyPlatformSignature(input.version, keys.platformPublicKey);\n }\n if (requirePlatform && !platformVerified) {\n return {\n ok: false,\n publisherVerified: publisher.verified,\n platformVerified,\n reason: keys.platformPublicKey\n ? 'platform counter-signature missing or invalid'\n : 'no platform public key configured',\n };\n }\n\n return { ok: true, publisherVerified: publisher.verified, platformVerified };\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * [#13905] The discriminator that tells **\"nothing ever registered this\n * service\"** apart from **\"the service IS registered and could not be built\"**\n * on the ASYNCHRONOUS resolution path.\n *\n * ## The fault\n *\n * `PluginLoader.getService` (reached through `Kernel.getServiceAsync`) answered\n * both facts with the same bare `Error`. A caller that holds only the rejection\n * therefore could not tell an UNWIRED embedder from a BROKEN one, and the only\n * thing separating them was message text.\n *\n * That mattered one layer out. `RestServer.computeExecCtx`'s kernel branch\n * absorbs a failed `getServiceAsync('objectql')` and degrades to \"no engine is\n * wired\". It must keep doing so — a kernel with no data plane is a SUPPORTED\n * configuration (`rest-api-plugin.ts` declares\n * `optionalDependencies: ['com.objectstack.engine.objectql']`) — but a\n * multi-tenant host whose engine FAILED TO CONSTRUCT reached that same resolver\n * as \"no engine is wired\", degrading silently where it should have refused\n * loudly. The branch could not be repaired from the outside, because the fact\n * it needed had been collapsed before it arrived.\n *\n * ## Why a brand, and ⛔ not message text\n *\n * The SYNCHRONOUS accessor in `kernel.ts` already draws exactly this line, and\n * the comment there records what happened the last time someone read the fact\n * off the wrong surface: reading \"not found\" off the async path \"reported every\n * missing service as `is async - use await` — the wrong fix, pointing at the\n * wrong layer\". A second text classifier on a resolution path is the failure\n * mode this module removes, ⛔ not a repair of it.\n *\n * The sync side decides from the REGISTRY — synchronous and authoritative — and\n * raises two different messages. The async side now carries that same\n * distinction as a branded, `code`-bearing rejection: one fact, spelled for a\n * caller that only ever sees the rejection.\n *\n * ## The test is CLOSED, and its default is LOUD\n *\n * Exactly one throw in `PluginLoader.getService` means \"never registered\", and\n * it is the one branded here. Every other way that method can reject — a\n * factory that threw, a missing scope id, an unset loader context, a circular\n * service dependency — is a service that IS registered and could not be\n * produced, and stays unbranded. So `false` is the safe answer: a consumer that\n * absorbs only the branded rejection stays loud about everything else,\n * including rejections added to that method later.\n *\n * ## Two deliberate omissions\n *\n * - **No `status`.** An ADR-0112 envelope pairs `code` with a `status`, but\n * the whole point of this discriminator is that the CONSUMER decides what an\n * unwired service means — absorb and degrade (the supported no-data-plane\n * kernel) or refuse. Carrying an HTTP status here would presuppose that\n * decision at the layer that must not make it.\n * - **No `name` override.** The rejection stays `name: 'Error'` with a\n * byte-identical message, so `String(err)`, logs and existing assertions\n * render exactly as before. The only observable change is two added\n * own-properties.\n *\n * Brand shape follows `AuthzStoreUnavailableError` (2026-08-30): a string-keyed\n * own property rather than `instanceof`, so the predicate still answers\n * correctly when two copies of `@objectstack/core` are installed (a duplicated\n * module makes `instanceof` say \"no\" to an error it built itself).\n *\n * ⚠️ The brand does NOT survive `structuredClone`, and no claim here depends on\n * it doing so — measured on Node 22: cloning an `Error` keeps `name`, `message`,\n * `stack` and `cause` and DROPS every other own property, brand and `code`\n * alike. This discriminator is for an in-process rejection travelling from\n * `PluginLoader.getService` to a seam that catches it, which is the only path\n * it is used on.\n */\n\n/**\n * The code carried by the \"never registered\" rejection.\n *\n * ⚠️ Spelled the ADR-0112 way, but deliberately NOT wire vocabulary: this value\n * is read in-process by the seam that catches the rejection and is never\n * serialized into an `error.code` envelope. `dispatcher-error-vocabulary.ts`\n * classifies it `door: 'none'` / `boot-refusal` for exactly that reason — the\n * same class as the migration-journal runner refusals. If a transport ever\n * needs to ANSWER with this fact, that is a registration question for #8846's\n * ledger, ⛔ not something to start doing at a door.\n */\nexport const SERVICE_NOT_REGISTERED_CODE = 'SERVICE_NOT_REGISTERED';\n\n/**\n * The own-property brand {@link isServiceNotRegisteredError} tests for.\n * A plain string key rather than `instanceof` or a `Symbol.for` registry key,\n * so a duplicated copy of this module still brands identically. See the module\n * doc for what it deliberately does NOT claim.\n */\nconst SERVICE_NOT_REGISTERED_BRAND = '__objectstackServiceNotRegistered';\n\n/**\n * Build the rejection for \"no factory and no instance is registered under this\n * name\". Package-internal on purpose: `PluginLoader.getService` is the single\n * construction site, and `@objectstack/core` publishes only the two symbols a\n * CONSUMER needs ({@link SERVICE_NOT_REGISTERED_CODE} and\n * {@link isServiceNotRegisteredError}) — see `index.ts`.\n *\n * The message is kept verbatim: callers and tests that render or assert on it\n * must not move when the discriminator arrives.\n */\nexport function serviceNotRegisteredError(name: string): Error {\n const err = new Error(`Service '${name}' not found`) as Error & {\n [SERVICE_NOT_REGISTERED_BRAND]?: true;\n code?: string;\n serviceName?: string;\n };\n err[SERVICE_NOT_REGISTERED_BRAND] = true;\n err.code = SERVICE_NOT_REGISTERED_CODE;\n err.serviceName = name;\n return err;\n}\n\n/**\n * True when `err` is the rejection meaning **nothing was ever registered under\n * that service name** — never when a registered service failed to construct.\n *\n * The predicate a seam uses to keep absorbing the supported \"no data plane\"\n * composition while staying loud about a service that IS wired and broke.\n */\nexport function isServiceNotRegisteredError(\n err: unknown,\n): err is Error & { readonly code: typeof SERVICE_NOT_REGISTERED_CODE; readonly serviceName: string } {\n return (\n typeof err === 'object'\n && err !== null\n && (err as Record<string, unknown>)[SERVICE_NOT_REGISTERED_BRAND] === true\n );\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Plugin, PluginContext } from './types.js';\nimport type { Logger } from '@objectstack/spec/contracts';\nimport { parseSignature } from './security/plugin-artifact-signature.js';\nimport { serviceNotRegisteredError } from './service-not-registered.js';\n\n/**\n * Service Lifecycle Types\n * Defines how services are instantiated and managed\n */\nexport enum ServiceLifecycle {\n /** Single instance shared across all requests */\n SINGLETON = 'singleton',\n /** New instance created for each request */\n TRANSIENT = 'transient',\n /** New instance per scope (e.g., per HTTP request) */\n SCOPED = 'scoped',\n}\n\n/**\n * Service Factory\n * Function that creates a service instance\n */\nexport type ServiceFactory<T = any> = (ctx: PluginContext, scopeId?: string) => T | Promise<T>;\n\n/**\n * Service Registration Options\n */\nexport interface ServiceRegistration {\n name: string;\n factory: ServiceFactory;\n lifecycle: ServiceLifecycle;\n dependencies?: string[];\n}\n\n/**\n * Plugin Metadata with Enhanced Features\n */\nexport interface PluginMetadata extends Plugin {\n /** Semantic version (e.g., \"1.0.0\") */\n version: string;\n\n // `configSchema` was retired on 2026-08-27 (ADR-0049 enforce-or-remove;\n // recorded in ADR-0025 §3.7): the loader's only call passed no config and\n // no caller could — plugin factories close over their config, so the\n // kernel never receives it. Plugins parse their own config at their own\n // seam instead (the `packages/rest` pattern). Re-declaring a kernel-owned\n // config-validation surface is a fresh decision for the day the ADR-0025\n // distribution layer lands.\n\n /** Plugin signature for security verification */\n signature?: string;\n \n /** Plugin health check function */\n healthCheck?(): Promise<PluginHealthStatus>;\n \n /** Startup timeout in milliseconds (default: 30000) */\n startupTimeout?: number;\n\n // `hotReloadable` was retired on 2026-08-27 (#12587, same ADR-0049 batch):\n // declared and documented with zero reads — `HotReloadManager.reloadPlugin`\n // gates only on its own registered reload configs, so `hotReloadable:\n // false` was hot-reloaded identically to `true`. Reload participation is\n // governed solely by `HotReloadManager.registerReloadConfig`.\n}\n\n/**\n * Plugin Health Status\n */\nexport interface PluginHealthStatus {\n healthy: boolean;\n message?: string;\n details?: Record<string, any>;\n lastCheck?: Date;\n}\n\n/**\n * Plugin Load Result\n */\nexport interface PluginLoadResult {\n success: boolean;\n plugin?: PluginMetadata;\n error?: Error;\n loadTime?: number;\n}\n\n/**\n * Plugin Startup Result\n */\nexport interface PluginStartupResult {\n success: boolean;\n pluginName: string;\n startTime?: number;\n error?: Error;\n timedOut?: boolean;\n}\n\n/**\n * Version Compatibility Result\n */\nexport interface VersionCompatibility {\n compatible: boolean;\n pluginVersion: string;\n requiredVersion?: string;\n message?: string;\n}\n\n/**\n * Enhanced Plugin Loader\n * Provides advanced plugin loading capabilities with validation, security, and lifecycle management\n */\nexport class PluginLoader {\n private logger: Logger;\n private context?: PluginContext;\n private loadedPlugins: Map<string, PluginMetadata> = new Map();\n private serviceFactories: Map<string, ServiceRegistration> = new Map();\n private serviceInstances: Map<string, any> = new Map();\n private scopedServices: Map<string, Map<string, any>> = new Map();\n private creating: Set<string> = new Set();\n\n constructor(logger: Logger) {\n this.logger = logger;\n }\n\n /**\n * Set the plugin context for service factories\n */\n setContext(context: PluginContext): void {\n this.context = context;\n }\n\n /**\n * Get a synchronous service instance if it exists (Sync Helper)\n */\n getServiceInstance<T>(name: string): T | undefined {\n return this.serviceInstances.get(name) as T;\n }\n\n /**\n * Load a plugin asynchronously with validation\n */\n async loadPlugin(plugin: Plugin): Promise<PluginLoadResult> {\n const startTime = Date.now();\n \n try {\n this.logger.info(`Loading plugin: ${plugin.name}`);\n \n // Convert to PluginMetadata\n const metadata = this.toPluginMetadata(plugin);\n \n // Validate plugin structure\n this.validatePluginStructure(metadata);\n \n // Check version compatibility\n const versionCheck = this.checkVersionCompatibility(metadata);\n if (!versionCheck.compatible) {\n throw new Error(`Version incompatible: ${versionCheck.message}`);\n }\n \n // Verify signature if provided\n if (metadata.signature) {\n await this.verifyPluginSignature(metadata);\n }\n \n // Store loaded plugin\n this.loadedPlugins.set(metadata.name, metadata);\n \n const loadTime = Date.now() - startTime;\n this.logger.info(`Plugin loaded: ${plugin.name} (${loadTime}ms)`);\n \n return {\n success: true,\n plugin: metadata,\n loadTime,\n };\n } catch (error) {\n this.logger.error(`Failed to load plugin: ${plugin.name}`, error as Error);\n return {\n success: false,\n error: error as Error,\n loadTime: Date.now() - startTime,\n };\n }\n }\n\n /**\n * Register a service with factory function\n */\n registerServiceFactory(registration: ServiceRegistration): void {\n if (this.serviceFactories.has(registration.name)) {\n throw new Error(`Service factory '${registration.name}' already registered`);\n }\n \n this.serviceFactories.set(registration.name, registration);\n this.logger.debug(`Service factory registered: ${registration.name} (${registration.lifecycle})`);\n }\n\n /**\n * Get or create a service instance based on lifecycle type\n */\n async getService<T>(name: string, scopeId?: string): Promise<T> {\n const registration = this.serviceFactories.get(name);\n \n if (!registration) {\n // Fall back to static service instances\n const instance = this.serviceInstances.get(name);\n if (!instance) {\n // [#13905] The ONE rejection on this method that means \"nothing\n // was ever registered under this name\". Branded so a caller\n // holding only the rejection can tell it from a service that IS\n // registered and failed to construct (a factory that threw, a\n // missing scope id, an unset context, a circular dependency) —\n // which all reject from below, unbranded, and so stay loud.\n // The message is unchanged; the discriminator rides beside it.\n // ⛔ Not message text: see `service-not-registered.ts` for why\n // this repo does not classify a resolution fault by matching on\n // it.\n throw serviceNotRegisteredError(name);\n }\n return instance as T;\n }\n \n switch (registration.lifecycle) {\n case ServiceLifecycle.SINGLETON:\n return await this.getSingletonService<T>(registration);\n \n case ServiceLifecycle.TRANSIENT:\n return await this.createTransientService<T>(registration);\n \n case ServiceLifecycle.SCOPED:\n if (!scopeId) {\n throw new Error(`Scope ID required for scoped service '${name}'`);\n }\n return await this.getScopedService<T>(registration, scopeId);\n \n default:\n throw new Error(`Unknown service lifecycle: ${registration.lifecycle}`);\n }\n }\n\n /**\n * Register a static service instance (legacy support)\n */\n registerService(name: string, service: any): void {\n if (this.serviceInstances.has(name)) {\n throw new Error(`Service '${name}' already registered`);\n }\n this.serviceInstances.set(name, service);\n }\n\n /**\n * Replace an existing service instance.\n * Used by optimization plugins to swap kernel internals.\n * @throws Error if service does not exist\n */\n replaceService(name: string, service: any): void {\n if (!this.hasService(name)) {\n throw new Error(`Service '${name}' not found`);\n }\n this.serviceInstances.set(name, service);\n }\n\n /**\n * Check if a service is registered (either as instance or factory)\n */\n hasService(name: string): boolean {\n return this.serviceInstances.has(name) || this.serviceFactories.has(name);\n }\n\n /**\n * Detect circular dependencies in service factories\n * Note: This only detects cycles in service dependencies, not plugin dependencies.\n * Plugin dependency cycles are detected in the kernel's resolveDependencies method.\n */\n detectCircularDependencies(): string[] {\n const cycles: string[] = [];\n const visited = new Set<string>();\n const visiting = new Set<string>();\n \n const visit = (serviceName: string, path: string[] = []) => {\n if (visiting.has(serviceName)) {\n const cycle = [...path, serviceName].join(' -> ');\n cycles.push(cycle);\n return;\n }\n \n if (visited.has(serviceName)) {\n return;\n }\n \n visiting.add(serviceName);\n \n const registration = this.serviceFactories.get(serviceName);\n if (registration?.dependencies) {\n for (const dep of registration.dependencies) {\n visit(dep, [...path, serviceName]);\n }\n }\n \n visiting.delete(serviceName);\n visited.add(serviceName);\n };\n \n for (const serviceName of this.serviceFactories.keys()) {\n visit(serviceName);\n }\n \n return cycles;\n }\n\n /**\n * Check plugin health\n */\n async checkPluginHealth(pluginName: string): Promise<PluginHealthStatus> {\n const plugin = this.loadedPlugins.get(pluginName);\n \n if (!plugin) {\n return {\n healthy: false,\n message: 'Plugin not found',\n lastCheck: new Date(),\n };\n }\n \n if (!plugin.healthCheck) {\n return {\n healthy: true,\n message: 'No health check defined',\n lastCheck: new Date(),\n };\n }\n \n try {\n const status = await plugin.healthCheck();\n return {\n ...status,\n lastCheck: new Date(),\n };\n } catch (error) {\n return {\n healthy: false,\n message: `Health check failed: ${(error as Error).message}`,\n lastCheck: new Date(),\n };\n }\n }\n\n /**\n * Clear scoped services for a scope\n */\n clearScope(scopeId: string): void {\n this.scopedServices.delete(scopeId);\n this.logger.debug(`Cleared scope: ${scopeId}`);\n }\n\n /**\n * Get all loaded plugins\n */\n getLoadedPlugins(): Map<string, PluginMetadata> {\n return new Map(this.loadedPlugins);\n }\n\n // Private helper methods\n\n private toPluginMetadata(plugin: Plugin): PluginMetadata {\n // Fix: Do not use object spread {...plugin} as it destroys the prototype chain for Class-based plugins.\n // Instead, cast the original object and inject default values if missing.\n const metadata = plugin as PluginMetadata;\n \n if (!metadata.version) {\n metadata.version = '0.0.0';\n }\n \n return metadata;\n }\n\n private validatePluginStructure(plugin: PluginMetadata): void {\n if (!plugin.name) {\n throw new Error('Plugin name is required');\n }\n \n if (!plugin.init) {\n throw new Error('Plugin init function is required');\n }\n \n if (!this.isValidSemanticVersion(plugin.version)) {\n throw new Error(`Invalid semantic version: ${plugin.version}`);\n }\n }\n\n private checkVersionCompatibility(plugin: PluginMetadata): VersionCompatibility {\n // Basic semantic version compatibility check\n // In a real implementation, this would check against kernel version\n const version = plugin.version;\n \n if (!this.isValidSemanticVersion(version)) {\n return {\n compatible: false,\n pluginVersion: version,\n message: 'Invalid semantic version format',\n };\n }\n \n return {\n compatible: true,\n pluginVersion: version,\n };\n }\n\n private isValidSemanticVersion(version: string): boolean {\n const semverRegex = /^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?(\\+[a-zA-Z0-9.-]+)?$/;\n return semverRegex.test(version);\n }\n\n private async verifyPluginSignature(plugin: PluginMetadata): Promise<void> {\n if (!plugin.signature) {\n return;\n }\n \n // Cryptographic verification of a third-party plugin's PUBLISHER and\n // PLATFORM signatures is performed against the `.osplugin` artifact\n // bytes + version identity at materialize/install time, by\n // `verifyPluginArtifact` (security/plugin-artifact-signature.ts —\n // ADR-0025 §3.7). By the time a plugin reaches loadPlugin() it is an\n // in-memory module with no artifact bytes, so we cannot re-run the\n // artifact chains here; we only validate that any signature carried\n // on the metadata is well-formed (`ed25519:<keyId>:<base64url>`) and\n // surface its keyId, failing fast on a malformed value.\n const parsed = parseSignature(plugin.signature);\n if (!parsed) {\n throw new Error(\n `Plugin ${plugin.name} carries a malformed signature (expected ed25519:<keyId>:<base64url>)`,\n );\n }\n this.logger.debug(\n `Plugin ${plugin.name} signature well-formed (alg=${parsed.alg}, keyId=${parsed.keyId}); ` +\n `artifact verification occurs at materialize time`,\n );\n }\n\n private async getSingletonService<T>(registration: ServiceRegistration): Promise<T> {\n let instance = this.serviceInstances.get(registration.name);\n \n if (!instance) {\n // Create instance (would need context)\n instance = await this.createServiceInstance(registration);\n this.serviceInstances.set(registration.name, instance);\n this.logger.debug(`Singleton service created: ${registration.name}`);\n }\n \n return instance as T;\n }\n\n private async createTransientService<T>(registration: ServiceRegistration): Promise<T> {\n const instance = await this.createServiceInstance(registration);\n this.logger.debug(`Transient service created: ${registration.name}`);\n return instance as T;\n }\n\n private async getScopedService<T>(registration: ServiceRegistration, scopeId: string): Promise<T> {\n if (!this.scopedServices.has(scopeId)) {\n this.scopedServices.set(scopeId, new Map());\n }\n\n const scope = this.scopedServices.get(scopeId)!;\n let instance = scope.get(registration.name);\n\n if (!instance) {\n instance = await this.createServiceInstance(registration, scopeId);\n scope.set(registration.name, instance);\n this.logger.debug(`Scoped service created: ${registration.name} (scope: ${scopeId})`);\n }\n\n return instance as T;\n }\n\n private async createServiceInstance(registration: ServiceRegistration, scopeId?: string): Promise<any> {\n if (!this.context) {\n throw new Error(`[PluginLoader] Context not set - cannot create service '${registration.name}'`);\n }\n\n if (this.creating.has(registration.name)) {\n throw new Error(`Circular dependency detected: ${Array.from(this.creating).join(' -> ')} -> ${registration.name}`);\n }\n\n this.creating.add(registration.name);\n try {\n return await registration.factory(this.context, scopeId);\n } finally {\n this.creating.delete(registration.name);\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Environment utilities for universal (Node/Browser) compatibility.\n */\n\n// Check if running in a Node.js environment\nexport const isNode = typeof process !== 'undefined' && \n process.versions != null && \n process.versions.node != null;\n\n/**\n * Safely access environment variables\n */\nexport function getEnv(key: string, defaultValue?: string): string | undefined {\n // Node.js\n if (typeof process !== 'undefined' && process.env) {\n return process.env[key] || defaultValue;\n }\n \n // Browser (Vite/Webpack replacement usually handles process.env, \n // but if not, we check safe global access)\n try {\n // @ts-ignore\n if (typeof globalThis !== 'undefined' && globalThis.process?.env) {\n // @ts-ignore\n return globalThis.process.env[key] || defaultValue;\n }\n } catch (e) {\n // Ignore access errors\n }\n \n return defaultValue;\n}\n\n/**\n * Safely exit the process if in Node.js\n */\nexport function safeExit(code: number = 0): void {\n if (isNode) {\n process.exit(code);\n }\n}\n\n/**\n * Safely get memory usage\n */\nexport function getMemoryUsage(): { heapUsed: number; heapTotal: number } {\n if (isNode) {\n return process.memoryUsage();\n }\n return { heapUsed: 0, heapTotal: 0 };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * In-memory Map-backed cache fallback.\n *\n * Implements the ICacheService contract with basic get/set/delete/has/clear\n * and TTL expiry. Used by ObjectKernel as an automatic fallback when no\n * real cache plugin (e.g. Redis) is registered.\n *\n * [#4058] Self-describes as `degraded`, not `stub` (ADR-0076 D12): this is a\n * real cache — it stores, expires, and reports true stats — just process-local\n * and unshared. The non-standard `_fallback: true` it used to carry was read by\n * nothing (`readServiceSelfInfo` reads only `__serviceInfo` — `_dev`, the other\n * marker it knew back then, was itself retired in #4319), so discovery reported\n * it as fully `available`. `handlerReady: false` because\n * no HTTP surface is mounted for `cache` at all — the same reason realtime\n * reports false.\n */\nexport function createMemoryCache() {\n const store = new Map<string, { value: unknown; expires?: number }>();\n let hits = 0;\n let misses = 0;\n return {\n __serviceInfo: {\n status: 'degraded' as const,\n handlerReady: false,\n message: 'In-process Map cache — not shared across instances, lost on restart. Register a cache plugin (e.g. Redis) for a real one.',\n },\n _serviceName: 'cache',\n async get<T = unknown>(key: string): Promise<T | undefined> {\n const entry = store.get(key);\n if (!entry || (entry.expires && Date.now() > entry.expires)) {\n store.delete(key);\n misses++;\n return undefined;\n }\n hits++;\n return entry.value as T;\n },\n async set<T = unknown>(key: string, value: T, ttl?: number): Promise<void> {\n store.set(key, { value, expires: ttl ? Date.now() + ttl * 1000 : undefined });\n },\n async delete(key: string): Promise<boolean> { return store.delete(key); },\n async has(key: string): Promise<boolean> { return store.has(key); },\n async clear(): Promise<void> { store.clear(); },\n async stats() { return { hits, misses, keyCount: store.size }; },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * In-memory publish/subscribe queue fallback.\n *\n * Implements the IQueueService contract with synchronous in-process delivery.\n * Used by ObjectKernel as an automatic fallback when no real queue plugin\n * (e.g. BullMQ / RabbitMQ) is registered.\n *\n * [#4058] `degraded`, not `stub` (ADR-0076 D12): messages really reach real\n * subscribers — synchronously, in-process, with no durability or retry.\n * `getQueueSize()` answering 0 follows from that rather than faking it: nothing\n * is ever buffered. `handlerReady: false` — no HTTP surface exists for `queue`.\n */\nexport function createMemoryQueue() {\n const handlers = new Map<string, Function[]>();\n let msgId = 0;\n return {\n __serviceInfo: {\n status: 'degraded' as const,\n handlerReady: false,\n message: 'Synchronous in-process delivery — no durability, retry, or cross-instance fan-out. Register a queue plugin (e.g. BullMQ) for a real one.',\n },\n _serviceName: 'queue',\n async publish<T = unknown>(queue: string, data: T): Promise<string> {\n const id = `fallback-msg-${++msgId}`;\n const fns = handlers.get(queue) ?? [];\n for (const fn of fns) fn({ id, data, attempts: 1, timestamp: Date.now() });\n return id;\n },\n async subscribe(queue: string, handler: (msg: any) => Promise<void>): Promise<void> {\n handlers.set(queue, [...(handlers.get(queue) ?? []), handler]);\n },\n async unsubscribe(queue: string): Promise<void> { handlers.delete(queue); },\n async getQueueSize(): Promise<number> { return 0; },\n async purge(queue: string): Promise<void> { handlers.delete(queue); },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { normalizeSupportedLocales } from '@objectstack/spec/system';\n\n/**\n * Recursively merge `source` into `target`. Nested plain objects are merged\n * rather than replaced, so multiple plugins can each contribute their own\n * slice of a locale's translations (e.g. `{objects: {account: ...}}` and\n * `{objects: {task: ...}}`) without clobbering one another.\n * Exported for the authored-translation sync (#2591).\n */\nexport function deepMerge(\n target: Record<string, unknown>,\n source: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...target };\n for (const key of Object.keys(source)) {\n const tVal = target[key];\n const sVal = source[key];\n if (\n tVal && sVal\n && typeof tVal === 'object' && !Array.isArray(tVal)\n && typeof sVal === 'object' && !Array.isArray(sVal)\n ) {\n result[key] = deepMerge(\n tVal as Record<string, unknown>,\n sVal as Record<string, unknown>,\n );\n } else {\n result[key] = sVal;\n }\n }\n return result;\n}\n\n/**\n * Resolve a locale code against available locales with fallback.\n *\n * Fallback chain:\n * 1. Exact match (e.g. `zh-CN` → `zh-CN`)\n * 2. Case-insensitive match (e.g. `zh-cn` → `zh-CN`)\n * 3. Base language match (e.g. `zh-CN` → `zh`)\n * 4. Variant expansion (e.g. `zh` → `zh-CN`)\n *\n * Returns the matched locale code, or `undefined` when no match is found.\n */\nexport function resolveLocale(requestedLocale: string, availableLocales: string[]): string | undefined {\n if (availableLocales.length === 0) return undefined;\n\n // 1. Exact match\n if (availableLocales.includes(requestedLocale)) return requestedLocale;\n\n // 2. Case-insensitive match\n const lower = requestedLocale.toLowerCase();\n const caseMatch = availableLocales.find(l => l.toLowerCase() === lower);\n if (caseMatch) return caseMatch;\n\n // 3. Base language match (zh-CN → zh)\n const baseLang = requestedLocale.split('-')[0].toLowerCase();\n const baseMatch = availableLocales.find(l => l.toLowerCase() === baseLang);\n if (baseMatch) return baseMatch;\n\n // 4. Variant expansion (zh → zh-CN, zh-TW, etc. — first match wins)\n const variantMatch = availableLocales.find(l => l.split('-')[0].toLowerCase() === baseLang);\n if (variantMatch) return variantMatch;\n\n return undefined;\n}\n\n/**\n * In-memory i18n service fallback.\n *\n * Implements the II18nService contract with basic translate/load/getLocales\n * operations. Used by ObjectKernel as an automatic fallback when no real\n * i18n plugin (e.g. I18nServicePlugin) is registered.\n *\n * Supports runtime translation loading, locale management, and\n * locale code fallback (e.g. `zh` → `zh-CN`).\n * Does not load files from disk — operates purely in-memory.\n */\nexport function createMemoryI18n() {\n const translations = new Map<string, Record<string, unknown>>();\n // Runtime-AUTHORED overlay (#2591): translations published as `translation`\n // metadata. Kept separate from the static map so a re-sync can REPLACE the\n // whole authored layer (clear-then-reload — deleted keys must not linger),\n // while authored values win over static bundle values on read.\n const authored = new Map<string, Record<string, unknown>>();\n let defaultLocale = 'en';\n // [#7679] The app's DECLARED `i18n.supportedLocales`, injected by\n // `AppPlugin.loadTranslations` the same way `defaultLocale` is. `undefined`\n // means the app declared nothing, which must keep reporting every loaded\n // locale. Held as a read-time filter, never as a prune of `translations`:\n // platform plugins push their bundles at `kernel:ready`, after the app\n // plugin has run, so anything pruned once would grow back.\n let supportedLocales: string[] | undefined;\n\n /**\n * Resolve a dot-notation key from a nested object.\n */\n function resolveKey(data: Record<string, unknown>, key: string): string | undefined {\n const parts = key.split('.');\n let current: unknown = data;\n for (const part of parts) {\n if (current == null || typeof current !== 'object') return undefined;\n current = (current as Record<string, unknown>)[part];\n }\n return typeof current === 'string' ? current : undefined;\n }\n\n /** Merged (static ⊕ authored) view of a single, exact locale key. */\n function mergedLocale(locale: string): Record<string, unknown> | undefined {\n const stat = translations.get(locale);\n const auth = authored.get(locale);\n if (stat && auth) return deepMerge(stat, auth);\n return auth ?? stat;\n }\n\n /**\n * Find translation data for a locale, with fallback resolution.\n */\n function resolveTranslations(locale: string): Record<string, unknown> | undefined {\n // Exact match\n const exact = mergedLocale(locale);\n if (exact) return exact;\n\n // Locale fallback (zh → zh-CN, en-us → en-US, etc.)\n const allLocales = [...new Set([...translations.keys(), ...authored.keys()])];\n const resolved = resolveLocale(locale, allLocales);\n if (resolved) return mergedLocale(resolved);\n\n return undefined;\n }\n\n return {\n // [#4058] `degraded` (ADR-0076 D12): translations, locale fallback and\n // interpolation are all real — what is missing is persistence and the\n // authoring surface service-i18n adds. `handlerReady` left at the\n // `degraded` default (true): the dispatcher's `/i18n` domain does serve\n // this implementation.\n __serviceInfo: {\n status: 'degraded' as const,\n message: 'In-memory translations — real lookup and locale fallback, but nothing is persisted. Register I18nServicePlugin from @objectstack/service-i18n for the full implementation.',\n },\n _serviceName: 'i18n',\n\n t(key: string, locale: string, params?: Record<string, unknown>): string {\n const data = resolveTranslations(locale) ?? mergedLocale(defaultLocale);\n const value = data ? resolveKey(data, key) : undefined;\n if (value == null) return key;\n if (!params) return value;\n // Interpolation format: {{paramName}} — matches FileI18nAdapter convention\n return value.replace(/\\{\\{(\\w+)\\}\\}/g, (_, name) => String(params[name] ?? `{{${name}}}`));\n },\n\n getTranslations(locale: string): Record<string, unknown> {\n return resolveTranslations(locale) ?? {};\n },\n\n loadTranslations(locale: string, data: Record<string, unknown>): void {\n const existing = translations.get(locale);\n if (existing) {\n translations.set(locale, deepMerge(existing, data));\n } else {\n translations.set(locale, { ...data });\n }\n },\n\n /**\n * Replace the ENTIRE runtime-authored translation layer (#2591). Called\n * by the authored-translation sync with the full current set of active\n * `translation` metadata items keyed by locale. Wholesale replacement —\n * not a merge — so deleted items/keys stop resolving on the next sync.\n */\n replaceAuthoredTranslations(byLocale: Record<string, Record<string, unknown>>): void {\n authored.clear();\n for (const [locale, data] of Object.entries(byLocale ?? {})) {\n if (!data || typeof data !== 'object') continue;\n authored.set(locale, { ...data });\n }\n },\n\n /**\n * Report the locales this stack offers.\n *\n * [#7679] When the app declared `i18n.supportedLocales`, that declaration\n * IS the answer — in declared order, and including a declared locale no\n * bundle was ever loaded for (declared-but-unserved). Reporting the\n * declaration rather than an intersection is what gives a client the\n * signal that the locale it is being offered has nothing behind it yet;\n * quietly dropping it would leave the gap invisible on both sides. It is\n * also the only answer that does not depend on how much had loaded by the\n * time this was called.\n *\n * With nothing declared, the loaded set — the behaviour every app that\n * never opted in already has.\n */\n getLocales(): string[] {\n if (supportedLocales) return [...supportedLocales];\n return [...new Set([...translations.keys(), ...authored.keys()])];\n },\n\n /** @see II18nService.setSupportedLocales — [#7679] */\n setSupportedLocales(locales: readonly string[] | undefined): void {\n supportedLocales = normalizeSupportedLocales(locales);\n },\n\n getDefaultLocale(): string {\n return defaultLocale;\n },\n\n setDefaultLocale(locale: string): void {\n defaultLocale = locale;\n },\n };\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * [#7378] The `IMetadataService` register/read argument contract, enforced —\n * the shared guard every shipped implementation of the contract's CRUD members\n * calls, so the maintainer's three-cell ruling has ONE implementation instead\n * of three re-derivations that can drift.\n *\n * Maintainer ruling, 2026-08-12 (#7378, 裁定人:维护者 huangyiirene), quoted\n * verbatim and untranslated:\n *\n * > 1. **Row 1(key 归属)= (c) 响亮拒绝。** `register(type, name, data)` 中\n * > `name` 参数与 `data.name` 不一致时,所有实现统一拒绝并报错定位 ——\n * > 不一致几乎必是作者 bug,任一方向的静默解决都可能把条目放错位置。\n * > 2. **Row 2(objects/object 别名)= 所有实现一个答案,与\n * > `check:meta-type-normalized` 收敛。** 类型名归一化是契约级规则,不是\n * > 各实现自留的民俗。\n * > 3. **Row 3(非对象 data 静默丢弃)= 响亮拒绝(throw)。** 接受后丢失、且\n * > 任何成员都读不回来,无可辩护;拒绝一个实现无法键控的 `data` 与契约\n * > 同样一致。修的是「接受再丢」,不强求「必须存下」。\n *\n * This module hosts rows 1 and 3 ({@link assertMetadataRegisterContract}) and\n * row 2 ({@link canonicalMetadataServiceType}). It lives in `@objectstack/core`\n * because core is the lowest common dependency of the three shipped\n * implementations — `createMemoryMetadata` (this package), `MetadataManager`\n * (`@objectstack/metadata`) and `MetadataFacade` (`@objectstack/objectql`).\n * The contract's own reference double (`packages/spec`) cannot import from\n * here — spec is the dependency root — so its copy of these rules rides the\n * spec-side half of the ruling, tracked on #7378.\n *\n * ## Row 2 — why the fold, and whose direction it is\n *\n * Plural→singular type-name folding is a decided, enforced platform direction,\n * not this module's invention. The owners this converges with, per the ruling's\n * own instruction to read the gate's existing direction first (「实现者先读该\n * 闸门的既有方向再落」):\n *\n * - `canonicalMetaType` (`metadata-protocol/src/protocol.ts`, #4432)\n * canonicalizes every `/meta` request type at the protocol boundary via the\n * same `PLURAL_TO_SINGULAR` map this module reads;\n * - `check:meta-type-normalized` (`scripts/check-meta-type-normalized.mjs`)\n * is the CI gate whose whole job is to refuse a DECISION made on the\n * un-normalized `:type` — its header carries the three authorization\n * bypasses (#3984, #5881, #6241) that made the direction a rule. Its scan\n * surface is `packages/rest/src`; what this module converges with is its\n * DIRECTION: normalize once, at the entry, and let every decision — here,\n * every store key — read the normalized value;\n * - Prime Directive #3: metadata type names are canonically **singular**.\n *\n * Before this ruling, `MetadataManager` and `createMemoryMetadata` keyed their\n * type stores on the raw string, so `register('objects', n, d)` landed in a\n * store `get('object', n)` never read — two stores for one type, differing\n * from `MetadataFacade`, whose `SchemaRegistry` reads alias both spellings.\n * One answer now: the store key is the canonical type.\n *\n * ## Rows 1 and 3 — what the refusals close\n *\n * Row 1: a `data.name` that disagrees with the `name` argument was resolved\n * silently in both directions in shipped code — argument-wins\n * (`MetadataManager`, `createMemoryMetadata`) and document-wins (the\n * pre-ruling `MetadataFacade`) — and either way an author's item could be\n * filed under a key the author never wrote. Refusing is the only answer that\n * cannot misplace the item.\n *\n * Row 3: a `data` that is not a plain object cannot be a metadata document.\n * The pre-ruling `MetadataFacade` accepted such a write and filed it under the\n * literal key `undefined` — readable back through no member (silent loss, the\n * #6725 family) — and the interim fix coerced it into a `{ name, content }`\n * box, which collides with `content` being a REAL authorable field on live\n * metadata types (`doc`, `knowledge_document`). The ruling forbids both:\n * refuse, do not coerce into storability. `null` and arrays are refused with\n * primitives — neither can carry the document identity a metadata store keys\n * on, and `{ ...[a, b] }` is `{ 0: a, 1: b }`, the same corruption one shape\n * over.\n *\n * The executable form of all three rows is `METADATA_ROUNDTRIP_CASES`\n * (`@objectstack/spec/contracts`) replayed by\n * `packages/objectql/src/metadata-service-roundtrip-conformance.test.ts`.\n */\n\n// The `/api` import is TYPE-ONLY on purpose — erased at compile time, so this\n// module makes no runtime demand on that subpath. This module is loaded by\n// every consumer of `@objectstack/core`, and several packages' vitest configs\n// alias the bare `@objectstack/spec` specifier to `spec/src/index.ts` (a FILE)\n// with per-subpath entries spelled out above it; an alias list matches by\n// PREFIX, so any subpath NOT spelled out resolves under the file and dies with\n// ENOTDIR at import time (measured: `@objectstack/plugin-hono-server` and\n// `@objectstack/driver-memory`, 39 test files dead at load between them). The\n// typed literal below keeps the closed-set compile check without the runtime\n// import — the `packages/spec/src/contracts/storage-service.ts` pattern.\n// `pluralToSingular` cannot get the same treatment: it is a runtime value and\n// its map has ONE owner (#7378 row 2 — copying it here would be the\n// per-implementation folk normalization the ruling forbids). It is imported\n// from `/meta-spelling` — the schema-free vocabulary entry — NOT `/shared`:\n// this module is on every browser consumer's eager graph through\n// `@objectstack/client`, and a `/shared` value import here linked the whole\n// zod schema closure into every browser bundle (#11503, measured in the\n// console; #10096 standing principle: 「浏览器可达的 spec 导出面必须\n// schema-free」). Consumer vitest configs resolve the subpath through their\n// anchored `@objectstack/spec/<ns>` alias rule (#9457).\nimport type { StandardErrorCode } from '@objectstack/spec/api';\nimport { pluralToSingular } from '@objectstack/spec/meta-spelling';\n\n/** The standard catalog's generic argument-validation code, type-checked against the closed set. */\nconst REGISTER_REFUSAL_CODE: StandardErrorCode = 'VALIDATION_ERROR';\n\n/**\n * The canonical spelling an `IMetadataService` type store is keyed on\n * (#7378 row 2). Folds a plural manifest spelling to the singular metadata\n * type name (`'objects'` → `'object'`, `'views'` → `'view'`, …) through the\n * platform's one plural↔singular map (`PLURAL_TO_SINGULAR`,\n * `@objectstack/spec/meta-spelling`); a name with no plural mapping — which includes\n * every canonical singular type — passes through unchanged.\n */\nexport function canonicalMetadataServiceType(type: string): string {\n return pluralToSingular(type);\n}\n\n/**\n * An ADR-0112-enveloped refusal (`code` + `status` on the error), so a caller\n * — and a rejection-class test — can assert the refusal rather than merely\n * \"it threw\". `VALIDATION_ERROR` is the standard catalog's generic\n * argument-validation code; the ledger's own guidance is to use the standard\n * catalog rather than register a synonym for a generic condition.\n */\nfunction registerRefusal(message: string): Error & { code: string; status: number } {\n const err = new Error(message) as Error & { code: string; status: number };\n err.code = REGISTER_REFUSAL_CODE;\n err.status = 400;\n return err;\n}\n\n/**\n * Enforce rows 1 and 3 of the #7378 ruling on a\n * `register(type, name, data)` payload — call it before the first store write,\n * so a refusal writes nothing anywhere.\n *\n * Refuses, with a locating `VALIDATION_ERROR` (status 400):\n *\n * - **a non-document `data`** (row 3): anything that is not a plain object —\n * primitives, `null`, arrays. The contract declares `data: unknown`, so\n * this is a runtime refusal, not a type error;\n * - **a `data.name` that disagrees with the `name` argument** (row 1), in\n * either direction. A document with NO `name` of its own is fine — the\n * argument is the key, and there is no disagreement to refuse.\n *\n * Deliberately NOT called by `registerInMemory`: that optional member is a\n * boot-time seeding primitive outside the ruled surface (the ruling names\n * `register`), and its callers hand it artefacts whose shape source control\n * owns. It shares the row-2 canonical fold — a store key is a store fact, not\n * a per-member choice — just not the refusals.\n */\nexport function assertMetadataRegisterContract(\n type: string,\n name: string,\n data: unknown,\n): asserts data is Record<string, unknown> {\n if (typeof data !== 'object' || data === null || Array.isArray(data)) {\n const shape = data === null ? 'null' : Array.isArray(data) ? 'an array' : `a ${typeof data}`;\n throw registerRefusal(\n `IMetadataService.register('${type}', '${name}'): data is ${shape}, not a metadata document. ` +\n `register() stores plain-object documents only — accepting a value the service cannot key was measured as ` +\n `accept-then-drop on document-keyed stores (#7378 row 3: refuse loudly, never coerce into storability). ` +\n `Wrap the value in a document object whose shape the '${type}' type's schema accepts, or store it under a type that declares one.`,\n );\n }\n const documentName = (data as { name?: unknown }).name;\n if (documentName !== undefined && documentName !== name) {\n throw registerRefusal(\n `IMetadataService.register('${type}', '${name}'): data.name is '${String(documentName)}', which disagrees with the ` +\n `name argument '${name}'. A disagreement is almost always an authoring bug, and resolving it silently in either ` +\n `direction can file the item under a key the caller never wrote (#7378 row 1: refuse loudly, locate the mismatch). ` +\n `Register under one name: pass the intended key as the argument and make data.name match it, or omit data.name.`,\n );\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport {\n assertMetadataRegisterContract,\n canonicalMetadataServiceType,\n} from '../metadata-service-contract.js';\n\n/**\n * In-memory metadata service fallback.\n *\n * Implements the IMetadataService contract with a simple Map-of-Maps store.\n * Used by ObjectKernel as an automatic fallback when no real metadata plugin\n * (e.g. MetadataPlugin with file-system persistence) is registered.\n *\n * [#7378] Carries the ruled register/read argument contract\n * (`../metadata-service-contract.ts` — the ruling is quoted there):\n * `register` refuses a `data.name` that disagrees with the `name` argument and\n * refuses a non-document `data` (rows 1/3), and every type store is keyed on\n * the CANONICAL type (row 2), so `register('objects', n, d)` and\n * `get('object', n)` address one store rather than two.\n */\nexport function createMemoryMetadata() {\n // canonical type -> name -> data\n const store = new Map<string, Map<string, any>>();\n\n // [#7378 row 2] The fold lives on the single accessor every member reads\n // and writes through, so no member can address a raw-spelling store.\n function getTypeMap(type: string): Map<string, any> {\n const canonical = canonicalMetadataServiceType(type);\n let map = store.get(canonical);\n if (!map) {\n map = new Map();\n store.set(canonical, map);\n }\n return map;\n }\n\n return {\n // [#4058] `degraded` (ADR-0076 D12): the registry is real — everything\n // registered is listable and readable back — it simply never reaches disk\n // or a database. `handlerReady` keeps the `degraded` default (true): the\n // dispatcher's `/meta` domain serves this implementation.\n __serviceInfo: {\n status: 'degraded' as const,\n message: 'In-memory metadata registry — real reads and writes, no persistence (lost on restart). Register MetadataPlugin for a persisted registry.',\n },\n _serviceName: 'metadata',\n async register(type: string, name: string, data: any): Promise<void> {\n // [#7378 rows 1/3] Refuse — before the store is touched — a data.name\n // that disagrees with the name argument, and a non-document data. The\n // guard's own header carries the ruling and the reasons.\n assertMetadataRegisterContract(type, name, data);\n getTypeMap(type).set(name, data);\n },\n // Mirror MetadataManager.registerInMemory (synchronous, no persistence).\n // AppPlugin gates code-defined-datasource / stack-RBAC registration on\n // `typeof metadata.registerInMemory === 'function'` (it must register\n // GitOps-managed artefacts *listably* but never persist them). Without this\n // method the guard was false on the host-config / standalone boot path —\n // where this fallback (not MetadataPlugin) provides the `metadata` service —\n // so `defineStack({ datasources })` entries silently never reached the\n // registry and were absent from GET /api/v1/datasources and\n // GET /api/v1/meta/datasource (ADR-0015 §18). This store is already\n // in-memory only, so registerInMemory and register share a store — but\n // NOT the [#7378] refusals: the ruling names `register`, and this member\n // is a boot-time seeding primitive for source-control-owned artefacts\n // (see assertMetadataRegisterContract's header for the boundary). It does\n // share the row-2 canonical type fold, via getTypeMap.\n registerInMemory(type: string, name: string, data: any): void {\n getTypeMap(type).set(name, data);\n },\n async get(type: string, name: string): Promise<any> {\n return getTypeMap(type).get(name);\n },\n async list(type: string): Promise<any[]> {\n return Array.from(getTypeMap(type).values());\n },\n async unregister(type: string, name: string): Promise<void> {\n getTypeMap(type).delete(name);\n },\n async exists(type: string, name: string): Promise<boolean> {\n return getTypeMap(type).has(name);\n },\n async listNames(type: string): Promise<string[]> {\n return Array.from(getTypeMap(type).keys());\n },\n async getObject(name: string): Promise<any> {\n return getTypeMap('object').get(name);\n },\n async listObjects(): Promise<any[]> {\n return Array.from(getTypeMap('object').values());\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * In-memory job registry — schedule/cancel/trigger bookkeeping with NO timer.\n *\n * [#10746] NOT pre-injected by ObjectKernel any more (it used to be, via\n * `CORE_FALLBACK_FACTORIES`): a fallback must not fake capability (maintainer\n * ruling 2026-08-22). Advertising a `schedule()` that records and never fires\n * made every \"prefer the platform job service, else own a timer\" consumer\n * take the job-service branch and then silently never run. The export remains\n * for embedders who deliberately want a manual-trigger job registry — e.g. in\n * tests that drive handlers via `trigger()` — and have read this docblock.\n *\n * [#4058] `degraded` (ADR-0076 D12), with the missing half named in the\n * message rather than left for a deployer to discover: `trigger()` really runs\n * the registered handler, but nothing here owns a timer, so a `schedule()`d job\n * NEVER fires on its own. That is reduced capability, not fabricated output —\n * no call returns a made-up answer. `handlerReady: false`: no HTTP surface.\n */\nexport function createMemoryJob() {\n const jobs = new Map<string, any>();\n return {\n __serviceInfo: {\n status: 'degraded' as const,\n handlerReady: false,\n message: 'In-process job registry — trigger() runs handlers, but scheduled jobs never fire on their own (no timer). Register a job plugin (e.g. Agenda) for real scheduling.',\n },\n _serviceName: 'job',\n async schedule(name: string, schedule: any, handler: any): Promise<void> { jobs.set(name, { schedule, handler }); },\n async cancel(name: string): Promise<void> { jobs.delete(name); },\n async trigger(name: string, data?: unknown): Promise<void> {\n const job = jobs.get(name);\n if (job?.handler) await job.handler({ jobId: name, data });\n },\n async getExecutions(): Promise<any[]> { return []; },\n async listJobs(): Promise<string[]> { return [...jobs.keys()]; },\n };\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Runtime-authored translation sync (#2591).\n *\n * Translations authored in the Studio persist as `translation` sys_metadata\n * rows (`allowRuntimeCreate: true`), but historically only STATIC bundles\n * (app `bundle.translations`, plugin `translations/`) were ever loaded into\n * the i18n runtime — a published translation was a dead-end on publish AND\n * after a restart.\n *\n * This module is the single shared implementation of the fix, wired by both\n * hosts an `i18n` service can come from:\n * - the runtime's AppPlugin (covers the kernel's in-memory fallback — the\n * dev/standalone reality) and\n * - @objectstack/service-i18n's I18nServicePlugin (the file-based adapter).\n * Both adapters expose `replaceAuthoredTranslations(byLocale)`; the sync\n * computes the full authored layer from the rows and REPLACES it wholesale\n * (clear-then-reload), so deleted items/keys stop resolving.\n *\n * Item payload is a single-locale `TranslationItem` — the same `objects.`\n * groups the file-authored bundles use, plus the `locale` it translates\n * (#3778; before that, this type was registered against an object-first\n * `o.<object>` dialect no resolver read, so an authored translation saved\n * cleanly and rendered nothing). Locale resolution: the top-level `locale`,\n * then the item name when it looks like a BCP-47 tag — the name fallback\n * covers rows written before `locale` became required. Rows still carrying\n * the retired shape are skipped with a warning naming the row, since their\n * content can never resolve. Multiple items on one locale deep-merge in name\n * order (deterministic).\n *\n * Trigger points (wired by {@link wireAuthoredTranslationSync}):\n * • `kernel:ready` — cold-boot coverage;\n * • `metadata:reloaded` — publish-while-running coverage (#2576);\n * • protocol `onMetadataMutation` — direct-active saves / deletes of\n * `translation` rows that don't go through a package publish (#2588's\n * mutation stream).\n *\n * Rows are read straight from `sys_metadata` through the engine — the same\n * discipline as the authored-hook re-sync (#2588): env-scoped kernels\n * surface authored rows nowhere else, and the i18n map is process-wide so\n * rows are taken across all organizations. Best-effort: a failed read keeps\n * the currently applied authored layer.\n */\n\nimport { LEGACY_OBJECT_FIRST_KEYS } from '@objectstack/spec/system';\nimport type { IDataEngine } from '@objectstack/spec/contracts';\n\nimport { deepMerge } from './memory-i18n.js';\n\ntype AnyRecord = Record<string, any>;\n\ninterface MinimalCtx {\n logger: { debug?: (...a: any[]) => void; info?: (...a: any[]) => void; warn?: (...a: any[]) => void };\n getService(name: string): any;\n hook?(name: string, fn: () => Promise<void> | void): void;\n}\n\n/**\n * Ownership marker: several plugins may wire the sync against the same\n * kernel (AppPlugin AND I18nServicePlugin on a production server). The first\n * wirer to touch a given i18n service instance claims it; later wirers\n * no-op, so the layer is computed once per change instead of N times.\n */\nconst OWNER_PROP = '__authoredTranslationSyncOwner';\n\n/**\n * The `i18n` slot as this wirer uses it: the authored-layer replace seam, plus\n * the ownership marker stamped on the instance.\n *\n * [#4251] `replaceAuthoredTranslations` is NOT on `II18nService` — it is the\n * authored-overlay seam service-i18n grew for this sync, and every call site\n * probes for it. `OWNER_PROP` is this module's own marker, stamped on whatever\n * object occupies the slot so two wirers cannot both drive one instance.\n * Declared here rather than erased to `any` so both facts stay legible: what\n * the slot must supply, and what this module writes onto it.\n */\ninterface AuthoredTranslationSink {\n replaceAuthoredTranslations(layer: Record<string, unknown>): void;\n [OWNER_PROP]?: symbol;\n}\n\n// Deliberately narrow (language + optional script/region only): item names\n// are snake_case, so a permissive multi-segment pattern would classify names\n// like `my_custom_strings` as locales.\nconst LOCALE_LIKE = /^[a-z]{2,3}([_-]([A-Za-z]{4}|[A-Za-z]{2}|[0-9]{3}))?$/;\n\n/**\n * Read ACTIVE `translation` metadata rows and compute the authored layer,\n * keyed by locale. Returns `null` when the read failed (callers must keep\n * the current layer, never tear it down on an error).\n */\nexport async function readAuthoredTranslationLayer(\n engine: { find(object: string, opts?: AnyRecord): Promise<any[]> },\n logger?: MinimalCtx['logger'],\n): Promise<Record<string, Record<string, unknown>> | null> {\n let rows: any[];\n try {\n rows = (await engine.find('sys_metadata', {\n where: { type: 'translation', state: 'active' },\n })) ?? [];\n if (rows.length === 0) {\n // Legacy plural rows — mirrors the protocol's singular/plural fallback.\n rows = (await engine.find('sys_metadata', {\n where: { type: 'translations', state: 'active' },\n })) ?? [];\n }\n } catch (err: any) {\n logger?.debug?.('[i18n] authored-translation read failed — keeping current layer', {\n error: err?.message,\n });\n return null;\n }\n\n const byLocale: Record<string, Record<string, unknown>> = {};\n const sorted = [...rows].sort((a, b) => String(a?.name ?? '').localeCompare(String(b?.name ?? '')));\n for (const row of sorted) {\n let data: any;\n try {\n data = typeof row.metadata === 'string' ? JSON.parse(row.metadata) : row.metadata;\n } catch {\n continue; // malformed row — skip it, keep the rest\n }\n if (!data || typeof data !== 'object') continue;\n\n // Rows written against the retired object-first shape resolve to nothing\n // no matter what locale they claim, so say that plainly rather than\n // letting them look loaded. New saves are rejected at the metadata door\n // by `TranslationItemSchema`; this covers rows that predate it.\n const legacyKeys = LEGACY_OBJECT_FIRST_KEYS.filter((key) => data[key] !== undefined);\n if (legacyKeys.length > 0) {\n logger?.warn?.(\n `[i18n] authored translation '${row?.name}' uses the retired object-first shape `\n + `(${legacyKeys.join(', ')}) — nothing resolves from it; re-author it under `\n + \"'objects.<object_name>' with a top-level 'locale' — skipped\",\n );\n continue;\n }\n\n const locale: string | undefined =\n (typeof data?.locale === 'string' && data.locale)\n || (typeof row?.name === 'string' && LOCALE_LIKE.test(row.name) ? row.name : undefined)\n || undefined;\n if (!locale) {\n logger?.warn?.(\n `[i18n] authored translation '${row?.name}' has no resolvable locale `\n + \"(set the top-level 'locale', or name the item after its BCP-47 locale) — skipped\",\n );\n continue;\n }\n // Strip authoring bookkeeping; everything else is translation data. The\n // lock/package fields are stamped by the metadata protocol on published\n // rows — merging them would seed junk keys into the i18n layer.\n const {\n name: _n, locale: _l,\n _packageId: _p, _packageVersion: _pv, _provenance: _pr,\n _lock: _lk, _lockReason: _lr, _lockDocsUrl: _ld, _lockSource: _ls,\n ...payload\n } = data;\n byLocale[locale] = deepMerge(byLocale[locale] ?? {}, payload as Record<string, unknown>);\n }\n return byLocale;\n}\n\n/**\n * Wire the authored-translation sync into a plugin context: registers the\n * `kernel:ready` / `metadata:reloaded` hooks and (at kernel:ready) the\n * protocol mutation subscription. Idempotent per i18n service instance via\n * the ownership marker. Safe to call on kernels with no engine, no protocol,\n * or an i18n service without `replaceAuthoredTranslations` — every path\n * degrades to a no-op.\n */\nexport function wireAuthoredTranslationSync(ctx: MinimalCtx): void {\n if (typeof ctx.hook !== 'function') return;\n\n const token = Symbol('authored-translation-sync');\n const resolveOwnedI18n = (): AuthoredTranslationSink | null => {\n let i18n: AuthoredTranslationSink | undefined;\n try { i18n = ctx.getService('i18n'); } catch { return null; }\n if (!i18n || typeof i18n.replaceAuthoredTranslations !== 'function') return null;\n const current = i18n[OWNER_PROP];\n if (current === undefined) {\n i18n[OWNER_PROP] = token;\n return i18n;\n }\n return current === token ? i18n : null; // another wirer owns this instance\n };\n\n // Serialized: overlapping publishes must not finish out of order and leave\n // the older authored snapshot applied.\n let chain: Promise<void> = Promise.resolve();\n const sync = (): Promise<void> => {\n const run = chain.then(async () => {\n const i18n = resolveOwnedI18n();\n if (!i18n) return;\n let engine: IDataEngine | undefined;\n try { engine = ctx.getService('objectql'); } catch { return; }\n if (!engine || typeof engine.find !== 'function') return;\n const layer = await readAuthoredTranslationLayer(engine, ctx.logger);\n if (layer === null) return; // failed read — keep current layer\n i18n.replaceAuthoredTranslations(layer);\n ctx.logger.info?.('[i18n] synced runtime-authored translations', {\n locales: Object.keys(layer),\n });\n });\n chain = run.catch(() => undefined);\n return run;\n };\n\n ctx.hook('kernel:ready', async () => {\n // Subscribe to translation mutations through the protocol choke point\n // (#2588). Only the owning wirer subscribes.\n if (resolveOwnedI18n()) {\n let protocol: any = null;\n try { protocol = ctx.getService('protocol'); } catch { /* no protocol on this kernel */ }\n if (protocol && typeof protocol.onMetadataMutation === 'function') {\n protocol.onMetadataMutation((evt: { type: string; name: string; state: string }) => {\n if (evt?.type !== 'translation' || evt.state === 'draft') return;\n void sync().catch((err: any) => {\n ctx.logger.warn?.('[i18n] authored-translation re-sync after mutation failed', {\n item: evt.name,\n error: err?.message,\n });\n });\n });\n }\n }\n await sync();\n });\n ctx.hook('metadata:reloaded', async () => {\n await sync();\n });\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { createMemoryCache } from './memory-cache.js';\nimport { createMemoryQueue } from './memory-queue.js';\nimport { createMemoryI18n } from './memory-i18n.js';\nimport { createMemoryMetadata } from './memory-metadata.js';\n\nexport { createMemoryCache } from './memory-cache.js';\nexport { createMemoryQueue } from './memory-queue.js';\nexport { createMemoryJob } from './memory-job.js';\nexport { createMemoryI18n, resolveLocale, deepMerge } from './memory-i18n.js';\nexport { createMemoryMetadata } from './memory-metadata.js';\nexport {\n wireAuthoredTranslationSync,\n readAuthoredTranslationLayer,\n} from './authored-translation-sync.js';\n\n/**\n * Map of core-criticality service names to their in-memory fallback factories.\n * This IS the kernel's pre-injection list: `ObjectKernel.preInjectCoreFallbacks()`\n * registers an entry for every unprovided `core` service before Phase 2, and\n * `validateSystemRequirements()` consults the same map as its final check.\n *\n * [#10746] `job` is deliberately ABSENT — a fallback must not fake capability\n * (maintainer ruling 2026-08-22). `createMemoryJob()`'s `schedule()` records a\n * job and never fires it, so pre-injecting it made every \"prefer the platform\n * job service, else own a timer\" consumer take the job-service branch and then\n * silently never run: `plugin-reports` logged `dispatcher registered with job\n * service` and dispatched nothing, ever (measured: 0 reads of\n * `sys_report_schedule` in 5600 ms with the success line present). With no\n * entry here, `getService('job')` throws when no job plugin is installed,\n * every consumer's documented no-job-service path becomes reachable (they all\n * already run on `LiteKernel`, which injects no fallbacks), and the kernel\n * says the absence out loud at boot: `validateSystemRequirements()` warns\n * \"Core service missing, functionality may be degraded: job\". Do NOT re-add\n * the entry to quiet that warning — install `@objectstack/service-job`, or\n * register a real scheduler, instead. `createMemoryJob` stays exported below\n * for embedders who deliberately want a manual-trigger job registry and have\n * read its docblock.\n */\nexport const CORE_FALLBACK_FACTORIES: Record<string, () => Record<string, any>> = {\n metadata: createMemoryMetadata,\n cache: createMemoryCache,\n queue: createMemoryQueue,\n i18n: createMemoryI18n,\n};\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Logger } from '@objectstack/spec/contracts';\n\n/**\n * The DECLARED duplicate-plugin-registration contract — one statement, shared\n * by both kernels (#9864, maintainer ruling 2026-08-19, option B).\n *\n * ## What the contract says\n *\n * Registering a plugin whose `name` is already registered **OVERWRITES** the\n * earlier registration — last-one-wins — and emits ONE `warn` naming the\n * plugin and BOTH versions. It is not an error on either kernel.\n *\n * ## Why it is written down here rather than in each kernel\n *\n * It was previously written twice, and the two copies disagreed:\n * `ObjectKernel.use()` accepted the duplicate silently (a bare\n * `plugins.set(name, meta)` with no check and no distinguishing log line),\n * while `LiteKernel.use()` threw `[Kernel] Plugin '<name>' already registered`.\n * `ObjectKernel` is the kernel `os serve` runs, so the production meaning of a\n * duplicate registration was the silent one — and it is load-bearing: it is\n * exactly what lets an app config's `plugins` entry supersede a plugin the CLI\n * auto-registered earlier in the same boot (#9863's `AuditPlugin` case). That\n * behaviour is PRESERVED here on purpose; what changes is that it is now\n * declared, audible, and pinned against both kernels\n * (`plugin-registration.contract.test.ts`) instead of being an accident of\n * whichever kernel a reader happened to open.\n *\n * This is the fourth measured instance of one contract implemented twice\n * across `ObjectKernel`/`LiteKernel` (#5170 hook-error propagation, #5282\n * `ObjectKernel` not inheriting `ObjectKernelBase`, #8357 adjacent one layer\n * up). `ObjectKernel` still does not extend `ObjectKernelBase`, so a shared\n * base class is not available as the sharing mechanism — a module both kernels\n * import is, and it is the same mechanism `plugin-order.ts` and\n * `hook-dispatch.ts` already use for the contracts they own.\n *\n * ⛔ Deliberately NOT exported from the package barrel. Under the ruling this\n * card declares EXISTING behaviour; it does not mint a public registration or\n * supersede API (that was the shape of the option that was NOT ruled). Both\n * kernels import it by relative path, as they do `plugin-order.js`'s internals.\n */\n\n/**\n * The only members the registration contract reads. Satisfied by both kernels'\n * registry value types — `Plugin` (LiteKernel, `version` optional) and\n * `PluginMetadata` (ObjectKernel, `version` always present because\n * `PluginLoader.toPluginMetadata()` defaults it).\n */\nexport interface NamedRegistration {\n name: string;\n version?: string;\n}\n\n/** Render a version for the warning; a plugin may legitimately carry none. */\nfunction versionLabel(plugin: NamedRegistration): string {\n return plugin.version ? `v${plugin.version}` : 'unversioned';\n}\n\n/**\n * The superseding warning's text.\n *\n * ## Why it cannot be confused with a first registration\n *\n * The failure this card exists to end is that a superseding registration was\n * indistinguishable from a first one: `ObjectKernel` logged\n * `Plugin registered: <name>@<version>` for BOTH, so one plugin replacing\n * another read as two plugins running. Four properties keep them apart, and\n * each is pinned by the contract test:\n *\n * 1. **A different verb, first thing on the line** — `Plugin superseded:`, not\n * `Plugin registered:`. A boot log greps and eyeballs the same way.\n * 2. **A different LEVEL** — `warn`, never `info`. This is not decoration on\n * the `os serve` path: the CLI's default kernel level is `warn`\n * (`DEFAULT_LOG_LEVEL`, `packages/cli/src/utils/log-level.ts`), at which\n * `Plugin registered:` is not emitted at all; and the boot-quiet window\n * (`BootLogCapture`, `BOOT_DIAGNOSTIC_FLOOR = 'warn'`) DISCARDS in-window\n * `info` and replays only `warn` and above. An `info`-level supersede notice\n * would be invisible on precisely the boot path where the defect lives.\n * 3. **BOTH versions, in order** — `(v1.0.0) → (v2.0.0)`. A plugin silently\n * replaced by a differently-configured instance of ITSELF is the expensive\n * direction, and there the two names are identical; the versions and the\n * arrow are what make one line say which instance survived.\n * 4. **The consequence, stated** — the earlier instance is discarded before it\n * ever boots, so a reader is not left to infer whether two plugins are now\n * running.\n *\n * `ObjectKernel` additionally SUPPRESSES its `Plugin registered:` line for a\n * superseding registration, so the count of `Plugin registered:` lines in a\n * boot log equals the number of plugins that will actually boot.\n */\nexport function describeSupersededRegistration(\n previous: NamedRegistration,\n next: NamedRegistration,\n): string {\n return (\n `Plugin superseded: '${next.name}' — the later registration (${versionLabel(next)}) ` +\n `REPLACED the earlier one (${versionLabel(previous)}). Only the later instance is ` +\n `initialized and started; the earlier one is discarded without ever running init(). ` +\n `Duplicate registration by name is last-one-wins on both kernels by declared contract ` +\n `(#9864) — register the plugin once if that is not what you meant.`\n );\n}\n\n/**\n * Apply the declared contract: write `plugin` into `registry` under its name,\n * warning when that displaces an earlier registration.\n *\n * @returns the displaced registration, or `undefined` for a first registration.\n * Callers use it to decide whether this was a plain registration —\n * `ObjectKernel` suppresses its `Plugin registered:` line when it was\n * not.\n *\n * ## Why no teardown of the displaced plugin (measured, #9864)\n *\n * Both kernels refuse registration outside the `idle` state\n * (`ObjectKernelBase.validateIdle()`; `ObjectKernel.use()`'s own state check),\n * so a supersede can only ever displace a plugin the kernel has **not yet\n * initialized** — `init()`, `start()` and `destroy()` all run from\n * `bootstrap()`/`destroy()`, over the resolved order read out of this very\n * registry, from which the displaced entry is already gone. `PluginLoader.\n * loadPlugin()`, which `ObjectKernel` runs BEFORE this point, is pure\n * validation plus a name-keyed map write of its own (so it drops the displaced\n * metadata for the same reason, rather than accumulating it); it invokes\n * nothing on the plugin.\n *\n * The kernel therefore acquired nothing on the displaced plugin's behalf, and\n * there is nothing here to tear down. Calling `destroy()` on it would be the\n * bug, not the fix: `destroy()` is the paired teardown for `init()`, and\n * running it against a never-initialized instance runs cleanup over state that\n * was never set up. Anything the displaced instance holds was acquired by the\n * CALLER's own `new` before `use()` was reached — a survey of all 52 in-tree\n * `implements Plugin` classes found none that acquires an OS-level resource in\n * its constructor (41 have a constructor body; every one normalizes options or\n * builds in-memory helpers — e.g. `HonoServerPlugin`'s `new HonoHttpServer()`\n * only constructs a `Hono` app, its socket opening at `kernel:listening`).\n */\nexport function registerPluginByName<T extends NamedRegistration>(\n registry: Map<string, T>,\n plugin: T,\n logger: Pick<Logger, 'warn'>,\n): T | undefined {\n const previous = registry.get(plugin.name);\n\n if (previous !== undefined) {\n // `warn`, not `error`: this is a FUNCTIONAL, fully-visible outcome —\n // the composition the host asked for is the one that boots, and\n // nothing that claims to be persisted fails to land. See AGENTS.md\n // \"Degradation log levels\" for why that distinction decides the level.\n logger.warn(describeSupersededRegistration(previous, plugin), {\n plugin: plugin.name,\n supersededVersion: previous.version,\n supersedingVersion: plugin.version,\n });\n }\n\n registry.set(plugin.name, plugin);\n\n return previous;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * The one-shot timeout guard both kernel lifecycle races arm.\n *\n * A guard is a `setTimeout` plus the promise a `Promise.race` holds a reaction\n * on. Reclaiming it after the race is decided therefore has TWO halves, and\n * the kernel used to do exactly one of them at each site — a different one:\n *\n * - `raceStartupTimeout` cleared the timer and left the promise pending.\n * - `shutdown()` `unref()`d the timer, never cleared it, and also left the\n * promise pending.\n *\n * Clearing the timer reclaims the TIMER. It does not settle the PROMISE: the\n * race attached `then(resolve, reject)` to that participant when it started,\n * and a promise that never settles keeps both itself and that reaction alive\n * for as long as anything can reach them. That is what\n * `vitest --detectAsyncLeaks` reports as a leaking PROMISE — two frames per\n * site, one for the guard promise and one for the race's reaction on it\n * (#10604). So `reclaim()` clears the timer AND settles the promise.\n *\n * ⛔ The guard is deliberately NOT `unref()`ed at arm time (#4813). An unref'd\n * guard also stops pinning the event loop, but it stops being a guard as well:\n * if the operation never settles and nothing else keeps the loop alive, Node\n * exits before the timer can fire and the timeout is never reported. The guard\n * has to stay ref'd exactly as long as the race is undecided — which is what\n * clearing on settle expresses and what `unref()` cannot. `unref()` is the\n * failure mode this class exists to prevent, not a second belt to add.\n */\nexport class TimeoutGuard {\n private timer: ReturnType<typeof setTimeout> | undefined;\n\n /**\n * Settles `expiry` without a value. `Promise<never>` has no resolvable\n * value in the type system, but settling it is the entire point: it is\n * only ever called from `reclaim()`, i.e. after the race it guarded has\n * already been decided, so the resolution is discarded by construction and\n * can never become a race winner. The cast localises that argument here\n * rather than pushing a lie into every caller's return type.\n */\n private settleExpiry: () => void = () => {};\n\n /**\n * The racing participant. Rejects with the caller's error when the guard\n * expires; resolves — settles, harmlessly — once `reclaim()` runs.\n */\n readonly expiry: Promise<never>;\n\n constructor(timeoutMs: number, createTimeoutError: () => Error) {\n this.expiry = new Promise<never>((resolve, reject) => {\n this.settleExpiry = resolve as unknown as () => void;\n this.timer = setTimeout(() => reject(createTimeoutError()), timeoutMs);\n });\n }\n\n /**\n * Reclaim the guard once the race it protects has been decided. Both\n * halves, always: the timer is cleared so it cannot fire against a\n * lifecycle phase that is already over, and `expiry` is settled so neither\n * it nor the race's reaction on it is retained.\n *\n * Idempotent — `clearTimeout` on a cleared handle and a second resolve on\n * a settled promise are both no-ops.\n */\n reclaim(): void {\n clearTimeout(this.timer);\n this.timer = undefined;\n this.settleExpiry();\n }\n}\n\n/**\n * Race `operation` against a startup/shutdown timeout guard, reclaiming the\n * guard the moment the race settles — whichever side won.\n *\n * `operation` is widened to `T | PromiseLike<T>` because the Plugin contract\n * permits a synchronous hook (`init`/`start` return `void | Promise<void>`);\n * such a hook wins the race immediately and the guard is reclaimed on the same\n * turn.\n *\n * `createTimeoutError` is a factory rather than an `Error` so a caller that\n * discriminates the timeout by IDENTITY (`shutdown()`, #5274) can hand back\n * its own pre-built instance, while callers that only need a message pay for\n * the `Error` — and its stack — only when the guard actually fires.\n */\nexport async function raceWithTimeout<T>(\n operation: T | PromiseLike<T>,\n timeoutMs: number,\n createTimeoutError: () => Error,\n): Promise<T> {\n const guard = new TimeoutGuard(timeoutMs, createTimeoutError);\n\n try {\n return await Promise.race([operation, guard.expiry]);\n } finally {\n guard.reclaim();\n }\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * ADR-0130 D4 + D5 — reading a release artifact's package list, and ordering it.\n *\n * A release artifact MAY carry N package manifests (ADR-0130 D1: everything\n * inside one artifact is delivered atomically by one publisher, and that joint\n * delivery IS the co-ownership declaration). This module is the ONE place that\n * turns an artifact — either shape — into the ordered list of manifests the\n * load path registers.\n *\n * ## Why this lives in `@objectstack/core` and not next to one of its readers\n *\n * There are TWO readers of `packages[]`, not one, and they sit in packages that\n * cannot import each other: `ObjectQLPlugin` (`@objectstack/objectql`, which\n * calls `registerApp` per package) and `MetadataPlugin`\n * (`@objectstack/metadata`, whose artifact door registers each package body's\n * collections stamped with that body's id). `@objectstack/objectql` depends on\n * `@objectstack/metadata`, so the metadata door physically cannot import this\n * module from where it started life.\n *\n * The alternative — a second read of `packages[]` inside the metadata door —\n * would have split more than the sort: this function is also the GATE (it\n * parses each entry against `ArtifactPackageSchema` and refuses a duplicate\n * package id), so two readers would have disagreed about which artifacts are\n * loadable, not just about what order to load them in. `@objectstack/core`\n * already owns `resolvePluginOrder` and is already a dependency of both\n * readers, so hosting it here adds NO package edge to the graph. ⛔ Do not\n * re-home this next to either reader; the next reader will have the same\n * problem.\n *\n * ## Both shapes are read (D4), and the fallback is the compatibility mechanism\n *\n * - `packages` present → iterate it.\n * - `packages` absent → treat `manifest` (singular) as a **single-element list**.\n *\n * The second branch is not a convenience: it is the term ADR-0130's whole\n * compatibility claim rests on (D7 — an existing single-`manifest` artifact must\n * register bit-identically through this path). That is why this function returns\n * the caller's ORIGINAL object in that branch rather than a copy or a\n * re-validated clone: the bytes `registerApp` receives must not move.\n *\n * ## The entry shape is NOT re-derived here (ADR-0116's lesson)\n *\n * `ArtifactPackageSchema` (`@objectstack/spec`, `stack.zod.ts`) is the sole\n * declaration of what one entry looks like — a wrapper object carrying the\n * package under `manifest:`, the structural position D4 reserves so a future\n * `{ ref, integrity }` external segment is an additive key rather than a\n * reshape, with the body half declared as the ASSEMBLED package body. This\n * module imports and applies that schema instead of duck-typing either half: a\n * second declaration of one shape is exactly the drift ADR-0116 exists about.\n *\n * ### The gate is a FULL parse now — what changed, and why it could not be\n *\n * This seam used to apply `ArtifactPackageEntrySchema` to the WRAPPER only,\n * filtering the verdict down to issues at the entry root or on `manifest`\n * itself. That was a deliberate, narrow accommodation of a real surface defect\n * rather than a choice: `ArtifactPackageEntrySchema.manifest` is\n * `ManifestSchema`, whose `objects` key is `z.array(z.string())` — GLOB\n * PATTERNS (`manifest.zod.ts`) — while what reaches this path is an ASSEMBLED\n * payload whose `objects` are object DEFINITIONS. Measured against that\n * schema, `safeParse` of a real entry failed with\n * `manifest.objects.0: expected string, received object`, so a full parse\n * would have refused exactly the artifacts this path exists to register.\n *\n * The maintainer settled that surface on 2026-09-02 (road **B**): the\n * assembled/registered form has its own declaration,\n * `AssembledPackageBodySchema`, and `ArtifactPackageSchema` is the entry that\n * carries it. Both stages are now describable, so this seam parses the WHOLE\n * entry — a package body whose collections are the wrong shape (globs where\n * definitions belong, a permission-capability list where permission sets\n * belong) is refused HERE, at the seam that would otherwise register it and\n * silently own nothing. ⛔ Road C — widening `ManifestSchema.objects` to accept\n * both spellings — was rejected by name: a union that accepts both stages makes\n * neither stage checkable.\n *\n * ⛔ The body handed to `registerApp` is still the caller's original\n * `entry.manifest`, never `verdict.data.manifest`. The parse is a GATE, and the\n * reason is unchanged by road B: `ManifestSchema` carries defaults\n * (`defaultDatasource: 'default'`, `scope: 'project'`) and Zod strips\n * undeclared keys, so registering a parsed clone would put different bytes into\n * the registry than the singular-`manifest` branch does for the same authored\n * package. D7 pins that those two branches do not disagree.\n *\n * ## Ordering reuses the ONE sorter (D5)\n *\n * Packages inside an artifact MUST register in dependency-topological order: a\n * package using `defineObjectExtension` to extend another package's object\n * registers after the package it extends. `resolvePluginOrder`\n * (`@objectstack/core`, `plugin-order.ts`) is the platform's single topological\n * sorter and ADR-0116 already established that ordering is a **declared**\n * contract resolved from `dependencies` — the failure mode that record exists\n * for being precisely \"correctness rode on which array slot each caller put it\n * in\". Sorting intra-artifact packages with a second, parallel implementation\n * would re-create that failure inside the artifact after ADR-0116 removed it\n * between packages. ⛔ Do not add a second sort here, in any form.\n *\n * ### Why declared dependencies enter as `optionalDependencies`\n *\n * `manifest.dependencies` is documented in `ManifestSchema` as a \"Map of package\n * IDs to version requirements\", and its own example is `@steedos/plugin-auth` —\n * an EXTERNAL package, resolved by the installer, definitionally not inside this\n * artifact. The artifact is not the resolution scope for those: the sort's node\n * set is the artifact's own package set, so a declared id that names a package\n * in this artifact is a real edge and one that does not is simply not an edge\n * here. That is `resolvePluginOrder`'s `optionalDependencies` semantics\n * verbatim — \"hoisted ahead when composed, silently skipped when absent\" — so\n * the classification is expressed by choosing the sorter's existing bucket\n * rather than by filtering the list first and re-implementing the same rule.\n *\n * Reading every declared id as a HARD edge instead would refuse, at load time,\n * every artifact whose manifest declares a dependency on any package outside it\n * — which is every real artifact, and which D7 forbids outright. The same\n * reading already exists one layer up and is written down there: the metadata\n * protocol's package-scope closure keeps an unresolvable declared dependency in\n * the closure and simply stops walking (`protocol.ts`,\n * `resolveWritePackageScope`) rather than treating it as a fault.\n *\n * Cycles keep the sorter's behaviour untouched: two packages in one artifact\n * that depend on each other throw, because an optional dependency is a real\n * edge whenever both sides are composed. ⛔ Neither the cycle throw nor the\n * missing-dependency semantics are re-adjudicated here.\n */\n\nimport { resolvePluginOrder, type OrderablePlugin } from './plugin-order.js';\nimport { ArtifactPackageSchema } from '@objectstack/spec';\n\n/**\n * Refusals raised by {@link resolveArtifactPackageOrder}, as ADR-0112 envelopes\n * (`code` + `status`) — the shape this repository's rejection tests assert\n * against, never a bare throw.\n */\nexport type ArtifactPackageError = Error & { code: string; status: number };\n\n/**\n * How many Zod issues one refusal quotes before it starts counting instead.\n *\n * A single wrong-shaped collection raises one issue per element, so an\n * uncapped message can run to hundreds of lines for one mistake. The cap is on\n * the QUOTED issues only — the count that follows names the remainder, so the\n * message never trails off the way a bare `slice` would.\n */\nconst MAX_REPORTED_ENTRY_ISSUES = 5;\n\nfunction refuse(code: string, message: string): ArtifactPackageError {\n const err = new Error(message) as ArtifactPackageError;\n err.code = code;\n err.status = 422;\n return err;\n}\n\n/**\n * The id one artifact package is keyed by.\n *\n * `||`, not `??`, on purpose: `ObjectQL.registerApp` keys the installed package\n * on `manifest.id || manifest.name`, so an empty-string `id` falls back to\n * `name` there. Every seam that has to name a package — this module's ordering\n * map, and the install gate's co-ownership set (ADR-0130 D1) — reads the id\n * through THIS function, so none of them can order or admit a package under a\n * key the registry never stores it by.\n *\n * @returns The package id, or `undefined` when the manifest carries neither a\n * usable `id` nor a usable `name`.\n */\nexport function artifactPackageId(manifest: unknown): string | undefined {\n const id = (manifest as { id?: unknown; name?: unknown } | null | undefined)?.id\n || (manifest as { name?: unknown } | null | undefined)?.name;\n return typeof id === 'string' && id !== '' ? id : undefined;\n}\n\n/** The ordering-relevant projection of one artifact package. */\ninterface ArtifactPackageNode extends OrderablePlugin {\n /** The caller's ORIGINAL manifest body — never a parsed clone. */\n manifest: unknown;\n}\n\n/**\n * Resolve an artifact into the manifests to register, in dependency-topological\n * order (ADR-0130 D4 + D5).\n *\n * @param artifact - A release artifact (`{ packages: [...] }`), or a bare\n * manifest / single-`manifest` artifact — both shapes are read.\n * @returns The manifest bodies to register, in the order to register them.\n * @throws An ADR-0112 envelope (`code` + `status: 422`) for a malformed entry or\n * a duplicate package id, and `resolvePluginOrder`'s own error for a cycle.\n */\nexport function resolveArtifactPackageOrder(artifact: unknown): unknown[] {\n const declared = (artifact as { packages?: unknown } | null | undefined)?.packages;\n\n // D4, second branch: no `packages` key → the artifact carries one package and\n // the caller's own object IS that package's manifest body. Returned by\n // reference, unvalidated and unrewritten — this is the path every artifact\n // built to date takes, and D7 pins that it did not move.\n if (declared === undefined || declared === null) return [artifact];\n\n if (!Array.isArray(declared)) {\n throw refuse(\n 'INVALID_ARTIFACT_PACKAGES',\n 'A release artifact\\'s `packages` must be an array of package entries '\n + '(ADR-0130 D4, `ArtifactPackageEntrySchema`), but this artifact carries '\n + `\\`packages\\` of type ${typeof declared}. Omit the key entirely for a `\n + 'single-package artifact — `manifest` is retained, not replaced.',\n );\n }\n\n const nodes = new Map<string, ArtifactPackageNode>();\n\n declared.forEach((entry: unknown, index: number) => {\n // The entry contract, read off its ONE declaration rather than duck-typed,\n // and parsed WHOLE (#14242 B — see the module header for what this replaced\n // and why the narrow version was not a choice). Two classes of mistake are\n // caught by the same call: the WRAPPER one the schema's `history` text\n // exists for — a body inlined straight onto the array element instead of\n // wrapped as `{ manifest: { … } }` — and a malformed BODY, most sharply a\n // manifest still carrying authoring-time glob patterns where the assembled\n // stage carries definitions.\n const verdict = ArtifactPackageSchema.safeParse(entry);\n if (!verdict.success) {\n // Capped: a body whose whole `objects` array is the wrong shape raises one\n // issue per element, and a refusal nobody can read is a refusal nobody\n // acts on. The count says how many were withheld rather than trailing off.\n const issues = verdict.error.issues;\n const shown = issues.slice(0, MAX_REPORTED_ENTRY_ISSUES);\n throw refuse(\n 'INVALID_ARTIFACT_PACKAGE_ENTRY',\n `Release artifact \\`packages[${index}]\\` is not a package entry (ADR-0130 D4): `\n + shown.map((i) => `${i.path.join('.') || '<entry>'}: ${i.message}`).join('; ')\n + (issues.length > shown.length ? ` (+${issues.length - shown.length} more)` : '')\n + '. Each entry is a WRAPPER object carrying its package under `manifest:` — '\n + 'wrap an inlined body as `{ manifest: { … } }`. The key position is reserved '\n + 'so a future external-segment form is an additive key rather than a reshape. '\n + 'The body under `manifest:` is the ASSEMBLED package body '\n + '(`AssembledPackageBodySchema`): its `objects` / `datasources` are '\n + 'DEFINITIONS, not the authoring manifest\\'s glob patterns — a compiled '\n + 'artifact has no files left to glob.',\n );\n }\n\n // ⛔ The ORIGINAL body, never `verdict.data.manifest` — see the module\n // header: the schema is a GATE here even now that it parses the whole\n // entry, because a parsed clone carries defaults and drops undeclared keys\n // the singular-`manifest` branch keeps.\n const manifest = (entry as { manifest?: unknown }).manifest;\n const id = artifactPackageId(manifest);\n\n if (id === undefined) {\n throw refuse(\n 'INVALID_ARTIFACT_PACKAGE_ENTRY',\n `Release artifact \\`packages[${index}]\\` carries a manifest with no usable `\n + 'package id: `registerApp` keys the installed package on `id || name`, so '\n + 'an entry without either cannot be ordered against its siblings or '\n + 'addressed after install.',\n );\n }\n\n if (nodes.has(id)) {\n // Deduplicating silently is the failure this refusal exists to prevent:\n // one of the two bodies would simply never register, and nothing\n // downstream could tell that it had been dropped.\n throw refuse(\n 'DUPLICATE_ARTIFACT_PACKAGE',\n `Release artifact declares package \"${id}\" more than once (\\`packages[${index}]\\` `\n + 'repeats an earlier entry). One artifact carries each package once — an '\n + 'artifact is one atomic delivery (ADR-0130 D1/D6), not a list with '\n + 'last-writer-wins.',\n );\n }\n\n nodes.set(id, {\n // `name` is what `resolvePluginOrder` puts in its diagnostics; the MAP KEY\n // is what its edges resolve against. Both are the package id, so an error\n // it raises names the same string the artifact author wrote.\n name: id,\n optionalDependencies: Object.keys(\n (manifest as { dependencies?: Record<string, unknown> }).dependencies ?? {},\n ),\n manifest,\n });\n });\n\n return resolvePluginOrder(nodes).map((node) => node.manifest);\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { Plugin } from './types.js';\nimport { createLogger, ObjectLogger } from './logger.js';\nimport type { LoggerConfig } from '@objectstack/spec/system';\nimport { ObjectKernelBase } from './kernel-base.js';\nimport { registerPluginByName } from './plugin-registration.js';\n\n/**\n * ObjectKernel - MiniKernel Architecture\n * \n * A highly modular, plugin-based microkernel that:\n * - Manages plugin lifecycle (init, start, destroy)\n * - Provides dependency injection via service registry\n * - Implements event/hook system for inter-plugin communication\n * - Handles dependency resolution (topological sort)\n * - Provides configurable logging for server and browser\n * \n * Core philosophy:\n * - Business logic is completely separated into plugins\n * - Kernel only manages lifecycle, DI, and hooks\n * - Plugins are loaded as equal building blocks\n */\nexport class LiteKernel extends ObjectKernelBase {\n constructor(config?: { logger?: Partial<LoggerConfig> }) {\n const logger = createLogger(config?.logger);\n super(logger);\n \n // Initialize context after logger is created\n this.context = this.createContext();\n }\n\n /**\n * Register a plugin\n * @param plugin - Plugin instance\n *\n * Duplicate names OVERWRITE, with one `warn` naming both versions — the\n * declared contract in `plugin-registration.ts`, applied identically by\n * `ObjectKernel.use()` (#9864, maintainer ruling 2026-08-19).\n *\n * This method used to `throw` `[Kernel] Plugin '<name>' already\n * registered` here while `ObjectKernel` overwrote silently, so one input\n * had two meanings depending on which kernel was running — and the kernel\n * that runs in production was the silent one. The ruling converged them on\n * the behaviour that already works (an app config superseding a plugin the\n * CLI auto-registered, #9863) and made it audible rather than removing it.\n */\n use(plugin: Plugin): this {\n this.validateIdle();\n\n registerPluginByName(this.plugins, plugin, this.logger);\n\n return this;\n }\n\n /**\n * Bootstrap the kernel\n * 1. Resolve dependencies (topological sort)\n * 2. Init phase - plugins register services\n * 3. Start phase - plugins execute business logic\n * 4. Trigger 'kernel:ready' hook\n */\n async bootstrap(): Promise<void> {\n this.validateState('idle');\n\n this.state = 'initializing';\n this.logger.info('Bootstrap started');\n\n // Resolve dependencies\n const orderedPlugins = this.resolveDependencies();\n\n // Pre-Phase-1 ordering contract (ADR-0116, #4131): a plugin that\n // requires a service provided only by a later plugin fails HERE,\n // named, before any init side effects.\n this.validateInitServices(orderedPlugins);\n\n // Phase 1: Init - Plugins register services\n this.logger.info('Phase 1: Init plugins');\n for (const plugin of orderedPlugins) {\n await this.runPluginInit(plugin);\n }\n\n // Phase 2: Start - Plugins execute business logic\n this.logger.info('Phase 2: Start plugins');\n this.state = 'running';\n \n for (const plugin of orderedPlugins) {\n await this.runPluginStart(plugin);\n }\n\n // The three boot-path lifecycle hooks all use PROPAGATING dispatch,\n // identical to `ObjectKernel.bootstrap()`'s `context.trigger` (a bare\n // awaited loop that never catches): a handler that throws FAILS THE\n // BOOT on both kernels, the remaining handlers are skipped, the\n // original error reaches the caller unwrapped, and the kernel is left\n // 'stopped' rather than 'running' so a failed boot never reads as a\n // live kernel. `kernel:ready` got this in #5170; `kernel:bootstrapped`\n // and `kernel:listening` in #5257.\n //\n // Why the boot path is the wrong place to be forgiving: everything\n // between here and the log line below is a PRECONDITION of the\n // \"✅ Bootstrap complete\" this method is about to print. Swallowing a\n // throw does not make the boot succeed — it only makes the failure\n // invisible while `bootstrap()` resolves normally. `kernel:listening`\n // is the sharpest case: it is where HTTP server plugins actually open\n // their socket (`HonoServerPlugin` awaits `server.listen(port)` with\n // no try/catch of its own, deliberately — propagation is the correct\n // behaviour there), so a swallowed EACCES / unavailable-listen on an\n // edge or serverless host produced a live process, a cheerful\n // \"Bootstrap complete\", and not one socket listening.\n try {\n // Route/middleware registration phase, and the only correct moment\n // for a plugin to assert that what it DECLARED can actually be\n // delivered — the registries are still filling during init(), so a\n // boot gate has nowhere earlier to run (#5170).\n await this.triggerHookOrThrow('kernel:ready');\n // \"All synchronous bootstrap has settled\" anchor, strictly after\n // every kernel:ready handler has settled and before any HTTP socket\n // opens. Carries reconcile/backfill/audit work. NOTE: does not\n // guarantee background app seed data has settled — subscribe\n // `app:seeded` for that (see plugin-lifecycle-events.ts).\n await this.triggerHookOrThrow('kernel:bootstrapped');\n // HTTP servers open their listening socket here — strictly after\n // every kernel:ready and kernel:bootstrapped handler has completed.\n await this.triggerHookOrThrow('kernel:listening');\n } catch (error) {\n this.state = 'stopped';\n throw error;\n }\n this.logger.info('✅ Bootstrap complete', {\n pluginCount: this.plugins.size\n });\n }\n\n /**\n * Shutdown the kernel\n * Calls destroy on all plugins in reverse order\n */\n async shutdown(): Promise<void> {\n await this.destroy();\n }\n\n /**\n * Graceful shutdown - destroy all plugins in reverse order\n */\n async destroy(): Promise<void> {\n if (this.state === 'stopped') {\n this.logger.warn('Kernel already stopped');\n return;\n }\n\n this.state = 'stopping';\n this.logger.info('Shutdown started');\n\n // Trigger shutdown hook — FAIL-SOFT dispatch ({@link triggerHook}),\n // deliberately, and NOT the propagating dispatcher the boot-path hooks\n // above use (#5257). This is a per-hook judgement written down, not an\n // inherited default: on the shutdown path there is no \"refuse to\n // proceed\" left to buy. The remaining work — every other subscriber's\n // cleanup, then each plugin's destroy() in reverse order — is what\n // flushes buffers, closes connections and releases locks, so letting\n // one subscriber's failure abort the rest converts a single bad\n // handler into leaked resources and unflushed writes. A failing\n // shutdown handler is logged (`Hook handler failed: kernel:shutdown`)\n // and the cleanup continues.\n await this.triggerHook('kernel:shutdown');\n\n // Destroy plugins in reverse order\n const orderedPlugins = this.resolveDependencies();\n for (const plugin of orderedPlugins.reverse()) {\n await this.runPluginDestroy(plugin);\n }\n\n this.state = 'stopped';\n this.logger.info('✅ Shutdown complete');\n \n // Cleanup logger resources\n if (this.logger && typeof (this.logger as ObjectLogger).destroy === 'function') {\n await (this.logger as ObjectLogger).destroy();\n }\n }\n\n /**\n * Get a service from the registry\n * Convenience method for external access\n */\n getService<T>(name: string): T {\n return this.context.getService<T>(name);\n }\n\n /**\n * Check if kernel is running\n */\n isRunning(): boolean {\n return this.state === 'running';\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nexport * from './adapter.js';\nexport * from './runner.js';\nexport * from './http-adapter.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport * as QA from '@objectstack/spec/qa';\nimport { TestExecutionAdapter } from './adapter.js';\n\nexport interface TestResult {\n scenarioId: string;\n passed: boolean;\n steps: StepResult[];\n error?: unknown;\n duration: number;\n}\n\nexport interface StepResult {\n stepName: string;\n passed: boolean;\n error?: unknown;\n output?: unknown;\n duration: number;\n}\n\n/**\n * Name the runtime shape of a value the way a suite author sees it in their fixture.\n * `typeof` answers `object` for both `null` and an array, which are the two shapes a\n * `contains` author most needs told apart from a plain record.\n */\nfunction describeActualType(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'array';\n return typeof value;\n}\n\n/**\n * Say WHICH of the two things is wrong, because the message is the only thing the\n * author has: `undefined`/`null` mean the path did not resolve to a value, so the\n * FIXTURE (the field path, or the response shape it was written against) is the\n * suspect; anything else means the path resolved fine and the ASSERTION picked an\n * operator that does not apply to what it found.\n */\nfunction containsInapplicableHint(actual: unknown): string {\n if (actual === undefined) {\n return (\n 'The path resolved to nothing — the field is absent from the result, or the path is misspelled. ' +\n \"Use 'is_null' if asserting absence is what you meant.\"\n );\n }\n if (actual === null) {\n return \"The path resolved to null. Use 'is_null' if asserting absence is what you meant.\";\n }\n return (\n \"'contains' tests array membership and string substrings only. \" +\n \"Use 'equals' to compare a scalar, or point the field at the array or string you meant to look inside.\"\n );\n}\n\nexport class TestRunner {\n constructor(private adapter: TestExecutionAdapter) {}\n\n async runSuite(suite: QA.TestSuite): Promise<TestResult[]> {\n const results: TestResult[] = [];\n for (const scenario of suite.scenarios) {\n results.push(await this.runScenario(scenario));\n }\n return results;\n }\n\n async runScenario(scenario: QA.TestScenario): Promise<TestResult> {\n const startTime = Date.now();\n const context: Record<string, unknown> = {}; // Variable context\n \n // Initialize context from initial payload if needed? Currently schema doesn't have initial context prop on Scenario\n // But we defined TestContextSchema separately.\n \n // Setup\n if (scenario.setup) {\n for (const step of scenario.setup) {\n try {\n await this.runStep(step, context);\n } catch (e) {\n return {\n scenarioId: scenario.id,\n passed: false,\n steps: [],\n error: `Setup failed: ${e instanceof Error ? e.message : String(e)}`,\n duration: Date.now() - startTime\n };\n }\n }\n }\n\n const stepResults: StepResult[] = [];\n let scenarioPassed = true;\n let scenarioError: unknown = undefined;\n\n // Main Steps\n for (const step of scenario.steps) {\n const stepStartTime = Date.now();\n try {\n const output = await this.runStep(step, context);\n stepResults.push({\n stepName: step.name,\n passed: true,\n output,\n duration: Date.now() - stepStartTime\n });\n } catch (e) {\n scenarioPassed = false;\n scenarioError = e;\n stepResults.push({\n stepName: step.name,\n passed: false,\n error: e,\n duration: Date.now() - stepStartTime\n });\n break; // Stop on first failure\n }\n }\n\n // Teardown (run even if failed)\n if (scenario.teardown) {\n for (const step of scenario.teardown) {\n try {\n await this.runStep(step, context);\n } catch (e) {\n // Log teardown failure but don't override main failure if it exists\n if (scenarioPassed) {\n scenarioPassed = false;\n scenarioError = `Teardown failed: ${e instanceof Error ? e.message : String(e)}`;\n }\n }\n }\n }\n\n return {\n scenarioId: scenario.id,\n passed: scenarioPassed,\n steps: stepResults,\n error: scenarioError,\n duration: Date.now() - startTime\n };\n }\n\n private async runStep(step: QA.TestStep, context: Record<string, unknown>): Promise<unknown> {\n // 1. Resolve Variables with Context (Simple interpolation or just pass context?)\n // For now, assume adpater handles context resolution or we do basic replacement\n const resolvedAction = this.resolveVariables(step.action, context);\n\n // 2. Execute Action\n const result = await this.adapter.execute(resolvedAction, context);\n\n // 3. Capture Outputs\n if (step.capture) {\n for (const [varName, path] of Object.entries(step.capture)) {\n context[varName] = this.getValueByPath(result, path);\n }\n }\n\n // 4. Run Assertions\n if (step.assertions) {\n for (const assertion of step.assertions) {\n this.assert(result, assertion, context);\n }\n }\n\n return result;\n }\n\n private resolveVariables(action: QA.TestAction, context: Record<string, unknown>): QA.TestAction {\n const actionStr = JSON.stringify(action);\n const resolved = actionStr.replace(/\\{\\{([^}]+)\\}\\}/g, (_match, varPath: string) => {\n const value = this.getValueByPath(context, varPath.trim());\n if (value === undefined) return _match; // Keep unresolved\n return typeof value === 'string' ? value : JSON.stringify(value);\n });\n try {\n return JSON.parse(resolved) as QA.TestAction;\n } catch {\n return action; // Fallback to original if parse fails\n }\n }\n\n private getValueByPath(obj: unknown, path: string): unknown {\n if (!path) return obj;\n const parts = path.split('.');\n let current: any = obj;\n for (const part of parts) {\n if (current === null || current === undefined) return undefined;\n current = current[part];\n }\n return current;\n }\n\n private assert(result: unknown, assertion: QA.TestAssertion, _context: Record<string, unknown>) {\n const actual = this.getValueByPath(result, assertion.field);\n // Resolve expected value if it's a variable ref? \n const expected = assertion.expectedValue; // Simplify for now\n\n switch (assertion.operator) {\n case 'equals':\n if (actual !== expected) throw new Error(`Assertion failed: ${assertion.field} expected ${expected}, got ${actual}`);\n break;\n case 'not_equals':\n if (actual === expected) throw new Error(`Assertion failed: ${assertion.field} expected not ${expected}, got ${actual}`);\n break;\n case 'contains':\n if (Array.isArray(actual)) {\n if (!actual.includes(expected)) throw new Error(`Assertion failed: ${assertion.field} array does not contain ${expected}`);\n } else if (typeof actual === 'string') {\n if (!actual.includes(String(expected))) throw new Error(`Assertion failed: ${assertion.field} string does not contain ${expected}`);\n } else {\n // `contains` is defined over arrays (membership) and strings (substring), and\n // over nothing else. This branch used to be absent, so every other shape fell\n // out of the switch and the assertion reported PASSED (#7256) — a `contains`\n // written against a path the result does not carry was the test silently\n // deleting itself, and CI believed the green. An assertion the engine cannot\n // evaluate is a FAILED assertion, which is the posture every other unhandled\n // shape in this engine already takes (`default:` below; the HTTP adapter's\n // unknown action type).\n throw new Error(\n `Assertion failed: ${assertion.field} cannot be evaluated by 'contains' — ` +\n `expected an array or a string at that path, got ${describeActualType(actual)}. ` +\n containsInapplicableHint(actual)\n );\n }\n break;\n case 'not_null':\n if (actual === null || actual === undefined) throw new Error(`Assertion failed: ${assertion.field} is null`);\n break;\n case 'is_null':\n if (actual !== null && actual !== undefined) throw new Error(`Assertion failed: ${assertion.field} is not null`);\n break;\n // ... Add other operators\n default:\n throw new Error(`Unknown assertion operator: ${assertion.operator}`);\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport * as QA from '@objectstack/spec/qa';\nimport { RestApiConfigSchema, CrudEndpointsConfigSchema } from '@objectstack/spec/api';\nimport { TestExecutionAdapter } from './adapter.js';\n\n/** Memoised {@link conventionMounts} — the schemas are `lazySchema`, so build them once. */\nlet conventionCache: { apiBase: string; dataPath: string } | undefined;\n\n/**\n * The paths a STOCK ObjectStack server serves the API base and the Data\n * Protocol under, derived from the two schemas `RestServer` resolves from.\n *\n * ## [#7848] Why this is derived and not written down\n *\n * Every record-shaped action type used to build `${baseUrl}/api/data/:object` —\n * a literal, one version segment short of the route the server registers, so\n * `create_record`, `read_record`, `update_record`, `delete_record` and\n * `query_records` (5 of the 8 declared `TestActionTypeSchema` members) answered\n * `HTTP Error 404: {\"error\":\"Not found\"}` against a stock boot. The suite author\n * reading that 404 has every reason to think it is their own URL.\n *\n * Replacing one literal with a corrected literal only moves the drift: the\n * server composes this path out of two declared pieces, and both of them are\n * configurable. So this asks the SAME schemas the server's own resolution asks:\n *\n * - `RestApiConfigSchema` → `apiPath ?? `${basePath}/${version}`` — the exact\n * expression `RestServer.getApiBasePath()` evaluates (`/api` + `v1`);\n * - `CrudEndpointsConfigSchema.dataPrefix` — what `RestServer` appends to it\n * to get `dataPath` (`/data`).\n *\n * These are the CONVENTION: what the schemas default to, which is what a\n * deployment serves until it says otherwise. `HttpTestAdapter` is handed an\n * origin and nothing else, so the convention is also all it can know before it\n * asks the server — which is what {@link HttpTestAdapter.resolveDataMount}\n * does (#7983). The `/discovery` path is derived here for the same reason the\n * data path is: `RestServer.registerDiscoveryEndpoints` mounts it at\n * `${getApiBasePath()}/discovery`, so a second literal would be a second place\n * to forget.\n */\nfunction conventionMounts(): { apiBase: string; dataPath: string } {\n if (conventionCache === undefined) {\n const api = RestApiConfigSchema.parse({});\n const crud = CrudEndpointsConfigSchema.parse({});\n const apiBase = api.apiPath ?? `${api.basePath}/${api.version}`;\n conventionCache = { apiBase, dataPath: `${apiBase}${crud.dataPrefix}` };\n }\n return conventionCache;\n}\n\n/**\n * Where this adapter decided the Data Protocol is mounted, and on what evidence.\n *\n * `why` is not decoration — it is the whole difference between a 404 that reads\n * as the suite author's own URL mistake and one that names the mount it used\n * and where that mount came from (Route & surface ownership §3).\n */\ninterface ResolvedDataMount {\n /** The path prefix record actions address, e.g. `/api/v1/data`. */\n readonly path: string;\n /** `discovery` = the server told us; `convention` = the schemas' defaults. */\n readonly source: 'discovery' | 'convention';\n /** One clause naming the probe and its outcome. */\n readonly why: string;\n}\n\nexport class HttpTestAdapter implements TestExecutionAdapter {\n /**\n * The single discovery probe of a run, memoised as the in-flight promise so\n * concurrent record actions share one request rather than racing N.\n *\n * `os test` builds ONE adapter for the whole run (`packages/cli/src/commands/\n * test.ts`) and hands it to every suite, so instance scope IS run scope.\n */\n private mountPromise?: Promise<ResolvedDataMount>;\n\n constructor(private baseUrl: string, private authToken?: string) {}\n\n /** The resolved data mount; probes at most once per adapter. */\n private dataMount(): Promise<ResolvedDataMount> {\n if (this.mountPromise === undefined) {\n this.mountPromise = this.resolveDataMount();\n }\n return this.mountPromise;\n }\n\n /**\n * Ask the server where it serves the Data Protocol, and fall back to the\n * convention — loudly — when it cannot say.\n *\n * ## [#7983] What the probe recovers, measured rather than assumed\n *\n * `@objectstack/client` answers the same question through discovery\n * (`getRoute`, `packages/client/src/index.ts`), and this follows it: prefer\n * the server's own `routes.data`, fall back to the convention. Measured on a\n * booted stack (REST generator + dispatcher bridge, three configs):\n *\n * | deployment | `{apiBase}/discovery` | serves |\n * |--------------------------------|-----------------------|---------------|\n * | stock | 200 `/api/v1/data` | `/api/v1/data`|\n * | `crud.dataPrefix: '/objects'` | 200 `/api/v1/objects` | `/api/v1/objects` |\n * | `api.apiPath: '/api/2026-01'` | **404** | `/api/2026-01/data` |\n *\n * So the probe closes the `dataPrefix` row exactly: `RestServer`'s discovery\n * handler substitutes the configured prefix into `routes.data`, and reading\n * it is strictly better than recomputing it here. The `apiPath` row it cannot\n * close, and the reason is structural rather than an oversight — `apiPath`\n * moves the base that discovery itself is mounted under, so the document that\n * would name the new mount is behind the very prefix we are missing.\n *\n * ⛔ And the one discovery document at a FIXED path does not rescue it:\n * `/.well-known/objectstack` is mounted at the site root by the dispatcher\n * bridge, but its `routes.data` is the DISPATCHER's own `${prefix}/data` —\n * measured as `/api/v1/data` under all three configs above, including the two\n * where the server serves elsewhere. Falling back to it would turn \"we could\n * not resolve the mount\" into \"discovery told us `/api/v1/data`\": the same\n * 404, now with a false provenance attached. Not probed, deliberately.\n *\n * Hence: one probe, then a diagnostic that NAMES the mount, the evidence and\n * the remedy. `api_call` takes the path it is given and is unaffected either\n * way — it stays the escape hatch for a host this cannot reach.\n */\n private async resolveDataMount(): Promise<ResolvedDataMount> {\n const { apiBase, dataPath } = conventionMounts();\n const probeUrl = `${this.baseUrl}${apiBase}/discovery`;\n let why: string;\n\n try {\n const headers: Record<string, string> = {};\n if (this.authToken) {\n headers['Authorization'] = `Bearer ${this.authToken}`;\n }\n const response = await fetch(probeUrl, { method: 'GET', headers });\n if (response.ok) {\n const body = await response.json();\n // `@objectstack/rest` answers the document bare; the dispatcher bridge\n // wraps it as `{ data: … }`. Pick the object that actually carries\n // `routes` rather than `body.data || body` — the document's own\n // `routes.data` key makes the looser test ambiguous to read here.\n const doc = (body && typeof body === 'object' && 'routes' in body)\n ? (body as { routes?: Record<string, unknown> })\n : ((body as { data?: { routes?: Record<string, unknown> } } | null)?.data);\n const advertised = doc?.routes?.data;\n if (typeof advertised === 'string' && advertised.length > 0) {\n return {\n path: advertised,\n source: 'discovery',\n why: `GET ${probeUrl} advertised routes.data`,\n };\n }\n why = `GET ${probeUrl} answered ${response.status} but carried no routes.data`;\n } else {\n why = `GET ${probeUrl} answered ${response.status}`;\n }\n } catch (error) {\n why = `GET ${probeUrl} could not be reached (${(error as Error).message})`;\n }\n\n const mount: ResolvedDataMount = { path: dataPath, source: 'convention', why };\n // Absence must be loud (Route & surface ownership §3): state the mount that\n // will be addressed, the evidence for it, and what to do about a host this\n // cannot reach — never leave the bare 404 to be diagnosed.\n console.warn(\n `[HttpTestAdapter] Data Protocol mount NOT resolved from discovery. `\n + `Record actions (create_record, read_record, update_record, delete_record, query_records) `\n + `will address ${this.baseUrl}${dataPath}, the convention declared by RestApiConfigSchema `\n + `(apiPath ?? basePath/version) + CrudEndpointsConfigSchema.dataPrefix. Evidence: ${why}. `\n + `A deployment that sets crud.dataPrefix is normally recovered by this probe; one that sets `\n + `api.apiPath moves discovery itself out from under it, and no fixed-path document reports `\n + `the REST mount — write those steps as api_call, which takes the path you give it.`,\n );\n return mount;\n }\n\n /** `{baseUrl}{dataMount}/{object}` — the collection URL. */\n private collectionUrl(mount: ResolvedDataMount, objectName: string): string {\n return `${this.baseUrl}${mount.path}/${encodeURIComponent(objectName)}`;\n }\n\n /** `{collection}/{id}` — the single-record URL. */\n private recordUrl(mount: ResolvedDataMount, objectName: string, id: unknown): string {\n return `${this.collectionUrl(mount, objectName)}/${encodeURIComponent(String(id))}`;\n }\n\n /**\n * The provenance clause appended to a failed record action's error.\n *\n * The card this closes is about a 404 that reads like the author's own URL\n * mistake; the mount is the one fact that distinguishes the two, so it rides\n * on the failure itself rather than only on a warning printed earlier in the\n * transcript.\n */\n private mountNote(mount: ResolvedDataMount): string {\n const how = mount.source === 'discovery'\n ? 'resolved from discovery'\n : 'assumed by convention — the server was not able to state it';\n return `record actions addressed ${this.baseUrl}${mount.path} (data mount ${how}: ${mount.why})`;\n }\n\n async execute(action: QA.TestAction, _context: Record<string, unknown>): Promise<unknown> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this.authToken) {\n headers['Authorization'] = `Bearer ${this.authToken}`;\n }\n // If action.user is specified, maybe add a specific header for impersonation if supported?\n if (action.user) {\n headers['X-Run-As'] = action.user;\n }\n\n switch (action.type) {\n case 'create_record':\n return this.createRecord(action.target, action.payload || {}, headers);\n case 'update_record':\n return this.updateRecord(action.target, action.payload || {}, headers);\n case 'delete_record':\n return this.deleteRecord(action.target, action.payload || {}, headers);\n case 'read_record':\n return this.readRecord(action.target, action.payload || {}, headers);\n case 'query_records':\n return this.queryRecords(action.target, action.payload || {}, headers);\n case 'api_call':\n return this.rawApiCall(action.target, action.payload || {}, headers);\n case 'wait':\n const ms = Number(action.payload?.duration || 1000);\n return new Promise(resolve => setTimeout(() => resolve({ waited: ms }), ms));\n default:\n throw new Error(`Unsupported action type in HttpAdapter: ${action.type}`);\n }\n }\n\n private async createRecord(objectName: string, data: Record<string, unknown>, headers: Record<string, string>) {\n const mount = await this.dataMount();\n const response = await fetch(this.collectionUrl(mount, objectName), {\n method: 'POST',\n headers,\n body: JSON.stringify(data)\n });\n return this.handleResponse(response, this.mountNote(mount));\n }\n\n private async updateRecord(objectName: string, data: Record<string, unknown>, headers: Record<string, string>) {\n const { id, ...fields } = data;\n if (!id) throw new Error('Update record requires id in payload');\n // PATCH, not PUT: `PATCH {dataMount}/:object/:id` is the route the server\n // registers, and there is no PUT sibling — the old verb 404'd even once the\n // path was right (#7848). The body is the field patch, so `id` is peeled off\n // rather than posted back as a column write.\n const mount = await this.dataMount();\n const response = await fetch(this.recordUrl(mount, objectName, id), {\n method: 'PATCH',\n headers,\n body: JSON.stringify(fields)\n });\n return this.handleResponse(response, this.mountNote(mount));\n }\n\n private async deleteRecord(objectName: string, data: Record<string, unknown>, headers: Record<string, string>) {\n const id = data.id;\n if (!id) throw new Error('Delete record requires id in payload');\n const mount = await this.dataMount();\n const response = await fetch(this.recordUrl(mount, objectName, id), {\n method: 'DELETE',\n headers\n });\n return this.handleResponse(response, this.mountNote(mount));\n }\n\n private async readRecord(objectName: string, data: Record<string, unknown>, headers: Record<string, string>) {\n const id = data.id;\n if (!id) throw new Error('Read record requires id in payload');\n const mount = await this.dataMount();\n const response = await fetch(this.recordUrl(mount, objectName, id), {\n method: 'GET',\n headers\n });\n return this.handleResponse(response, this.mountNote(mount));\n }\n\n private async queryRecords(objectName: string, data: Record<string, unknown>, headers: Record<string, string>) {\n // `POST {dataMount}/:object/query` — the spec-shape advanced query\n // (QueryAST in the body), the same route `client.data.query()` posts to.\n const mount = await this.dataMount();\n const response = await fetch(`${this.collectionUrl(mount, objectName)}/query`, {\n method: 'POST',\n headers,\n body: JSON.stringify(data)\n });\n return this.handleResponse(response, this.mountNote(mount));\n }\n\n private async rawApiCall(endpoint: string, data: Record<string, unknown>, headers: Record<string, string>) {\n // Deliberately does NOT consult the resolved mount — `api_call` takes the\n // path it is given, which is what makes it the escape hatch for a host\n // the discovery probe cannot reach (#7983). It probes nothing either: a\n // suite written entirely in `api_call` steps issues no discovery request.\n const method = (data.method as string) || 'GET';\n const body = data.body ? JSON.stringify(data.body) : undefined;\n const url = endpoint.startsWith('http') ? endpoint : `${this.baseUrl}${endpoint}`;\n\n const response = await fetch(url, {\n method,\n headers,\n body\n });\n return this.handleResponse(response);\n }\n\n private async handleResponse(response: Response, mountNote?: string) {\n if (!response.ok) {\n const text = await response.text();\n // The `HTTP Error <status>: <body>` prefix is unchanged — a suite author\n // (and every transcript predating #7983) reads the same first clause;\n // the mount provenance is APPENDED, never substituted.\n //\n // Appended on 404/405 only: those are the statuses a wrong mount\n // produces (nothing registered at the path / registered under another\n // verb), and they are exactly the ones this card calls unreadable. A\n // 400 or 422 reached the right route and got a real answer from it —\n // decorating those with the mount would bury the server's own message\n // under a URL the author has no reason to doubt.\n const wrongUrlShaped = response.status === 404 || response.status === 405;\n throw new Error(\n `HTTP Error ${response.status}: ${text}${wrongUrlShaped && mountNote ? ` — ${mountNote}` : ''}`,\n );\n }\n const contentType = response.headers.get('content-type');\n if (contentType && contentType.includes('application/json')) {\n return response.json();\n }\n return response.text();\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Logger } from '@objectstack/spec/contracts';\nimport type { PluginMetadata } from '../plugin-loader.js';\n\n// Conditionally import crypto for Node.js environments\nlet cryptoModule: typeof import('crypto') | null = null;\n\n\n/**\n * Plugin Signature Configuration\n * Controls how plugin signatures are verified\n */\nexport interface PluginSignatureConfig {\n /**\n * Map of publisher IDs to their trusted public keys\n * Format: { 'com.objectstack': '-----BEGIN PUBLIC KEY-----...' }\n */\n trustedPublicKeys: Map<string, string>;\n \n /**\n * Signature algorithm to use\n * - RS256: RSA with SHA-256\n * - ES256: ECDSA with SHA-256\n */\n algorithm: 'RS256' | 'ES256';\n \n /**\n * Strict mode: reject plugins without signatures\n * - true: All plugins must be signed\n * - false: Unsigned plugins are allowed with warning\n */\n strictMode: boolean;\n \n /**\n * Allow self-signed plugins in development\n */\n allowSelfSigned?: boolean;\n}\n\n/**\n * Plugin Signature Verification Result\n */\nexport interface SignatureVerificationResult {\n verified: boolean;\n error?: string;\n publisherId?: string;\n algorithm?: string;\n signedAt?: Date;\n}\n\n/**\n * Plugin Signature Verifier\n * \n * Implements cryptographic verification of plugin signatures to ensure:\n * 1. Plugin integrity - code hasn't been tampered with\n * 2. Publisher authenticity - plugin comes from trusted source\n * 3. Non-repudiation - publisher cannot deny signing\n * \n * Architecture:\n * - Uses Node.js crypto module for signature verification\n * - Supports RSA (RS256) and ECDSA (ES256) algorithms\n * - Verifies against trusted public key registry\n * - Computes hash of plugin code for integrity check\n * \n * Security Model:\n * - Public keys are pre-registered and trusted\n * - Plugin signature is verified before loading\n * - Strict mode rejects unsigned plugins\n * - Development mode allows self-signed plugins\n */\nexport class PluginSignatureVerifier {\n private config: PluginSignatureConfig;\n private logger: Logger;\n \n constructor(config: PluginSignatureConfig, logger: Logger) {\n this.config = config;\n this.logger = logger;\n \n this.validateConfig();\n }\n \n /**\n * Verify plugin signature\n * \n * @param plugin - Plugin metadata with signature\n * @returns Verification result\n * @throws Error if verification fails in strict mode\n */\n async verifyPluginSignature(plugin: PluginMetadata): Promise<SignatureVerificationResult> {\n // Handle unsigned plugins\n if (!plugin.signature) {\n return this.handleUnsignedPlugin(plugin);\n }\n \n try {\n // 1. Extract publisher ID from plugin name (reverse domain notation)\n const publisherId = this.extractPublisherId(plugin.name);\n \n // 2. Get trusted public key for publisher\n const publicKey = this.config.trustedPublicKeys.get(publisherId);\n if (!publicKey) {\n const error = `No trusted public key for publisher: ${publisherId}`;\n this.logger.warn(error, { plugin: plugin.name, publisherId });\n \n if (this.config.strictMode && !this.config.allowSelfSigned) {\n throw new Error(error);\n }\n \n return {\n verified: false,\n error,\n publisherId,\n };\n }\n \n // 3. Compute plugin code hash\n const pluginHash = this.computePluginHash(plugin);\n \n // 4. Verify signature using crypto module\n const isValid = await this.verifyCryptoSignature(\n pluginHash,\n plugin.signature,\n publicKey\n );\n \n if (!isValid) {\n const error = `Signature verification failed for plugin: ${plugin.name}`;\n this.logger.error(error, undefined, { plugin: plugin.name, publisherId });\n throw new Error(error);\n }\n \n this.logger.info(`✅ Plugin signature verified: ${plugin.name}`, {\n plugin: plugin.name,\n publisherId,\n algorithm: this.config.algorithm,\n });\n \n return {\n verified: true,\n publisherId,\n algorithm: this.config.algorithm,\n };\n \n } catch (error) {\n this.logger.error(`Signature verification error: ${plugin.name}`, error as Error);\n \n if (this.config.strictMode) {\n throw error;\n }\n \n return {\n verified: false,\n error: (error as Error).message,\n };\n }\n }\n \n /**\n * Register a trusted public key for a publisher\n */\n registerPublicKey(publisherId: string, publicKey: string): void {\n this.config.trustedPublicKeys.set(publisherId, publicKey);\n this.logger.info(`Trusted public key registered for: ${publisherId}`);\n }\n \n /**\n * Remove a trusted public key\n */\n revokePublicKey(publisherId: string): void {\n this.config.trustedPublicKeys.delete(publisherId);\n this.logger.warn(`Public key revoked for: ${publisherId}`);\n }\n \n /**\n * Get list of trusted publishers\n */\n getTrustedPublishers(): string[] {\n return Array.from(this.config.trustedPublicKeys.keys());\n }\n \n // Private methods\n \n private handleUnsignedPlugin(plugin: PluginMetadata): SignatureVerificationResult {\n if (this.config.strictMode) {\n const error = `Plugin missing signature (strict mode): ${plugin.name}`;\n this.logger.error(error, undefined, { plugin: plugin.name });\n throw new Error(error);\n }\n \n this.logger.warn(`⚠️ Plugin not signed: ${plugin.name}`, {\n plugin: plugin.name,\n recommendation: 'Consider signing plugins for production environments',\n });\n \n return {\n verified: false,\n error: 'Plugin not signed',\n };\n }\n \n private extractPublisherId(pluginName: string): string {\n // Extract publisher from reverse domain notation\n // Example: \"com.objectstack.engine.objectql\" -> \"com.objectstack\"\n const parts = pluginName.split('.');\n \n if (parts.length < 2) {\n throw new Error(`Invalid plugin name format: ${pluginName} (expected reverse domain notation)`);\n }\n \n // Return first two parts (domain reversed)\n return `${parts[0]}.${parts[1]}`;\n }\n \n private computePluginHash(plugin: PluginMetadata): string {\n // In browser environment, use SubtleCrypto\n if (typeof (globalThis as any).window !== 'undefined') {\n return this.computePluginHashBrowser(plugin);\n }\n \n // In Node.js environment, use crypto module\n return this.computePluginHashNode(plugin);\n }\n \n private computePluginHashNode(plugin: PluginMetadata): string {\n // Use pre-loaded crypto module\n if (!cryptoModule) {\n this.logger.warn('crypto module not available, using fallback hash');\n return this.computePluginHashFallback(plugin);\n }\n \n // Compute hash of plugin code\n const pluginCode = this.serializePluginCode(plugin);\n return cryptoModule.createHash('sha256').update(pluginCode).digest('hex');\n }\n \n private computePluginHashBrowser(plugin: PluginMetadata): string {\n // Browser environment - use simple hash for now\n // In production, should use SubtleCrypto for proper cryptographic hash\n this.logger.debug('Using browser hash (SubtleCrypto integration pending)');\n return this.computePluginHashFallback(plugin);\n }\n \n private computePluginHashFallback(plugin: PluginMetadata): string {\n // Simple hash fallback (not cryptographically secure)\n const pluginCode = this.serializePluginCode(plugin);\n let hash = 0;\n \n for (let i = 0; i < pluginCode.length; i++) {\n const char = pluginCode.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n \n return hash.toString(16);\n }\n \n private serializePluginCode(plugin: PluginMetadata): string {\n // Serialize plugin code for hashing\n // Include init, start, destroy functions\n const parts: string[] = [\n plugin.name,\n plugin.version,\n plugin.init.toString(),\n ];\n \n if (plugin.start) {\n parts.push(plugin.start.toString());\n }\n \n if (plugin.destroy) {\n parts.push(plugin.destroy.toString());\n }\n \n return parts.join('|');\n }\n \n private async verifyCryptoSignature(\n data: string,\n signature: string,\n publicKey: string\n ): Promise<boolean> {\n // In browser environment, use SubtleCrypto\n if (typeof (globalThis as any).window !== 'undefined') {\n return this.verifyCryptoSignatureBrowser(data, signature, publicKey);\n }\n \n // In Node.js environment, use crypto module\n return this.verifyCryptoSignatureNode(data, signature, publicKey);\n }\n \n private async verifyCryptoSignatureNode(\n data: string,\n signature: string,\n publicKey: string\n ): Promise<boolean> {\n if (!cryptoModule) {\n try {\n // @ts-ignore\n cryptoModule = await import('crypto');\n } catch (e) {\n // ignore\n }\n }\n\n if (!cryptoModule) {\n this.logger.error('Crypto module not available for signature verification');\n return false;\n }\n \n try {\n // Create verify object based on algorithm\n if (this.config.algorithm === 'ES256') {\n // ECDSA verification - requires lowercase 'sha256'\n const verify = cryptoModule.createVerify('sha256');\n verify.update(data);\n return verify.verify(\n {\n key: publicKey,\n format: 'pem',\n type: 'spki',\n },\n signature,\n 'base64'\n );\n } else {\n // RSA verification (RS256)\n const verify = cryptoModule.createVerify('RSA-SHA256');\n verify.update(data);\n return verify.verify(publicKey, signature, 'base64');\n }\n } catch (error) {\n this.logger.error('Signature verification failed', error as Error);\n return false;\n }\n }\n \n private async verifyCryptoSignatureBrowser(\n data: string,\n signature: string,\n publicKey: string\n ): Promise<boolean> {\n try {\n const subtle = globalThis.crypto?.subtle;\n if (!subtle) {\n this.logger.error('SubtleCrypto not available in this environment');\n return false;\n }\n\n // Decode PEM public key to raw DER bytes\n const pemBody = publicKey\n .replace(/-----BEGIN PUBLIC KEY-----/, '')\n .replace(/-----END PUBLIC KEY-----/, '')\n .replace(/\\s/g, '');\n const keyBytes = Uint8Array.from(atob(pemBody), c => c.charCodeAt(0));\n\n // Configure algorithms based on RS256 or ES256\n let importAlgorithm: { name: string; hash?: string; namedCurve?: string };\n let verifyAlgorithm: { name: string; hash?: string };\n\n if (this.config.algorithm === 'ES256') {\n importAlgorithm = { name: 'ECDSA', namedCurve: 'P-256' };\n verifyAlgorithm = { name: 'ECDSA', hash: 'SHA-256' };\n } else {\n importAlgorithm = { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' };\n verifyAlgorithm = { name: 'RSASSA-PKCS1-v1_5' };\n }\n\n const cryptoKey = await subtle.importKey(\n 'spki',\n keyBytes,\n importAlgorithm,\n false,\n ['verify']\n );\n\n // Decode base64 signature to ArrayBuffer\n const signatureBytes = Uint8Array.from(atob(signature), c => c.charCodeAt(0));\n\n // Encode data to ArrayBuffer\n const dataBytes = new TextEncoder().encode(data);\n\n return await subtle.verify(verifyAlgorithm, cryptoKey, signatureBytes, dataBytes);\n } catch (error) {\n this.logger.error('Browser signature verification failed', error as Error);\n return false;\n }\n }\n \n private validateConfig(): void {\n if (!this.config.trustedPublicKeys || this.config.trustedPublicKeys.size === 0) {\n this.logger.warn('No trusted public keys configured - all signatures will fail');\n }\n \n if (!this.config.algorithm) {\n throw new Error('Signature algorithm must be specified');\n }\n \n if (!['RS256', 'ES256'].includes(this.config.algorithm)) {\n throw new Error(`Unsupported algorithm: ${this.config.algorithm}`);\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Per-file integrity verification for `.osplugin` artifacts (ADR-0025 §3.2).\n *\n * Checks an unpacked artifact's files against the manifest's declared\n * `integrity` map (artifact-relative path → SRI-style `sha256-<base64>`\n * digest, the format `os plugin build` writes). Like its sibling\n * `plugin-artifact-signature.ts`, this module is pure and dependency-free\n * (node:crypto only) so it stays byte-for-byte portable to the cloud\n * control plane, which owes the unpack-time re-verification leg\n * (ADR-0025 §3.5 step 5 — tracked on #11331, NOT discharged by this\n * module). The framework caller is the `os plugin publish` preflight: the\n * publisher self-checks its own artifact before upload.\n *\n * Verdict semantics:\n * - absent map (`undefined` / `null`) → ok, `skipped: true` — the field\n * is `.optional()` in the manifest schema, so artifacts predating\n * integrity computation stay publishable (permissive by contract).\n * - digest mismatch, declared entry with no file, or file with no\n * declared entry → NOT ok, with one structured violation per finding.\n * An extra file is refused because a stale map is exactly the drift\n * this check exists to catch.\n */\n\nimport { createHash } from 'node:crypto';\n\n/** A single unpacked artifact file. `path` is POSIX, archive-relative. */\nexport interface IntegrityFile {\n path: string;\n data: Uint8Array;\n}\n\nexport type IntegrityViolationKind = 'digest_mismatch' | 'missing_file' | 'extra_file';\n\n/** One structured integrity finding (the rejection envelope's unit). */\nexport interface IntegrityViolation {\n kind: IntegrityViolationKind;\n /** Artifact-relative POSIX path the finding is about. */\n path: string;\n /** The digest the manifest declares (absent for `extra_file`). */\n declared?: string;\n /** The digest computed from the supplied bytes (absent unless comparable). */\n actual?: string;\n}\n\nexport interface VerifyIntegrityResult {\n /** Overall verdict: every declared digest matched and no file was unaccounted for. */\n ok: boolean;\n /** True when no integrity map was supplied, so nothing was checked (still `ok`). */\n skipped: boolean;\n /** Number of declared entries whose digests were computed and compared. */\n checked: number;\n violations: IntegrityViolation[];\n}\n\n/** SRI hash algorithms this verifier can compute (`<alg>-<base64>`). */\nconst SRI_ALGORITHMS = new Set(['sha256', 'sha384', 'sha512']);\n\nfunction sriDigestFor(declared: string, data: Uint8Array): string {\n const dash = declared.indexOf('-');\n const alg = dash > 0 && SRI_ALGORITHMS.has(declared.slice(0, dash)) ? declared.slice(0, dash) : 'sha256';\n return `${alg}-${createHash(alg).update(data).digest('base64')}`;\n}\n\n/**\n * Verify `files` against the manifest's declared `integrity` map.\n *\n * `options.exempt` names paths outside the map's coverage — the compiled\n * manifest itself and the signature placeholder, which `computeIntegrity`\n * excludes at build time (the manifest cannot hash itself, and the\n * signature signs the manifest) — so their presence is never an\n * `extra_file` finding.\n */\nexport function verifyIntegrity(\n files: readonly IntegrityFile[],\n integrity: Readonly<Record<string, string>> | null | undefined,\n options: { exempt?: readonly string[] } = {},\n): VerifyIntegrityResult {\n if (integrity === null || integrity === undefined) {\n return { ok: true, skipped: true, checked: 0, violations: [] };\n }\n const exempt = new Set(options.exempt ?? []);\n const byPath = new Map<string, Uint8Array>();\n for (const f of files) {\n if (!exempt.has(f.path)) byPath.set(f.path, f.data);\n }\n\n const violations: IntegrityViolation[] = [];\n let checked = 0;\n for (const [path, declaredRaw] of Object.entries(integrity)) {\n if (exempt.has(path)) continue;\n const declared = typeof declaredRaw === 'string' ? declaredRaw : String(declaredRaw);\n const data = byPath.get(path);\n if (data === undefined) {\n violations.push({ kind: 'missing_file', path, declared });\n continue;\n }\n checked++;\n const actual = sriDigestFor(declared, data);\n if (actual !== declared) violations.push({ kind: 'digest_mismatch', path, declared, actual });\n }\n for (const path of [...byPath.keys()].sort()) {\n if (!Object.prototype.hasOwnProperty.call(integrity, path)) {\n violations.push({ kind: 'extra_file', path });\n }\n }\n return { ok: violations.length === 0, skipped: false, checked, violations };\n}\n\n/** Render one violation as a single human-actionable line. */\nexport function formatIntegrityViolation(v: IntegrityViolation): string {\n switch (v.kind) {\n case 'digest_mismatch':\n return `${v.path}: digest mismatch — manifest declares ${v.declared}, artifact bytes hash to ${v.actual}`;\n case 'missing_file':\n return `${v.path}: declared in the integrity map but absent from the artifact`;\n case 'extra_file':\n return `${v.path}: present in the artifact but not in the integrity map`;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Logger } from '@objectstack/spec/contracts';\nimport type { PluginCapability, PluginPermissions as GrantedPermissions } from '@objectstack/spec/kernel';\nimport type { PluginContext } from '../types.js';\n\n/**\n * Plugin Permissions\n * Defines what actions a plugin is allowed to perform\n */\nexport interface PluginPermissions {\n canAccessService(serviceName: string): boolean;\n canTriggerHook(hookName: string): boolean;\n canReadFile(path: string): boolean;\n canWriteFile(path: string): boolean;\n canNetworkRequest(url: string): boolean;\n}\n\n/**\n * Permission Check Result\n */\nexport interface PermissionCheckResult {\n allowed: boolean;\n reason?: string;\n capability?: string;\n}\n\n/**\n * Plugin Permission Enforcer\n * \n * Implements capability-based security model to enforce:\n * 1. Service access control - which services a plugin can use\n * 2. Hook restrictions - which hooks a plugin can trigger\n * 3. File system permissions - what files a plugin can read/write\n * 4. Network permissions - what URLs a plugin can access\n * \n * Architecture:\n * - Uses capability declarations from plugin manifest\n * - Checks permissions before allowing operations\n * - Logs all permission denials for security audit\n * - Supports allowlist and denylist patterns\n * \n * Security Model:\n * - Principle of least privilege - plugins get minimal permissions\n * - Explicit declaration - all capabilities must be declared\n * - Runtime enforcement - checks happen at operation time\n * - Audit trail - all denials are logged\n * \n * Usage:\n * ```typescript\n * const enforcer = new PluginPermissionEnforcer(logger);\n * enforcer.registerPluginPermissions(pluginName, capabilities);\n * enforcer.enforceServiceAccess(pluginName, 'database');\n * ```\n */\nexport class PluginPermissionEnforcer {\n private logger: Logger;\n private permissionRegistry: Map<string, PluginPermissions> = new Map();\n private capabilityRegistry: Map<string, PluginCapability[]> = new Map();\n \n constructor(logger: Logger) {\n this.logger = logger;\n }\n \n /**\n * Register plugin capabilities and build permission set\n * \n * @param pluginName - Plugin identifier\n * @param capabilities - Array of capability declarations\n */\n registerPluginPermissions(pluginName: string, capabilities: PluginCapability[]): void {\n this.capabilityRegistry.set(pluginName, capabilities);\n \n const permissions: PluginPermissions = {\n canAccessService: (service) => this.checkServiceAccess(capabilities, service),\n canTriggerHook: (hook) => this.checkHookAccess(capabilities, hook),\n canReadFile: (path) => this.checkFileRead(capabilities, path),\n canWriteFile: (path) => this.checkFileWrite(capabilities, path),\n canNetworkRequest: (url) => this.checkNetworkAccess(capabilities, url),\n };\n \n this.permissionRegistry.set(pluginName, permissions);\n \n this.logger.info(`Permissions registered for plugin: ${pluginName}`, {\n plugin: pluginName,\n capabilityCount: capabilities.length,\n });\n }\n \n /**\n * Register the install-time GRANTED permission set for a plugin\n * (ADR-0025 F4). This is the structured `{ services, hooks, network, fs }`\n * grant that the cloud control plane persists to\n * `sys_package_installation.granted_permissions` after the user consents\n * at install (ADR §3.5 step 2). The runtime calls this when materializing\n * a third-party plugin so {@link SecurePluginContext} enforces exactly the\n * consented surface — independent of whatever the manifest *requested*.\n *\n * Prefer this over {@link registerPluginPermissions} for distributed\n * plugins: it enforces what was granted, not what was declared.\n */\n registerGrantedPermissions(pluginName: string, granted: GrantedPermissions | null | undefined): void {\n this.permissionRegistry.set(pluginName, buildPermissionsFromGrants(granted));\n this.logger.info(`Granted permissions registered for plugin: ${pluginName}`, {\n plugin: pluginName,\n services: granted?.services?.length ?? 0,\n hooks: granted?.hooks?.length ?? 0,\n network: granted?.network?.length ?? 0,\n fs: granted?.fs?.length ?? 0,\n });\n }\n\n /**\n * Enforce service access permission\n *\n * @param pluginName - Plugin requesting access\n * @param serviceName - Service to access\n * @throws Error if permission denied\n */\n enforceServiceAccess(pluginName: string, serviceName: string): void {\n const result = this.checkPermission(pluginName, (perms) => perms.canAccessService(serviceName));\n \n if (!result.allowed) {\n const error = `Permission denied: Plugin ${pluginName} cannot access service ${serviceName}`;\n this.logger.warn(error, {\n plugin: pluginName,\n service: serviceName,\n reason: result.reason,\n });\n throw new Error(error);\n }\n \n this.logger.debug(`Service access granted: ${pluginName} -> ${serviceName}`);\n }\n \n /**\n * Enforce hook trigger permission\n * \n * @param pluginName - Plugin requesting access\n * @param hookName - Hook to trigger\n * @throws Error if permission denied\n */\n enforceHookTrigger(pluginName: string, hookName: string): void {\n const result = this.checkPermission(pluginName, (perms) => perms.canTriggerHook(hookName));\n \n if (!result.allowed) {\n const error = `Permission denied: Plugin ${pluginName} cannot trigger hook ${hookName}`;\n this.logger.warn(error, {\n plugin: pluginName,\n hook: hookName,\n reason: result.reason,\n });\n throw new Error(error);\n }\n \n this.logger.debug(`Hook trigger granted: ${pluginName} -> ${hookName}`);\n }\n \n /**\n * Enforce file read permission\n * \n * @param pluginName - Plugin requesting access\n * @param path - File path to read\n * @throws Error if permission denied\n */\n enforceFileRead(pluginName: string, path: string): void {\n const result = this.checkPermission(pluginName, (perms) => perms.canReadFile(path));\n \n if (!result.allowed) {\n const error = `Permission denied: Plugin ${pluginName} cannot read file ${path}`;\n this.logger.warn(error, {\n plugin: pluginName,\n path,\n reason: result.reason,\n });\n throw new Error(error);\n }\n \n this.logger.debug(`File read granted: ${pluginName} -> ${path}`);\n }\n \n /**\n * Enforce file write permission\n * \n * @param pluginName - Plugin requesting access\n * @param path - File path to write\n * @throws Error if permission denied\n */\n enforceFileWrite(pluginName: string, path: string): void {\n const result = this.checkPermission(pluginName, (perms) => perms.canWriteFile(path));\n \n if (!result.allowed) {\n const error = `Permission denied: Plugin ${pluginName} cannot write file ${path}`;\n this.logger.warn(error, {\n plugin: pluginName,\n path,\n reason: result.reason,\n });\n throw new Error(error);\n }\n \n this.logger.debug(`File write granted: ${pluginName} -> ${path}`);\n }\n \n /**\n * Enforce network request permission\n * \n * @param pluginName - Plugin requesting access\n * @param url - URL to access\n * @throws Error if permission denied\n */\n enforceNetworkRequest(pluginName: string, url: string): void {\n const result = this.checkPermission(pluginName, (perms) => perms.canNetworkRequest(url));\n \n if (!result.allowed) {\n const error = `Permission denied: Plugin ${pluginName} cannot access URL ${url}`;\n this.logger.warn(error, {\n plugin: pluginName,\n url,\n reason: result.reason,\n });\n throw new Error(error);\n }\n \n this.logger.debug(`Network request granted: ${pluginName} -> ${url}`);\n }\n \n /**\n * Get plugin capabilities\n * \n * @param pluginName - Plugin identifier\n * @returns Array of capabilities or undefined\n */\n getPluginCapabilities(pluginName: string): PluginCapability[] | undefined {\n return this.capabilityRegistry.get(pluginName);\n }\n \n /**\n * Get plugin permissions\n * \n * @param pluginName - Plugin identifier\n * @returns Permissions object or undefined\n */\n getPluginPermissions(pluginName: string): PluginPermissions | undefined {\n return this.permissionRegistry.get(pluginName);\n }\n \n /**\n * Revoke all permissions for a plugin\n * \n * @param pluginName - Plugin identifier\n */\n revokePermissions(pluginName: string): void {\n this.permissionRegistry.delete(pluginName);\n this.capabilityRegistry.delete(pluginName);\n this.logger.warn(`Permissions revoked for plugin: ${pluginName}`);\n }\n \n // Private methods\n \n private checkPermission(\n pluginName: string,\n check: (perms: PluginPermissions) => boolean\n ): PermissionCheckResult {\n const permissions = this.permissionRegistry.get(pluginName);\n \n if (!permissions) {\n return {\n allowed: false,\n reason: 'Plugin permissions not registered',\n };\n }\n \n const allowed = check(permissions);\n \n return {\n allowed,\n reason: allowed ? undefined : 'No matching capability found',\n };\n }\n \n private checkServiceAccess(capabilities: PluginCapability[], serviceName: string): boolean {\n // Check if plugin has capability to access this service\n return capabilities.some(cap => {\n const protocolId = cap.protocol.id;\n \n // Check for wildcard service access\n if (protocolId.includes('protocol.service.all')) {\n return true;\n }\n \n // Check for specific service protocol\n if (protocolId.includes(`protocol.service.${serviceName}`)) {\n return true;\n }\n \n // Check for service category match\n const serviceCategory = serviceName.split('.')[0];\n if (protocolId.includes(`protocol.service.${serviceCategory}`)) {\n return true;\n }\n \n return false;\n });\n }\n \n private checkHookAccess(capabilities: PluginCapability[], hookName: string): boolean {\n // Check if plugin has capability to trigger this hook\n return capabilities.some(cap => {\n const protocolId = cap.protocol.id;\n \n // Check for wildcard hook access\n if (protocolId.includes('protocol.hook.all')) {\n return true;\n }\n \n // Check for specific hook protocol\n if (protocolId.includes(`protocol.hook.${hookName}`)) {\n return true;\n }\n \n // Check for hook category match\n const hookCategory = hookName.split(':')[0];\n if (protocolId.includes(`protocol.hook.${hookCategory}`)) {\n return true;\n }\n \n return false;\n });\n }\n \n private matchGlob(pattern: string, str: string): boolean {\n const regexStr = pattern\n .split('**')\n .map(segment => {\n const escaped = segment.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return escaped.replace(/\\*/g, '[^/]*');\n })\n .join('.*');\n return new RegExp(`^${regexStr}$`).test(str);\n }\n \n private checkFileRead(capabilities: PluginCapability[], path: string): boolean {\n // Check if plugin has capability to read this file\n return capabilities.some(cap => {\n const protocolId = cap.protocol.id;\n \n // Check for file read capability\n if (protocolId.includes('protocol.filesystem.read')) {\n const paths = cap.metadata?.paths;\n if (!Array.isArray(paths) || paths.length === 0) {\n return true;\n }\n return paths.some(p => typeof p === 'string' && this.matchGlob(p, path));\n }\n \n return false;\n });\n }\n \n private checkFileWrite(capabilities: PluginCapability[], path: string): boolean {\n // Check if plugin has capability to write this file\n return capabilities.some(cap => {\n const protocolId = cap.protocol.id;\n \n // Check for file write capability\n if (protocolId.includes('protocol.filesystem.write')) {\n const paths = cap.metadata?.paths;\n if (!Array.isArray(paths) || paths.length === 0) {\n return true;\n }\n return paths.some(p => typeof p === 'string' && this.matchGlob(p, path));\n }\n \n return false;\n });\n }\n \n private checkNetworkAccess(capabilities: PluginCapability[], url: string): boolean {\n // Check if plugin has capability to access this URL\n return capabilities.some(cap => {\n const protocolId = cap.protocol.id;\n \n // Check for network capability\n if (protocolId.includes('protocol.network')) {\n const hosts = cap.metadata?.hosts;\n if (!Array.isArray(hosts) || hosts.length === 0) {\n return true;\n }\n return hosts.some(h => typeof h === 'string' && this.matchGlob(h, url));\n }\n \n return false;\n });\n }\n}\n\n/**\n * Secure Plugin Context\n * Wraps PluginContext with permission checks\n */\nexport class SecurePluginContext implements PluginContext {\n constructor(\n private pluginName: string,\n private permissionEnforcer: PluginPermissionEnforcer,\n private baseContext: PluginContext\n ) {}\n \n registerService(name: string, service: any): void {\n // No permission check for service registration (handled during init)\n this.baseContext.registerService(name, service);\n }\n \n getService<T>(name: string): T {\n // Check permission before accessing service\n this.permissionEnforcer.enforceServiceAccess(this.pluginName, name);\n return this.baseContext.getService<T>(name);\n }\n \n replaceService<T>(name: string, implementation: T): void {\n // Check permission before replacing service\n this.permissionEnforcer.enforceServiceAccess(this.pluginName, name);\n this.baseContext.replaceService(name, implementation);\n }\n \n getServices(): Map<string, any> {\n // Return all services (no permission check for listing)\n return this.baseContext.getServices();\n }\n \n hook(name: string, handler: (...args: any[]) => void | Promise<void>): void {\n // No permission check for registering hooks (handled during init)\n this.baseContext.hook(name, handler);\n }\n \n async trigger(name: string, ...args: any[]): Promise<void> {\n // Check permission before triggering hook\n this.permissionEnforcer.enforceHookTrigger(this.pluginName, name);\n await this.baseContext.trigger(name, ...args);\n }\n \n get logger() {\n return this.baseContext.logger;\n }\n \n getKernel() {\n return this.baseContext.getKernel();\n }\n\n registerServiceFactory(name: string, factory: (ctx: PluginContext, scopeId?: string) => any, lifecycle?: import('../plugin-loader.js').ServiceLifecycle, dependencies?: string[]): void {\n this.baseContext.registerServiceFactory(name, factory, lifecycle, dependencies);\n }\n\n getServiceScoped<T>(name: string, scopeId: string): Promise<T> {\n return this.baseContext.getServiceScoped<T>(name, scopeId);\n }\n}\n\n/**\n * Create a plugin permission enforcer\n *\n * @param logger - Logger instance\n * @returns Plugin permission enforcer\n */\nexport function createPluginPermissionEnforcer(logger: Logger): PluginPermissionEnforcer {\n return new PluginPermissionEnforcer(logger);\n}\n\n/**\n * Glob match supporting `*` (within a path segment) and `**` (across\n * segments). A bare `*` entry matches everything.\n */\nfunction grantGlobMatch(pattern: string, value: string): boolean {\n if (pattern === '*' || pattern === '**') return true;\n const regexStr = pattern\n .split('**')\n .map((segment) => segment.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '[^/]*'))\n .join('.*');\n return new RegExp(`^${regexStr}$`).test(value);\n}\n\n/** Extract the host from a URL for network-grant matching; falls back to the raw value. */\nfunction hostOf(url: string): string {\n try {\n return new URL(url).host;\n } catch {\n return url;\n }\n}\n\nconst inList = (list: string[] | undefined, value: string): boolean =>\n Array.isArray(list) && list.some((p) => p === value || grantGlobMatch(p, value));\n\n/**\n * Build the runtime {@link PluginPermissions} bag from a structured\n * install-time grant set (ADR-0025 §3.2 `{ services, hooks, network, fs }`).\n *\n * Matching: an entry allows when it equals the requested value, is a glob\n * that matches it, or is the wildcard `*`. Network grants match against the\n * request URL's host (or the raw URL). `fs` governs both read and write —\n * the structured grant set does not split the two. A null/empty grant set\n * denies everything (principle of least privilege).\n */\nexport function buildPermissionsFromGrants(\n granted: GrantedPermissions | null | undefined,\n): PluginPermissions {\n const services = granted?.services;\n const hooks = granted?.hooks;\n const network = granted?.network;\n const fs = granted?.fs;\n return {\n canAccessService: (name) => inList(services, name),\n canTriggerHook: (name) => inList(hooks, name),\n canReadFile: (path) => inList(fs, path),\n canWriteFile: (path) => inList(fs, path),\n canNetworkRequest: (url) =>\n inList(network, hostOf(url)) || inList(network, url),\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { \n PluginPermission,\n PluginPermissionSet,\n PermissionAction,\n ResourceType\n} from '@objectstack/spec/kernel';\nimport type { ObjectLogger } from '../logger.js';\n\n/**\n * Permission Grant\n * Represents a granted permission at runtime\n */\nexport interface PermissionGrant {\n permissionId: string;\n pluginId: string;\n grantedAt: Date;\n grantedBy?: string;\n expiresAt?: Date;\n conditions?: Record<string, any>;\n}\n\n/**\n * Permission Check Result\n */\nexport interface PermissionCheckResult {\n allowed: boolean;\n reason?: string;\n requiredPermission?: string;\n grantedPermissions?: string[];\n}\n\n/**\n * Plugin Permission Manager\n * \n * Manages fine-grained permissions for plugin security and access control\n */\nexport class PluginPermissionManager {\n private logger: ObjectLogger;\n \n // Plugin permission definitions\n private permissionSets = new Map<string, PluginPermissionSet>();\n \n // Granted permissions (pluginId -> Set of permission IDs)\n private grants = new Map<string, Set<string>>();\n \n // Permission grant details\n private grantDetails = new Map<string, PermissionGrant>();\n\n constructor(logger: ObjectLogger) {\n this.logger = logger.child({ component: 'PermissionManager' });\n }\n\n /**\n * Register permission requirements for a plugin\n */\n registerPermissions(pluginId: string, permissionSet: PluginPermissionSet): void {\n this.permissionSets.set(pluginId, permissionSet);\n \n this.logger.info('Permissions registered for plugin', { \n pluginId,\n permissionCount: permissionSet.permissions.length\n });\n }\n\n /**\n * Grant a permission to a plugin\n */\n grantPermission(\n pluginId: string,\n permissionId: string,\n grantedBy?: string,\n expiresAt?: Date\n ): void {\n // Verify permission exists in plugin's declared permissions\n const permissionSet = this.permissionSets.get(pluginId);\n if (!permissionSet) {\n throw new Error(`No permissions registered for plugin: ${pluginId}`);\n }\n\n const permission = permissionSet.permissions.find(p => p.id === permissionId);\n if (!permission) {\n throw new Error(`Permission ${permissionId} not declared by plugin ${pluginId}`);\n }\n\n // Create grant\n if (!this.grants.has(pluginId)) {\n this.grants.set(pluginId, new Set());\n }\n this.grants.get(pluginId)!.add(permissionId);\n\n // Store grant details\n const grantKey = `${pluginId}:${permissionId}`;\n this.grantDetails.set(grantKey, {\n permissionId,\n pluginId,\n grantedAt: new Date(),\n grantedBy,\n expiresAt,\n });\n\n this.logger.info('Permission granted', { \n pluginId, \n permissionId,\n grantedBy \n });\n }\n\n /**\n * Revoke a permission from a plugin\n */\n revokePermission(pluginId: string, permissionId: string): void {\n const grants = this.grants.get(pluginId);\n if (grants) {\n grants.delete(permissionId);\n \n const grantKey = `${pluginId}:${permissionId}`;\n this.grantDetails.delete(grantKey);\n\n this.logger.info('Permission revoked', { pluginId, permissionId });\n }\n }\n\n /**\n * Grant all permissions for a plugin\n */\n grantAllPermissions(pluginId: string, grantedBy?: string): void {\n const permissionSet = this.permissionSets.get(pluginId);\n if (!permissionSet) {\n throw new Error(`No permissions registered for plugin: ${pluginId}`);\n }\n\n for (const permission of permissionSet.permissions) {\n this.grantPermission(pluginId, permission.id, grantedBy);\n }\n\n this.logger.info('All permissions granted', { pluginId, grantedBy });\n }\n\n /**\n * Check if a plugin has a specific permission\n */\n hasPermission(pluginId: string, permissionId: string): boolean {\n const grants = this.grants.get(pluginId);\n if (!grants) {\n return false;\n }\n\n // Check if granted\n if (!grants.has(permissionId)) {\n return false;\n }\n\n // Check expiration\n const grantKey = `${pluginId}:${permissionId}`;\n const grantDetails = this.grantDetails.get(grantKey);\n if (grantDetails?.expiresAt && grantDetails.expiresAt < new Date()) {\n this.revokePermission(pluginId, permissionId);\n return false;\n }\n\n return true;\n }\n\n /**\n * Check if plugin can perform an action on a resource\n */\n checkAccess(\n pluginId: string,\n resource: ResourceType,\n action: PermissionAction,\n resourceId?: string\n ): PermissionCheckResult {\n const permissionSet = this.permissionSets.get(pluginId);\n if (!permissionSet) {\n return {\n allowed: false,\n reason: 'No permissions registered for plugin',\n };\n }\n\n // Find matching permissions\n const matchingPermissions = permissionSet.permissions.filter(p => {\n // Check resource type\n if (p.resource !== resource) {\n return false;\n }\n\n // Check action\n if (!p.actions.includes(action)) {\n return false;\n }\n\n // Check resource filter if specified\n if (resourceId && p.filter?.resourceIds) {\n if (!p.filter.resourceIds.includes(resourceId)) {\n return false;\n }\n }\n\n return true;\n });\n\n if (matchingPermissions.length === 0) {\n return {\n allowed: false,\n reason: `No permission found for ${action} on ${resource}`,\n };\n }\n\n // Check if any matching permission is granted\n const grantedPermissions = matchingPermissions.filter(p => \n this.hasPermission(pluginId, p.id)\n );\n\n if (grantedPermissions.length === 0) {\n return {\n allowed: false,\n reason: 'Required permissions not granted',\n requiredPermission: matchingPermissions[0].id,\n };\n }\n\n return {\n allowed: true,\n grantedPermissions: grantedPermissions.map(p => p.id),\n };\n }\n\n /**\n * Get all permissions for a plugin\n */\n getPluginPermissions(pluginId: string): PluginPermission[] {\n const permissionSet = this.permissionSets.get(pluginId);\n return permissionSet?.permissions || [];\n }\n\n /**\n * Get granted permissions for a plugin\n */\n getGrantedPermissions(pluginId: string): string[] {\n const grants = this.grants.get(pluginId);\n return grants ? Array.from(grants) : [];\n }\n\n /**\n * Get required but not granted permissions\n */\n getMissingPermissions(pluginId: string): PluginPermission[] {\n const permissionSet = this.permissionSets.get(pluginId);\n if (!permissionSet) {\n return [];\n }\n\n const granted = this.grants.get(pluginId) || new Set();\n \n return permissionSet.permissions.filter(p => \n p.required && !granted.has(p.id)\n );\n }\n\n /**\n * Check if all required permissions are granted\n */\n hasAllRequiredPermissions(pluginId: string): boolean {\n return this.getMissingPermissions(pluginId).length === 0;\n }\n\n /**\n * Get permission grant details\n */\n getGrantDetails(pluginId: string, permissionId: string): PermissionGrant | undefined {\n const grantKey = `${pluginId}:${permissionId}`;\n return this.grantDetails.get(grantKey);\n }\n\n /**\n * Validate permission against scope constraints\n */\n validatePermissionScope(\n permission: PluginPermission,\n context: {\n tenantId?: string;\n userId?: string;\n resourceId?: string;\n }\n ): boolean {\n switch (permission.scope) {\n case 'global':\n return true;\n\n case 'tenant':\n return !!context.tenantId;\n\n case 'user':\n return !!context.userId;\n\n case 'resource':\n return !!context.resourceId;\n\n case 'plugin':\n return true;\n\n default:\n return false;\n }\n }\n\n /**\n * Clear all permissions for a plugin\n */\n clearPluginPermissions(pluginId: string): void {\n this.permissionSets.delete(pluginId);\n \n const grants = this.grants.get(pluginId);\n if (grants) {\n for (const permissionId of grants) {\n const grantKey = `${pluginId}:${permissionId}`;\n this.grantDetails.delete(grantKey);\n }\n this.grants.delete(pluginId);\n }\n\n this.logger.info('All permissions cleared', { pluginId });\n }\n\n /**\n * Shutdown permission manager\n */\n shutdown(): void {\n this.permissionSets.clear();\n this.grants.clear();\n this.grantDetails.clear();\n \n this.logger.info('Permission manager shutdown complete');\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport nodePath from 'node:path';\n\nimport type { \n SandboxConfig\n} from '@objectstack/spec/kernel';\nimport type { ObjectLogger } from '../logger.js';\nimport { getMemoryUsage } from '../utils/env.js';\n\n/**\n * Resource Usage Statistics\n */\nexport interface ResourceUsage {\n memory: {\n current: number;\n peak: number;\n limit?: number;\n };\n cpu: {\n current: number;\n average: number;\n limit?: number;\n };\n connections: {\n current: number;\n limit?: number;\n };\n}\n\n/**\n * Sandbox Execution Context\n * Represents an isolated execution environment for a plugin\n */\nexport interface SandboxContext {\n pluginId: string;\n config: SandboxConfig;\n startTime: Date;\n resourceUsage: ResourceUsage;\n}\n\n/**\n * Plugin Sandbox Runtime\n * \n * Provides isolated execution environments for plugins with resource limits\n * and access controls\n */\nexport class PluginSandboxRuntime {\n private static readonly MONITORING_INTERVAL_MS = 5000;\n\n private logger: ObjectLogger;\n \n // Active sandboxes (pluginId -> context)\n private sandboxes = new Map<string, SandboxContext>();\n \n // Resource monitoring intervals\n private monitoringIntervals = new Map<string, NodeJS.Timeout>();\n\n // Per-plugin resource baselines for delta tracking\n private memoryBaselines = new Map<string, number>();\n private cpuBaselines = new Map<string, { user: number; system: number }>();\n\n constructor(logger: ObjectLogger) {\n this.logger = logger.child({ component: 'SandboxRuntime' });\n }\n\n /**\n * Create a sandbox for a plugin\n */\n createSandbox(pluginId: string, config: SandboxConfig): SandboxContext {\n if (this.sandboxes.has(pluginId)) {\n throw new Error(`Sandbox already exists for plugin: ${pluginId}`);\n }\n\n const context: SandboxContext = {\n pluginId,\n config,\n startTime: new Date(),\n resourceUsage: {\n memory: { current: 0, peak: 0, limit: config.memory?.maxHeap },\n cpu: { current: 0, average: 0, limit: config.cpu?.maxCpuPercent },\n connections: { current: 0, limit: config.network?.maxConnections },\n },\n };\n\n this.sandboxes.set(pluginId, context);\n\n // Capture resource baselines for per-plugin delta tracking\n const baselineMemory = getMemoryUsage();\n this.memoryBaselines.set(pluginId, baselineMemory.heapUsed);\n this.cpuBaselines.set(pluginId, process.cpuUsage());\n\n // Start resource monitoring\n this.startResourceMonitoring(pluginId);\n\n this.logger.info('Sandbox created', { \n pluginId,\n level: config.level,\n memoryLimit: config.memory?.maxHeap,\n cpuLimit: config.cpu?.maxCpuPercent\n });\n\n return context;\n }\n\n /**\n * Destroy a sandbox\n */\n destroySandbox(pluginId: string): void {\n const context = this.sandboxes.get(pluginId);\n if (!context) {\n return;\n }\n\n // Stop monitoring\n this.stopResourceMonitoring(pluginId);\n\n this.memoryBaselines.delete(pluginId);\n this.cpuBaselines.delete(pluginId);\n this.sandboxes.delete(pluginId);\n\n this.logger.info('Sandbox destroyed', { pluginId });\n }\n\n /**\n * Check if resource access is allowed\n */\n checkResourceAccess(\n pluginId: string,\n resourceType: 'file' | 'network' | 'process' | 'env',\n resourcePath?: string\n ): { allowed: boolean; reason?: string } {\n const context = this.sandboxes.get(pluginId);\n if (!context) {\n return { allowed: false, reason: 'Sandbox not found' };\n }\n\n const { config } = context;\n\n switch (resourceType) {\n case 'file':\n return this.checkFileAccess(config, resourcePath);\n \n case 'network':\n return this.checkNetworkAccess(config, resourcePath);\n \n case 'process':\n return this.checkProcessAccess(config);\n \n case 'env':\n return this.checkEnvAccess(config, resourcePath);\n \n default:\n return { allowed: false, reason: 'Unknown resource type' };\n }\n }\n\n /**\n * Check file system access\n * Uses path.resolve() and path.normalize() to prevent directory traversal.\n */\n private checkFileAccess(\n config: SandboxConfig,\n filePath?: string\n ): { allowed: boolean; reason?: string } {\n if (config.level === 'none') {\n return { allowed: true };\n }\n\n if (!config.filesystem) {\n return { allowed: false, reason: 'File system access not configured' };\n }\n\n // If no path specified, check general access\n if (!filePath) {\n return { allowed: config.filesystem.mode !== 'none' };\n }\n\n // Check allowed paths using proper path resolution to prevent directory traversal\n const allowedPaths = config.filesystem.allowedPaths || [];\n const resolvedPath = nodePath.normalize(nodePath.resolve(filePath));\n const isAllowed = allowedPaths.some(allowed => {\n const resolvedAllowed = nodePath.normalize(nodePath.resolve(allowed));\n return resolvedPath.startsWith(resolvedAllowed);\n });\n\n if (allowedPaths.length > 0 && !isAllowed) {\n return { \n allowed: false, \n reason: `Path not in allowed list: ${filePath}` \n };\n }\n\n // Check denied paths using proper path resolution\n const deniedPaths = config.filesystem.deniedPaths || [];\n const isDenied = deniedPaths.some(denied => {\n const resolvedDenied = nodePath.normalize(nodePath.resolve(denied));\n return resolvedPath.startsWith(resolvedDenied);\n });\n\n if (isDenied) {\n return { \n allowed: false, \n reason: `Path is explicitly denied: ${filePath}` \n };\n }\n\n return { allowed: true };\n }\n\n /**\n * Check network access\n * Uses URL parsing to properly validate hostnames.\n */\n private checkNetworkAccess(\n config: SandboxConfig,\n url?: string\n ): { allowed: boolean; reason?: string } {\n if (config.level === 'none') {\n return { allowed: true };\n }\n\n if (!config.network) {\n return { allowed: false, reason: 'Network access not configured' };\n }\n\n // Check if network access is enabled\n if (config.network.mode === 'none') {\n return { allowed: false, reason: 'Network access disabled' };\n }\n\n // If no URL specified, check general access\n if (!url) {\n return { allowed: (config.network.mode as string) !== 'none' };\n }\n\n // Parse URL and check hostname against allowed/denied hosts\n let parsedHostname: string;\n try {\n parsedHostname = new URL(url).hostname;\n } catch {\n return { allowed: false, reason: `Invalid URL: ${url}` };\n }\n\n // Check allowed hosts\n const allowedHosts = config.network.allowedHosts || [];\n if (allowedHosts.length > 0) {\n const isAllowed = allowedHosts.some(host => {\n return parsedHostname === host;\n });\n\n if (!isAllowed) {\n return { \n allowed: false, \n reason: `Host not in allowed list: ${url}` \n };\n }\n }\n\n // Check denied hosts\n const deniedHosts = config.network.deniedHosts || [];\n const isDenied = deniedHosts.some(host => {\n return parsedHostname === host;\n });\n\n if (isDenied) {\n return { \n allowed: false, \n reason: `Host is blocked: ${url}` \n };\n }\n\n return { allowed: true };\n }\n\n /**\n * Check process spawning access\n */\n private checkProcessAccess(\n config: SandboxConfig\n ): { allowed: boolean; reason?: string } {\n if (config.level === 'none') {\n return { allowed: true };\n }\n\n if (!config.process) {\n return { allowed: false, reason: 'Process access not configured' };\n }\n\n if (!config.process.allowSpawn) {\n return { allowed: false, reason: 'Process spawning not allowed' };\n }\n\n return { allowed: true };\n }\n\n /**\n * Check environment variable access\n */\n private checkEnvAccess(\n config: SandboxConfig,\n varName?: string\n ): { allowed: boolean; reason?: string } {\n if (config.level === 'none') {\n return { allowed: true };\n }\n\n if (!config.process) {\n return { allowed: false, reason: 'Environment access not configured' };\n }\n\n // If no variable specified, check general access\n if (!varName) {\n return { allowed: true };\n }\n\n // For now, allow all env access if process is configured\n // In a real implementation, would check specific allowed vars\n return { allowed: true };\n }\n\n /**\n * Check resource limits\n */\n checkResourceLimits(pluginId: string): { \n withinLimits: boolean; \n violations: string[] \n } {\n const context = this.sandboxes.get(pluginId);\n if (!context) {\n return { withinLimits: true, violations: [] };\n }\n\n const violations: string[] = [];\n const { resourceUsage, config } = context;\n\n // Check memory limit\n if (config.memory?.maxHeap && \n resourceUsage.memory.current > config.memory.maxHeap) {\n violations.push(`Memory limit exceeded: ${resourceUsage.memory.current} > ${config.memory.maxHeap}`);\n }\n\n // Check CPU limit (would need runtime config)\n if (config.runtime?.resourceLimits?.maxCpu && \n resourceUsage.cpu.current > config.runtime.resourceLimits.maxCpu) {\n violations.push(`CPU limit exceeded: ${resourceUsage.cpu.current}% > ${config.runtime.resourceLimits.maxCpu}%`);\n }\n\n // Check connection limit\n if (config.network?.maxConnections && \n resourceUsage.connections.current > config.network.maxConnections) {\n violations.push(`Connection limit exceeded: ${resourceUsage.connections.current} > ${config.network.maxConnections}`);\n }\n\n return {\n withinLimits: violations.length === 0,\n violations,\n };\n }\n\n /**\n * Get resource usage for a plugin\n */\n getResourceUsage(pluginId: string): ResourceUsage | undefined {\n const context = this.sandboxes.get(pluginId);\n return context?.resourceUsage;\n }\n\n /**\n * Start monitoring resource usage\n */\n private startResourceMonitoring(pluginId: string): void {\n // Monitor at the configured interval\n const interval = setInterval(() => {\n this.updateResourceUsage(pluginId);\n }, PluginSandboxRuntime.MONITORING_INTERVAL_MS);\n\n this.monitoringIntervals.set(pluginId, interval);\n }\n\n /**\n * Stop monitoring resource usage\n */\n private stopResourceMonitoring(pluginId: string): void {\n const interval = this.monitoringIntervals.get(pluginId);\n if (interval) {\n clearInterval(interval);\n this.monitoringIntervals.delete(pluginId);\n }\n }\n\n /**\n * Update resource usage statistics\n * \n * Tracks per-plugin memory and CPU usage using delta from baseline\n * captured at sandbox creation time. This is an approximation since\n * true per-plugin isolation isn't possible in a single Node.js process.\n */\n private updateResourceUsage(pluginId: string): void {\n const context = this.sandboxes.get(pluginId);\n if (!context) {\n return;\n }\n\n // In a real implementation, this would collect actual metrics\n // For now, this is a placeholder structure\n \n // Update memory usage using delta from baseline for per-plugin approximation\n const memoryUsage = getMemoryUsage();\n const memoryBaseline = this.memoryBaselines.get(pluginId) ?? 0;\n const memoryDelta = Math.max(0, memoryUsage.heapUsed - memoryBaseline);\n context.resourceUsage.memory.current = memoryDelta;\n context.resourceUsage.memory.peak = Math.max(\n context.resourceUsage.memory.peak,\n memoryDelta\n );\n\n // Update CPU usage using delta from baseline for per-plugin approximation\n const cpuBaseline = this.cpuBaselines.get(pluginId) ?? { user: 0, system: 0 };\n const cpuCurrent = process.cpuUsage();\n const cpuDeltaUser = cpuCurrent.user - cpuBaseline.user;\n const cpuDeltaSystem = cpuCurrent.system - cpuBaseline.system;\n // Convert microseconds to a percentage approximation over the monitoring interval\n const totalCpuMicros = cpuDeltaUser + cpuDeltaSystem;\n const intervalMicros = PluginSandboxRuntime.MONITORING_INTERVAL_MS * 1000;\n context.resourceUsage.cpu.current = (totalCpuMicros / intervalMicros) * 100;\n // Update baseline for next interval\n this.cpuBaselines.set(pluginId, cpuCurrent);\n\n // Check for violations\n const { withinLimits, violations } = this.checkResourceLimits(pluginId);\n if (!withinLimits) {\n this.logger.warn('Resource limit violations detected', { \n pluginId, \n violations \n });\n }\n }\n\n /**\n * Get all active sandboxes\n */\n getAllSandboxes(): Map<string, SandboxContext> {\n return new Map(this.sandboxes);\n }\n\n /**\n * Shutdown sandbox runtime\n */\n shutdown(): void {\n // Stop all monitoring\n for (const pluginId of this.monitoringIntervals.keys()) {\n this.stopResourceMonitoring(pluginId);\n }\n\n this.sandboxes.clear();\n this.memoryBaselines.clear();\n this.cpuBaselines.clear();\n \n this.logger.info('Sandbox runtime shutdown complete');\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { \n KernelSecurityVulnerability,\n KernelSecurityScanResult\n} from '@objectstack/spec/kernel';\nimport type { ObjectLogger } from '../logger.js';\n\n/**\n * Scan Target\n */\nexport interface ScanTarget {\n pluginId: string;\n version: string;\n files?: string[];\n dependencies?: Record<string, string>;\n}\n\n/**\n * Security Issue\n */\nexport interface SecurityIssue {\n id: string;\n severity: 'critical' | 'high' | 'medium' | 'low' | 'info';\n category: 'vulnerability' | 'malware' | 'license' | 'code-quality' | 'configuration';\n title: string;\n description: string;\n location?: {\n file?: string;\n line?: number;\n column?: number;\n };\n remediation?: string;\n cve?: string;\n cvss?: number;\n}\n\n/**\n * Plugin Security Scanner\n * \n * Scans plugins for security vulnerabilities, malware, and license issues\n */\nexport class PluginSecurityScanner {\n private logger: ObjectLogger;\n \n // Known vulnerabilities database (CVE cache)\n private vulnerabilityDb = new Map<string, KernelSecurityVulnerability>();\n \n // Scan results cache\n private scanResults = new Map<string, KernelSecurityScanResult>();\n\n private passThreshold: number = 70;\n\n constructor(logger: ObjectLogger, config?: { passThreshold?: number }) {\n this.logger = logger.child({ component: 'SecurityScanner' });\n if (config?.passThreshold !== undefined) {\n this.passThreshold = config.passThreshold;\n }\n }\n\n /**\n * Perform a comprehensive security scan on a plugin\n */\n async scan(target: ScanTarget): Promise<KernelSecurityScanResult> {\n this.logger.info('Starting security scan', { \n pluginId: target.pluginId,\n version: target.version \n });\n\n const issues: SecurityIssue[] = [];\n\n try {\n // 1. Scan for code vulnerabilities\n const codeIssues = await this.scanCode(target);\n issues.push(...codeIssues);\n\n // 2. Scan dependencies for known vulnerabilities\n const depIssues = await this.scanDependencies(target);\n issues.push(...depIssues);\n\n // 3. Scan for malware patterns\n const malwareIssues = await this.scanMalware(target);\n issues.push(...malwareIssues);\n\n // 4. Check license compliance\n const licenseIssues = await this.scanLicenses(target);\n issues.push(...licenseIssues);\n\n // 5. Check configuration security\n const configIssues = await this.scanConfiguration(target);\n issues.push(...configIssues);\n\n // Calculate security score (0-100, higher is better)\n const score = this.calculateSecurityScore(issues);\n\n const result: KernelSecurityScanResult = {\n timestamp: new Date().toISOString(),\n scanner: { name: 'ObjectStack Security Scanner', version: '1.0.0' },\n status: score >= this.passThreshold ? 'passed' : 'failed',\n vulnerabilities: issues.map(issue => ({\n id: issue.id,\n severity: issue.severity,\n category: issue.category,\n title: issue.title,\n description: issue.description,\n location: issue.location ? `${issue.location.file}:${issue.location.line}` : undefined,\n remediation: issue.remediation,\n affectedVersions: [],\n exploitAvailable: false,\n patchAvailable: false,\n })),\n summary: {\n totalVulnerabilities: issues.length,\n criticalCount: issues.filter(i => i.severity === 'critical').length,\n highCount: issues.filter(i => i.severity === 'high').length,\n mediumCount: issues.filter(i => i.severity === 'medium').length,\n lowCount: issues.filter(i => i.severity === 'low').length,\n infoCount: issues.filter(i => i.severity === 'info').length,\n },\n };\n\n this.scanResults.set(`${target.pluginId}:${target.version}`, result);\n\n this.logger.info('Security scan complete', { \n pluginId: target.pluginId,\n score,\n status: result.status,\n summary: result.summary\n });\n\n return result;\n } catch (error) {\n this.logger.error('Security scan failed', { \n pluginId: target.pluginId, \n error \n });\n\n throw error;\n }\n }\n\n /**\n * Scan code for vulnerabilities\n */\n private async scanCode(target: ScanTarget): Promise<SecurityIssue[]> {\n const issues: SecurityIssue[] = [];\n\n // In a real implementation, this would:\n // - Parse code with AST (e.g., using @typescript-eslint/parser)\n // - Check for dangerous patterns (eval, Function constructor, etc.)\n // - Check for XSS vulnerabilities\n // - Check for SQL injection patterns\n // - Check for insecure crypto usage\n // - Check for path traversal vulnerabilities\n\n this.logger.debug('Code scan complete', { \n pluginId: target.pluginId,\n issuesFound: issues.length \n });\n\n return issues;\n }\n\n /**\n * Scan dependencies for known vulnerabilities\n */\n private async scanDependencies(target: ScanTarget): Promise<SecurityIssue[]> {\n const issues: SecurityIssue[] = [];\n\n if (!target.dependencies) {\n return issues;\n }\n\n // In a real implementation, this would:\n // - Query npm audit API\n // - Check GitHub Advisory Database\n // - Check Snyk vulnerability database\n // - Check OSV (Open Source Vulnerabilities)\n\n for (const [depName, version] of Object.entries(target.dependencies)) {\n const vulnKey = `${depName}@${version}`;\n const vulnerability = this.vulnerabilityDb.get(vulnKey);\n\n if (vulnerability) {\n issues.push({\n id: `vuln-${vulnerability.cve || depName}`,\n severity: vulnerability.severity,\n category: 'vulnerability',\n title: `Vulnerable dependency: ${depName}`,\n description: `${depName}@${version} has known security vulnerabilities`,\n remediation: vulnerability.fixedIn \n ? `Upgrade to ${vulnerability.fixedIn.join(' or ')}`\n : 'No fix available',\n cve: vulnerability.cve,\n });\n }\n }\n\n this.logger.debug('Dependency scan complete', { \n pluginId: target.pluginId,\n dependencies: Object.keys(target.dependencies).length,\n vulnerabilities: issues.length \n });\n\n return issues;\n }\n\n /**\n * Scan for malware patterns\n */\n private async scanMalware(target: ScanTarget): Promise<SecurityIssue[]> {\n const issues: SecurityIssue[] = [];\n\n // In a real implementation, this would:\n // - Check for obfuscated code\n // - Check for suspicious network activity patterns\n // - Check for crypto mining patterns\n // - Check for data exfiltration patterns\n // - Use ML-based malware detection\n // - Check file hashes against known malware databases\n\n this.logger.debug('Malware scan complete', { \n pluginId: target.pluginId,\n issuesFound: issues.length \n });\n\n return issues;\n }\n\n /**\n * Check license compliance\n */\n private async scanLicenses(target: ScanTarget): Promise<SecurityIssue[]> {\n const issues: SecurityIssue[] = [];\n\n if (!target.dependencies) {\n return issues;\n }\n\n // In a real implementation, this would:\n // - Check license compatibility\n // - Detect GPL contamination\n // - Flag proprietary dependencies\n // - Check for missing licenses\n // - Verify SPDX identifiers\n\n this.logger.debug('License scan complete', { \n pluginId: target.pluginId,\n issuesFound: issues.length \n });\n\n return issues;\n }\n\n /**\n * Check configuration security\n */\n private async scanConfiguration(target: ScanTarget): Promise<SecurityIssue[]> {\n const issues: SecurityIssue[] = [];\n\n // In a real implementation, this would:\n // - Check for hardcoded secrets\n // - Check for weak permissions\n // - Check for insecure defaults\n // - Check for missing security headers\n // - Check CSP policies\n\n this.logger.debug('Configuration scan complete', { \n pluginId: target.pluginId,\n issuesFound: issues.length \n });\n\n return issues;\n }\n\n /**\n * Calculate security score based on issues\n */\n private calculateSecurityScore(issues: SecurityIssue[]): number {\n // Start with perfect score\n let score = 100;\n\n // Deduct points based on severity\n for (const issue of issues) {\n switch (issue.severity) {\n case 'critical':\n score -= 20;\n break;\n case 'high':\n score -= 10;\n break;\n case 'medium':\n score -= 5;\n break;\n case 'low':\n score -= 2;\n break;\n case 'info':\n score -= 0;\n break;\n }\n }\n\n // Ensure score doesn't go below 0\n return Math.max(0, score);\n }\n\n /**\n * Add a vulnerability to the database\n */\n addVulnerability(\n packageName: string,\n version: string,\n vulnerability: KernelSecurityVulnerability\n ): void {\n const key = `${packageName}@${version}`;\n this.vulnerabilityDb.set(key, vulnerability);\n \n this.logger.debug('Vulnerability added to database', { \n package: packageName, \n version,\n cve: vulnerability.cve \n });\n }\n\n /**\n * Get scan result from cache\n */\n getScanResult(pluginId: string, version: string): KernelSecurityScanResult | undefined {\n return this.scanResults.get(`${pluginId}:${version}`);\n }\n\n /**\n * Clear scan results cache\n */\n clearCache(): void {\n this.scanResults.clear();\n this.logger.debug('Scan results cache cleared');\n }\n\n /**\n * Update vulnerability database from external source\n */\n async updateVulnerabilityDatabase(): Promise<void> {\n this.logger.info('Updating vulnerability database');\n\n // In a real implementation, this would:\n // - Fetch from GitHub Advisory Database\n // - Fetch from npm audit\n // - Fetch from NVD (National Vulnerability Database)\n // - Parse and cache vulnerability data\n\n this.logger.info('Vulnerability database updated', { \n entries: this.vulnerabilityDb.size \n });\n }\n\n /**\n * Shutdown security scanner\n */\n shutdown(): void {\n this.vulnerabilityDb.clear();\n this.scanResults.clear();\n \n this.logger.info('Security scanner shutdown complete');\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * api-key — hand-rolled API-key primitives + verifier for `sys_api_key`.\n *\n * better-auth 1.6.x ships no apiKey plugin, so ObjectStack owns the full\n * lifecycle: generation, at-rest hashing, header extraction, validation, and\n * the verify-time principal lookup. This is the SINGLE shared source of truth\n * used by BOTH inbound surfaces — the runtime dispatcher / MCP path\n * (`resolveExecutionContext`) and the REST data API (`@objectstack/rest`) — so\n * the two can never drift on how a key authenticates. It lives in\n * `@objectstack/core` (server-side; both `runtime` and `rest` depend on it,\n * and `core` depends on neither, so there is no cycle).\n *\n * SECURITY (zero-tolerance):\n * - The raw key is returned EXACTLY ONCE, by {@link generateApiKey}. It is\n * never persisted; only `sha256(raw)` (hex) is stored in `sys_api_key.key`.\n * - The raw key and its hash must never enter logs, HTTP responses, error\n * messages, commit messages or comments.\n * - Validation is fail-closed: anything ambiguous (missing, revoked, expired,\n * malformed) resolves to \"no principal\", never to an elevated one.\n */\n\nimport { createHash, randomBytes } from 'node:crypto';\n\nimport { postureEnforcesWall, postureUsesUnionScope, normalizeTenancyPosture } from '@objectstack/spec/security';\nimport type { TenancyPosture } from '@objectstack/spec/security';\n\n/** Default visible prefix for generated keys (helps users identify a key). */\nexport const API_KEY_PREFIX = 'osk_';\n\n/** Bytes of entropy in the secret portion of a generated key (256 bits). */\nconst API_KEY_ENTROPY_BYTES = 32;\n\n/** Length of the human-visible prefix stored in `sys_api_key.prefix`. */\nconst VISIBLE_PREFIX_LEN = 12;\n\n/**\n * Derive the at-rest hash for an API key. Inbound keys are hashed the same way\n * before the DB lookup. Because the lookup matches an indexed, high-entropy\n * hash exactly, this doubles as a constant-effort comparison: an attacker\n * cannot recover the raw key by probing for partial matches.\n */\nexport function hashApiKey(raw: string): string {\n return createHash('sha256').update(raw, 'utf8').digest('hex');\n}\n\n/** Result of {@link generateApiKey}. `raw` is shown to the user only once. */\nexport interface GeneratedApiKey {\n /** The full secret to hand to the client. NEVER persist this. */\n raw: string;\n /** `sha256(raw)` hex — store this in `sys_api_key.key`. */\n hash: string;\n /** Short non-secret prefix for display/identification (`sys_api_key.prefix`). */\n prefix: string;\n}\n\n/**\n * Generate a fresh API key. Returns the raw secret (caller must surface it to\n * the user exactly once and then discard it), its at-rest hash, and a short\n * non-secret prefix for display.\n */\nexport function generateApiKey(prefix: string = API_KEY_PREFIX): GeneratedApiKey {\n // base64url so the token is URL/header-safe with no padding.\n const secret = randomBytes(API_KEY_ENTROPY_BYTES).toString('base64url');\n const raw = `${prefix}${secret}`;\n return {\n raw,\n hash: hashApiKey(raw),\n prefix: raw.slice(0, VISIBLE_PREFIX_LEN),\n };\n}\n\n/**\n * Extract an API key from request headers. Accepts, in order:\n * - `X-API-Key: <token>`\n * - `Authorization: ApiKey <token>` (case-insensitive scheme)\n * - `Authorization: Bearer <token>` ONLY when `<token>` carries the ObjectStack\n * api-key prefix (`osk_`). Remote MCP clients (Claude Desktop / Cursor /\n * Claude Code) authenticate to `/api/v1/mcp` with the key as a Bearer per the\n * MCP spec, so rejecting Bearer outright made every standard MCP client fail.\n * A better-auth *session* token never starts with `osk_`, so a session Bearer\n * still falls through to the session path — this can't shadow it.\n */\nexport function extractApiKey(headers: any): string | undefined {\n const x = readHeader(headers, 'x-api-key');\n if (x && x.trim()) return x.trim();\n const auth = readHeader(headers, 'authorization');\n if (!auth) return undefined;\n const apiKeyScheme = auth.match(/^ApiKey\\s+(\\S.*)$/i);\n if (apiKeyScheme?.[1]?.trim()) return apiKeyScheme[1].trim();\n // Bearer is accepted only for prefixed api-keys (never for session tokens).\n const bearer = auth.match(/^Bearer\\s+(\\S.*)$/i)?.[1]?.trim();\n if (bearer && bearer.startsWith(API_KEY_PREFIX)) return bearer;\n return undefined;\n}\n\n/** Parse a `scopes` value that may be a JSON-string textarea or a real array. */\nexport function parseScopes(value: unknown): string[] {\n if (Array.isArray(value)) {\n return value.filter((s): s is string => typeof s === 'string' && s.length > 0);\n }\n if (typeof value === 'string' && value.trim()) {\n const parsed = safeJsonParse<unknown>(value, []);\n if (Array.isArray(parsed)) {\n return parsed.filter((s): s is string => typeof s === 'string' && s.length > 0);\n }\n }\n return [];\n}\n\n/** Return true when an expiry timestamp is in the past (i.e. the key is dead). */\nexport function isExpired(value: unknown, nowMs: number): boolean {\n if (value == null) return false;\n let ms: number;\n if (typeof value === 'number') {\n // Heuristic: seconds vs milliseconds epoch.\n ms = value < 1e12 ? value * 1000 : value;\n } else if (value instanceof Date) {\n ms = value.getTime();\n } else if (typeof value === 'string') {\n ms = Date.parse(value);\n } else {\n return false;\n }\n if (Number.isNaN(ms)) return false;\n return ms <= nowMs;\n}\n\n/** The principal resolved from a valid `sys_api_key`. */\nexport interface ApiKeyPrincipal {\n userId: string;\n /**\n * The organization this key authenticates INTO — read from the row's\n * `active_organization_id` and adopted by `resolveAuthzContext` as the\n * request's active organization (`ExecutionContext.tenantId`), which is what\n * lets the ADR-0105 Layer 0 wall match. `undefined` for a key minted before\n * #8287, or one minted under the `single` posture where there is no\n * organization to inherit.\n */\n tenantId?: string;\n scopes: string[];\n}\n\n/**\n * [#8287] Why a key was refused. Distinct from \"no key present\" and from \"this\n * key is unknown/revoked/expired\": a refusal means the credential is real and\n * intact but cannot be admitted under this deployment's tenancy posture.\n */\nexport type ApiKeyRefusalReason = 'organization_required' | 'organization_membership_ended';\n\n/**\n * The verdict on an inbound API key. Three outcomes, deliberately distinct:\n *\n * - `none` — no key header, or a key that is unknown / revoked / expired /\n * owner-less. Indistinguishable by design (never tell a prober which), and\n * the caller MAY fall through to the session path exactly as before.\n * - `admitted` — a usable principal.\n * - `refused` — a real, intact key the posture cannot admit. The caller must\n * NOT fall through to the session path: falling through would be more\n * permissive than today's behaviour (an API key already outranks a session),\n * and the whole point of the refusal is that it is LOUD at call time.\n */\nexport type ApiKeyAdmission =\n | { outcome: 'none' }\n | { outcome: 'admitted'; principal: ApiKeyPrincipal }\n | { outcome: 'refused'; reason: ApiKeyRefusalReason; message: string };\n\n/**\n * The shape of the kernel's `tenancy` service this module reads a posture from.\n * Structural on purpose: `@objectstack/core` must not depend on the plugin that\n * provides it, and an embedding without that plugin simply supplies nothing.\n */\nexport interface TenancyPostureSource {\n posture?: string;\n isolationActive?: boolean;\n}\n\n/**\n * [#8287] Resolve the EFFECTIVE tenancy posture from the kernel's `tenancy`\n * service — the same reconciliation `plugin-security` performs before handing a\n * posture to `computeTenantLayer0Filter`, so the wall and the API-key admission\n * can never disagree about which posture is in force.\n *\n * ⚠️ Deliberately NOT `resolveTenancyPosture()` from `@objectstack/types`, which\n * reads `OS_TENANCY_POSTURE` directly. That answers what the operator ASKED\n * for, not what is ENFORCED: under ADR-0093 D4/D5 a deployment that requests\n * `isolated` without the enterprise `@objectstack/organizations` runtime\n * resolves to `single` and runs with NO organization wall. Reading the env\n * there would refuse org-less API keys on a deployment whose wall is not even\n * active — breaking working automation to enforce a boundary that does not\n * exist. The `tenancy` service is the one place that already knows the\n * difference.\n *\n * Returns `undefined` when no service is available, which callers must treat as\n * \"no posture-conditional refusal\" — see {@link resolveApiKeyAdmission}.\n */\nexport function effectiveTenancyPosture(\n tenancy: TenancyPostureSource | undefined | null,\n): TenancyPosture | undefined {\n if (!tenancy) return undefined;\n return normalizeTenancyPosture(tenancy.posture) ?? (tenancy.isolationActive ? 'isolated' : 'single');\n}\n\n/**\n * Verify an inbound API key against `sys_api_key` and resolve its principal.\n * This is the ONE verify path shared by the dispatcher/MCP and REST surfaces.\n *\n * Fail-closed: returns `undefined` for a missing key, an unusable data engine,\n * a lookup error, or a key that is unknown / revoked / expired / owner-less.\n *\n * @param ql A data engine with `find(object, { where, limit, context })`.\n * @param headers Request headers (Web `Headers` or a plain object).\n * @param nowMs Clock for expiry checks (injectable for tests).\n */\nexport async function resolveApiKeyPrincipal(\n ql: any,\n headers: any,\n nowMs: number = Date.now(),\n tenancyPosture?: TenancyPosture,\n): Promise<ApiKeyPrincipal | undefined> {\n const admission = await resolveApiKeyAdmission(ql, headers, nowMs, tenancyPosture);\n return admission.outcome === 'admitted' ? admission.principal : undefined;\n}\n\n/**\n * [#8287] The full verdict behind {@link resolveApiKeyPrincipal} — same lookup,\n * but it distinguishes a POSTURE REFUSAL from \"no principal\".\n *\n * `resolveApiKeyPrincipal` collapses `refused` into `undefined` so every\n * existing caller keeps working and keeps failing closed; a caller that can\n * report WHY (the shared `resolveAuthzContext`) uses this instead.\n *\n * The only refusal decided here is the org-less one, because it needs nothing\n * but the row and the posture. The ex-member refusal needs the caller's\n * membership set and is decided in `resolveAuthzContext`, where that set is\n * already resolved.\n */\nexport async function resolveApiKeyAdmission(\n ql: any,\n headers: any,\n nowMs: number = Date.now(),\n tenancyPosture?: TenancyPosture,\n): Promise<ApiKeyAdmission> {\n const apiKey = extractApiKey(headers);\n if (!apiKey) return { outcome: 'none' };\n if (!ql || typeof ql.find !== 'function') return { outcome: 'none' };\n\n // Match by the indexed at-rest hash only — never query by the raw key.\n let rows: any;\n try {\n rows = await ql.find('sys_api_key', {\n where: { key: hashApiKey(apiKey), revoked: false },\n limit: 1,\n context: { isSystem: true },\n });\n } catch {\n return { outcome: 'none' };\n }\n if (rows && (rows as any).value) rows = (rows as any).value;\n const row = Array.isArray(rows) ? rows[0] : undefined;\n if (!row || row.revoked === true) return { outcome: 'none' };\n\n const expiresAt = row.expires_at ?? row.expiresAt;\n if (isExpired(expiresAt, nowMs)) return { outcome: 'none' };\n\n const userId = row.user_id ?? row.userId;\n if (!userId || typeof userId !== 'string') return { outcome: 'none' };\n\n // [#8287 / PD #12] ONE spelling. The producer (`runtime` `/keys`) writes\n // `active_organization_id` and nothing else, so this reads that column and\n // nothing else. The `row.organization_id ?? row.organizationId` chain that\n // stood here was a consumer-side tolerance for a producer that did not exist\n // yet — it read a column no mint path ever wrote, which is why the ruling's\n // \"key auth establishes that organization\" clause was already implemented\n // and still measured as inert.\n const tenantId = typeof row.active_organization_id === 'string' && row.active_organization_id\n ? row.active_organization_id\n : undefined;\n\n // [#8287] Posture-conditional refusal for a key that carries no organization.\n //\n // ⛔ Never backfilled — inferring the org from the owner's CURRENT membership\n // would silently upgrade a credential minted under a different promise.\n // Refusal is scoped to the one posture where such a key is provably dead:\n //\n // - `single` — no wall exists; an org-less key works, and always did.\n // - `group` — the wall is `organization_id IN accessible_org_ids`, and\n // that set derives from the owner's `sys_member` rows\n // INDEPENDENTLY of the active organization, so an org-less\n // key already reads the union of its owner's orgs. Refusing\n // would break working deployments for no security gain.\n // - `isolated` — the wall is `organization_id = activeOrganizationId`; with\n // no active organization NOTHING can match, which is exactly\n // the `200 + total 0` this card was filed for. Refuse, so the\n // failure is loud at call time instead of silently empty.\n //\n // ⚠️ An ABSENT posture means \"the caller could not tell us which posture is in\n // force\", and the answer to that is to admit — i.e. today's behaviour. Not\n // fail-closed, deliberately, and this is the one place in this module where\n // that is the right call: refusing on an unknown posture would break every\n // org-less key on every `single` deployment whose transport has not been\n // wired, to enforce a wall that may not exist. Fail-closed belongs on\n // questions about THIS credential; this is a question about the deployment.\n if (!tenantId && tenancyPosture) {\n const posture = tenancyPosture;\n if (postureEnforcesWall(posture) && !postureUsesUnionScope(posture)) {\n return {\n outcome: 'refused',\n reason: 'organization_required',\n message:\n 'This API key carries no organization and cannot be used under the `isolated` tenancy '\n + 'posture, where every organization-scoped read is walled to an active organization. '\n + 'Mint a replacement key — new keys inherit the minter’s active organization.',\n };\n }\n }\n\n return {\n outcome: 'admitted',\n principal: { userId, tenantId, scopes: parseScopes(row.scopes) },\n };\n}\n\nfunction readHeader(headers: any, name: string): string | undefined {\n if (!headers) return undefined;\n const lower = name.toLowerCase();\n if (typeof headers.get === 'function') {\n const v = headers.get(name) ?? headers.get(lower);\n return v == null ? undefined : String(v);\n }\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lower) {\n const v = headers[key];\n return Array.isArray(v) ? v[0] : v == null ? undefined : String(v);\n }\n }\n return undefined;\n}\n\nfunction safeJsonParse<T>(s: string, fallback: T): T {\n try {\n return JSON.parse(s) as T;\n } catch {\n return fallback;\n }\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * [#13279] The LOUD failure an unreachable permission store raises.\n *\n * ## The defect this exists to end\n *\n * `resolveAuthzContext`'s per-read helper `tryFind` used to answer a THROWN\n * read the same way it answers an EMPTY one: `[]`. So a permission-store\n * outage resolved as a well-formed context for an AUTHENTICATED principal\n * holding ZERO capabilities, and the package-management door answered\n * `403 FORBIDDEN` — \"Reading packages requires the `studio.access` or\n * `setup.access` capability.\" That answer was measured BYTE-IDENTICAL\n * (`JSON.stringify` equal, against a positive control that separates two\n * answers which differ) to the answer a caller who genuinely holds nothing\n * gets. An administrator was told they lack a capability, during an outage of\n * the store that holds the capability.\n *\n * The resolver was asserting a fact it did not have. \"No rows came back\"\n * and \"the read failed\" are different facts, and only one of them licenses\n * the sentence \"this user holds nothing\".\n *\n * ## …and \"the read failed\" turned out to be TWO facts (ruled 2026-08-30, A)\n *\n * A read ALSO throws when the table was never PROVISIONED — a real engine,\n * wired and reachable, whose `sys_*` tables were never created. There \"this\n * user holds nothing\" is TRUE, not invented, so failing loud would refuse\n * service to a correctly-configured deployment. The first implementation of\n * this card did exactly that and four CI suites measured it.\n *\n * So `tryFind` raises this error only for a read failure that is NOT\n * positively identified as an unprovisioned table, asking the one relocated\n * `isMissingTableError` predicate (`@objectstack/types`) rather than a second\n * copy. The boundary, the ruling's verbatim text and the false-positive risk\n * signed off with it are written beside that call in `resolve-authz-context.ts`;\n * both directions are pinned in `authz-store-unavailable.test.ts` §4.\n *\n * ## Maintainer ruling, 2026-08-30, verbatim 「第一批其余同意」\n *\n * > `tryFind` 区分「无行」与「读失败」,读失败 fail-loud —— 权限库不可达时\n * > 不再解析为「已认证零能力」,而是响亮拒绝(与真实能力拒绝的 403 可区分),\n * > 让宕机不再伪装成一次逐字节相同的能力否决。\n *\n * The ruling fixes the DIRECTION and leaves the spelling to the implementation.\n *\n * ## Why a THROW, and not a field on the envelope\n *\n * The alternative was a discriminator field on `ResolvedAuthzContext` — the\n * shape `authRefusal` already has. That was rejected on a MEASUREMENT, not a\n * preference: `authRefusal` has existed since #8287 and, outside this module\n * and its own unit test, has **zero** consumers anywhere in the repo. A\n * diagnostic field on this envelope is demonstrably not read by any door. Every\n * transport reads `userId` and `systemPermissions`; a new sibling field would\n * have to be taught to eight separate call sites before it made a single door\n * louder, and would answer the old quiet 403 at every site that was missed.\n *\n * A field is quiet by default and must be deliberately made loud. A throw is\n * loud by default and must be deliberately silenced. On a security surface\n * whose whole defect is a silence, the default is the entire decision.\n *\n * It is also the idiom this platform already uses for unresolvable authority:\n * `packages/mcp`'s stdio entry throws and refuses to start rather than run with\n * an authority it could not resolve.\n *\n * ## Why `SERVICE_UNAVAILABLE` / 503, and why that is not a new wire shape\n *\n * `SERVICE_UNAVAILABLE` is an EXISTING member of the closed ADR-0112 wire\n * vocabulary (`StandardErrorCode`, `packages/spec/src/api/errors.zod.ts`), and\n * `HttpStatusErrorCodeMap` already maps it to 503 — \"service exists but is\n * temporarily down\". Nothing is added to the vocabulary and no envelope gains\n * or loses a key: a door that already renders `{ code, message }` renders this\n * one the same way. What changes is WHICH declared code an outage selects —\n * from the caller's `FORBIDDEN` to the operator's `SERVICE_UNAVAILABLE`.\n *\n * That is the ruling's own test, stated on the wire: 503 is not 403, so an\n * outage is no longer answerable as a capability denial.\n *\n * ## Recognise by BRAND, never by `instanceof`\n *\n * {@link isAuthzStoreUnavailableError} tests a own-property brand rather than\n * `instanceof`. This error crosses package boundaries (`@objectstack/core` →\n * rest / runtime / mcp / services / plugins) and a monorepo resolves the same\n * module through more than one path (`src` under vitest aliases, `dist` under\n * the published `exports`). Two copies of this class make `instanceof` answer\n * FALSE for a genuine instance — which, here, silently restores the exact\n * quiet 403 this module exists to remove. The brand survives duplication.\n */\n\n/** HTTP status an unreachable authorization store answers with. */\nexport const AUTHZ_STORE_UNAVAILABLE_STATUS = 503 as const;\n\n/**\n * Machine code — an EXISTING `StandardErrorCode` member (ADR-0112: SCREAMING).\n * Deliberately NOT a new code: the wire vocabulary is closed.\n */\nexport const AUTHZ_STORE_UNAVAILABLE_CODE = 'SERVICE_UNAVAILABLE' as const;\n\n/**\n * Human-facing message. States the OUTAGE, and says explicitly that no\n * capability judgement was reached — so neither the caller nor the operator\n * reads it as a permission verdict.\n */\nexport const AUTHZ_STORE_UNAVAILABLE_MESSAGE =\n 'The authorization store could not be read, so this request\\'s permissions were never determined. '\n + 'This is a server-side outage, not a permission denial.';\n\n/**\n * The own-property brand {@link isAuthzStoreUnavailableError} tests for.\n * A string-keyed own property (not a `Symbol.for` registry key), so a\n * duplicated copy of this module still brands identically — which is exactly\n * what `instanceof` cannot do (module doc above).\n *\n * ⚠️ The brand does NOT survive `structuredClone`, and no claim here depends\n * on it doing so — the same measured behaviour `service-not-registered.ts`\n * records for its own brand. Reproduce on Node 22.22.2:\n *\n * ```js\n * const e = new Error('x'); e.__brand = true; e.code = 'C';\n * const c = structuredClone(e);\n * // c.__brand === undefined c.code === undefined c.message === 'x'\n * // control: structuredClone({ __brand: true, code: 'C' }) keeps BOTH keys\n * ```\n *\n * `Error` has a dedicated serialization carrying `message`, `stack` and\n * `cause` only, so it DROPS every other own property — this brand, the\n * ADR-0112 `code`, `status` and `object` alike (and a subclass's own `name`\n * returns as `'Error'`). The plain-object control is the half that proves the\n * loss is specific to `Error`, not general to `structuredClone`.\n *\n * ⛔ So never branch on this brand across a worker or `postMessage` boundary:\n * it would answer `false` and fail OPEN. Every call site today is in-process —\n * `rethrowAuthzStoreUnavailable` on the rest rethrow paths and\n * `isAuthzStoreUnavailableError` inside service `catch` blocks.\n */\nconst AUTHZ_STORE_UNAVAILABLE_BRAND = '__objectstackAuthzStoreUnavailable' as const;\n\n/**\n * Raised when a permission-store read FAILED — never when it legitimately\n * returned no rows.\n *\n * Carries the `object` whose read failed so an operator sees WHICH table was\n * unreachable, and the originating error as `cause` so the driver's own\n * diagnostic is not lost behind this one.\n */\nexport class AuthzStoreUnavailableError extends Error {\n /** Brand — see the module doc on why this is not `instanceof`. */\n readonly [AUTHZ_STORE_UNAVAILABLE_BRAND] = true as const;\n /** ADR-0112 wire code. */\n readonly code = AUTHZ_STORE_UNAVAILABLE_CODE;\n /** HTTP status a transport should answer. */\n readonly status = AUTHZ_STORE_UNAVAILABLE_STATUS;\n /** The object/table whose read failed (e.g. `sys_user_permission_set`). */\n readonly object: string;\n /** The driver's originating failure, kept so its diagnostic is not lost. */\n readonly cause?: unknown;\n\n constructor(object: string, cause?: unknown) {\n // `cause` is assigned manually — the ES2022 `ErrorOptions` constructor\n // overload is unavailable at this package's compile target (ES2020). Same\n // idiom as `packages/spec`'s `ConnectorUpstreamUnavailableError`.\n super(`${AUTHZ_STORE_UNAVAILABLE_MESSAGE} (failed read: \\`${object}\\`)`);\n this.name = 'AuthzStoreUnavailableError';\n this.object = object;\n if (cause !== undefined) this.cause = cause;\n }\n}\n\n/**\n * True when `err` is the loud authorization-store failure above.\n *\n * The predicate every transport uses to tell \"the store was unreachable\" apart\n * from every other throw, so a fail-closed `catch` can re-raise THIS one\n * without loosening its handling of anything else.\n */\nexport function isAuthzStoreUnavailableError(err: unknown): err is AuthzStoreUnavailableError {\n return (\n typeof err === 'object'\n && err !== null\n && (err as Record<string, unknown>)[AUTHZ_STORE_UNAVAILABLE_BRAND] === true\n );\n}\n\n/**\n * The `.catch` argument every fail-closed seam between `resolveAuthzContext`\n * and a door should use in place of `() => undefined`.\n *\n * Re-raises {@link AuthzStoreUnavailableError} and swallows everything else to\n * `undefined`, so a seam keeps its existing fail-closed behaviour for every\n * fault EXCEPT the one the 2026-08-30 ruling requires to stay loud.\n *\n * ## Why the seams need this at all\n *\n * Making `tryFind` throw is necessary but NOT sufficient, and that was\n * MEASURED rather than assumed. Between the resolver and the package door sit\n * three independent nets — `computeExecCtx`'s `try { … } catch { return\n * undefined; }`, `resolvePackageRouteExecutionContext`'s `.catch(() =>\n * undefined)`, and `refusePackageRequest`'s own — and with the throw in place\n * but the nets untouched, the door answered **401**: the outage had simply\n * changed disguises, from \"you hold no capability\" (403) into \"you are not\n * authenticated\" (401), which is byte-identical to a genuine anonymous caller.\n * Distinguishable from a capability denial, yes — but still not LOUD, and now\n * wearing the costume of a different card's defect.\n *\n * A blanket `catch` cannot tell a fault from a refusal, so each net has to be\n * told once, in one shape. This is that shape.\n */\nexport function rethrowAuthzStoreUnavailable(err: unknown): undefined {\n if (isAuthzStoreUnavailableError(err)) throw err;\n return undefined;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * resolveAuthzContext — the SINGLE source of truth for resolving an inbound\n * request's identity + authorization context (positions, permissions, RLS scoping).\n *\n * Every HTTP entry point (REST server, runtime dispatcher, MCP, any future\n * transport) MUST resolve authorization through this function — never by\n * re-reading `sys_member` / `sys_user_position` / `sys_*_permission_set` itself.\n *\n * Why this exists: authorization resolution used to be DUPLICATED across the\n * REST server (`@objectstack/rest`) and the runtime dispatcher\n * (`@objectstack/runtime`). On a security path, duplicated logic drifts and the\n * drift is silent: the REST copy had quietly omitted `sys_user_position` (so custom\n * roles granted via the ADR-0057 D4 platform-RBAC path didn't apply over REST),\n * `sys_position_permission_set`, `mapMembershipRole` normalization, the\n * platform-admin derivation, and the `ai_seat` synthesis. The API-key half was\n * already shared here (`resolveApiKeyPrincipal`); this completes the extraction\n * by bringing session + role/permission aggregation home too. There is now ONE\n * implementation; both entry points are thin adapters that supply `ql` /\n * `getSession` their own way and delegate here.\n *\n * Fail-closed: every read is defensive about a MISSING service — an unwired\n * `ql` yields a partial context (even `{ positions: [], permissions: [] }`) and\n * enforcement stays the SecurityPlugin's job, never this resolver's.\n *\n * ⚠️ [#13279] A FAILED read is not a missing service, and since the 2026-08-30\n * ruling the two no longer share an answer. When a permission-store read is\n * issued and THROWS, this resolver raises {@link AuthzStoreUnavailableError}\n * instead of reporting an empty grant set: an outage must not be answerable as\n * a capability denial. See `authz-store-unavailable.ts` for the full reasoning\n * and for why the failure is a throw rather than a field on the envelope.\n *\n * ⚠️ …with ONE positively-identified exception, ruled the same day: a read that\n * failed because the table was never PROVISIONED is quiet, because there\n * \"zero capabilities\" is the true answer rather than an invention. The full\n * boundary, the ruling's verbatim text and the risk signed off with it are in\n * `tryFind`'s `catch` — read that before changing anything on this path.\n */\n\nimport { AuthzStoreUnavailableError } from './authz-store-unavailable.js';\n// [#13279, ruled 2026-08-30] `isMissingTableError` is the one \"was this READ\n// failure just an unprovisioned table?\" predicate. It was `@objectstack/metadata`'s\n// until this resolver needed it; metadata depends on core, so the ruling relocated\n// it to `@objectstack/types` — which core already depends on — rather than let a\n// second copy of a security-relevant classifier exist. See `tryFind`'s catch.\n//\n// [#13667] `resolveTenancyPosture` is the REQUESTED tenancy posture. It is read at\n// exactly one place below (§6b-config) to key the legacy-grant deprecation pointer\n// to the same walled postures the BOOT-side detector already keys on — the same\n// module, the same function, the same fail-stricter direction that detector reads.\nimport { isMissingTableError, resolveTenancyPosture } from '@objectstack/types';\nimport {\n mapMembershipRole,\n BUILTIN_IDENTITY_PLATFORM_ADMIN,\n ADMIN_FULL_ACCESS,\n ADMIN_FULL_ACCESS_CAPABILITIES,\n ORGANIZATION_ADMIN_GRANTS,\n} from '@objectstack/spec';\nimport type { AuthzPosture, TenancyPosture } from '@objectstack/spec/security';\nimport { postureEnforcesWall } from '@objectstack/spec/security';\n\nimport { resolveApiKeyAdmission } from './api-key.js';\nimport type { ApiKeyRefusalReason } from './api-key.js';\nimport { isGrantActive, nextGrantValidityBoundary } from './grant-validity.js';\nimport { openUserGrantsCache } from './resolve-user-grants-cache.js';\nimport {\n matchesConfiguredPlatformAdmin,\n reportLegacyPlatformAdminGrant,\n resolvePlatformAdminEmails,\n} from './platform-admin.js';\nimport { derivePosture } from './posture-ladder.js';\nimport { isRowActive } from './row-active.js';\n\n/** The transport-agnostic authorization envelope produced from a request. */\nexport interface ResolvedAuthzContext {\n userId?: string;\n tenantId?: string;\n email?: string;\n accessToken?: string;\n positions: string[];\n permissions: string[];\n systemPermissions: string[];\n tabPermissions?: Record<string, 'visible' | 'hidden' | 'default_on' | 'default_off'>;\n /** Fellow-org user IDs for RLS scoping of identity tables (`id IN (...)`). */\n org_user_ids: string[];\n /**\n * [ADR-0105 D2] Every organization this principal currently holds a VALID\n * membership in — the caller's org access set, and the read reach of the\n * `group` tenancy posture (Layer 0 becomes `organization_id IN (...)`).\n * Resolved here, once, so no surface re-derives it; empty for an anonymous or\n * membership-less principal, which fails the group wall closed.\n */\n accessible_org_ids: string[];\n /**\n * [ADR-0095 D2/D3] The monotonic posture rung this principal resolves to,\n * DERIVED once here from held capability grants (never a better-auth role):\n * `PLATFORM_ADMIN` (an unscoped `admin_full_access` grant, or — since #11663\n * L2 — a VERIFIED `sys_user.email` on the deployment's declared\n * administrator list) > `TENANT_ADMIN`\n * (`organization_admin`) > `MEMBER` (the authenticated floor). `EXTERNAL` is\n * defined/test-locked but never resolved yet (no external principal type —\n * see `posture-ladder.ts`). Present only for an authenticated principal;\n * anonymous requests carry no rung.\n */\n posture?: AuthzPosture;\n /**\n * [#8287] Set when an inbound API key was REFUSED — a real, intact\n * credential this deployment's tenancy posture cannot admit. The context is\n * otherwise EMPTY (no `userId`), so every transport already fails it closed\n * to 401 with no change; this field only lets a transport that wants to say\n * WHY do so, instead of answering the operator with a bare \"unauthenticated\"\n * for a key they can see is neither revoked nor expired.\n *\n * ⚠️ `reason` is NOT an `error.code`. The wire vocabulary is closed\n * (ADR-0112: `StandardErrorCode ∪ ERROR_CODE_LEDGER`, both in `packages/spec`)\n * and a refused credential's standard member is `UNAUTHENTICATED`. This is a\n * diagnostic discriminator for the message, deliberately lowercase so it can\n * never be mistaken for one.\n */\n authRefusal?: { reason: ApiKeyRefusalReason; message: string };\n}\n\nexport interface ResolveAuthzInput {\n /** Data engine (ObjectQL) exposing `find(object, { where, limit, context })`. */\n ql: any;\n /** Inbound request headers (Web `Headers` or a plain record). */\n headers: any;\n /**\n * Resolve a better-auth session from `headers`, returning `{ user?, session? }`\n * (or undefined). Optional — when omitted or throwing, only the API-key path\n * runs and anonymous requests resolve to an empty context.\n */\n getSession?: (headers: any) => Promise<any> | any;\n /** Clock injection for API-key expiry (tests). */\n nowMs?: number;\n /**\n * [#8287] The deployment's EFFECTIVE tenancy posture, as resolved from the\n * kernel's `tenancy` service (`effectiveTenancyPosture`) — never from\n * `OS_TENANCY_POSTURE`, which reports what was requested rather than what is\n * enforced (ADR-0093 D4/D5).\n *\n * Supplied by the transport because this resolver is deliberately\n * kernel-agnostic. OMITTING it disables the two posture-conditional API-key\n * refusals and leaves behaviour exactly as it was — so an unwired caller is\n * never made WORSE, only less strict.\n */\n tenancyPosture?: TenancyPosture;\n}\n\nfunction safeJsonParse<T>(s: string, fallback: T): T {\n try { return JSON.parse(s) as T; } catch { return fallback; }\n}\n\nasync function tryFind(\n ql: any,\n object: string,\n where: any,\n limit = 100,\n /**\n * Resolve inside ONE organization. Threaded into the execution context, so\n * the read routes through `SqlDriver.applyTenantScope` — the governed\n * chokepoint — rather than being re-implemented here as a bare equality.\n * Omitted keeps the pre-existing installation-wide read, which is what the\n * user-keyed reads above want (they are already narrowed by `user_id`) and\n * what a `single`-posture deployment wants everywhere.\n */\n organizationId?: string,\n): Promise<any[]> {\n if (!ql || typeof ql.find !== 'function') return [];\n try {\n const context = organizationId ? { isSystem: true, tenantId: organizationId } : { isSystem: true };\n let rows = await ql.find(object, { where, limit, context } as any);\n if (rows && (rows as any).value) rows = (rows as any).value;\n return Array.isArray(rows) ? rows : [];\n } catch (err) {\n // [#13279] THE loud failure. This `catch` used to `return []`, which made a\n // FAILED read and an EMPTY one the same answer — so an outage of the\n // permission store resolved as an authenticated principal holding zero\n // capabilities, and the door answered a `403` byte-identical to a genuine\n // capability denial. Ruled 2026-08-30: distinguish the two, and fail LOUD.\n //\n // ⚠️ The `!ql` guard ABOVE deliberately still returns `[]`: \"no engine is\n // wired\" is not a failed read, and an embedder that never configured a data\n // plane must keep resolving to an empty-but-valid envelope exactly as\n // before. Only a read that was ISSUED and THREW reaches here.\n //\n // ── The boundary, and the risk signed off with it ──────────────────────\n //\n // \"The read failed\" is NOT one fact, and the first implementation of this\n // ruling proved it by turning four CI suites red. A read also throws when\n // the table was never PROVISIONED — a real engine, wired and reachable,\n // whose `sys_*` tables were never created. That is a supported, deliberately\n // tested deployment shape; `packages/runtime`'s\n // `notifications.hono.integration.test.ts` names it `ABSENT_AUTHZ_TABLES`.\n // There, \"zero capabilities\" is the TRUE answer rather than a fabrication:\n // nothing is provisioned, so nothing was withheld. Only an UNREACHABLE\n // store — the ruling's own word 不可达 — leaves the answer UNKNOWN, and only\n // an unknown answer may not be reported as a capability denial.\n //\n // Maintainer ruling, 2026-08-30, 第 5 场总监席决裁批 #9, verbatim:\n //\n // > 采选项 A —— 把 `isMissingTableError` 从 `@objectstack/metadata` 迁至\n // > `@objectstack/types`(core 已依赖);metadata 保留 re-export 兼容;\n // > `tryFind` 仅对未被判定为「表未 provision」的读失败抛\n // > `AuthzStoreUnavailableError`(SERVICE_UNAVAILABLE / 503)。\n //\n // ⚠️⚠️ THE SIGNED-OFF RISK — beside the predicate, as the ruling requires.\n // Ruling, verbatim:\n //\n // > 签字在案:基于 driver 错误码的表缺失判定获准在安全路径上门控响亮性;\n // > 其假阳方向(误判「表缺失」⇒ 静默恢复安静 403)是本裁定接受的已知风险,\n // > 须在谓词旁注释写明并以测试钉住两个方向(真 outage ⇒ 响;真未 provision\n // > ⇒ 零能力为真答案,不响)。\n //\n // Plainly: this predicate now GATES loudness on a security path. Its\n // false-POSITIVE direction — calling a genuine outage \"table not\n // provisioned\" — silently restores the quiet 403 this card exists to\n // remove, with no thrown error, no log line, and no other failing test to\n // notice it. That direction is an ACCEPTED, RECORDED risk, not an\n // oversight. It is acceptable because the predicate keys on driver CODES,\n // SQLSTATEs and errnos first, excludes the known superstring traps up\n // front, and returns `false` for anything it does not POSITIVELY\n // recognise — an unrecognised outage stays loud by default.\n //\n // ⛔ Do NOT widen `isMissingTableError` to make a first boot quieter: every\n // widening moves outages into this quiet branch. ⛔ Do NOT add a second\n // predicate here to hedge the accepted risk — a hedge makes loudness\n // conditional on two classifiers agreeing, which is strictly more ways to\n // fall silent, and the maintainer signed off the single-classifier design.\n // Both directions are pinned by name in `authz-store-unavailable.test.ts`\n // ('THE OUTAGE DIRECTION' / 'THE UNPROVISIONED DIRECTION'); keep them.\n //\n // `object` is passed as `readObject` so the #13324 narrowing applies: a\n // phrase that names some OTHER relation is not evidence about the table\n // this read asked for, and stays loud.\n if (isMissingTableError(err, object)) return [];\n throw new AuthzStoreUnavailableError(object, err);\n }\n}\n\n/**\n * Resolve the authorization context for an inbound request. Anonymous requests\n * yield `{ positions: [], permissions: [], ... }`.\n *\n * ⚠️ [#13279] This function used to document itself as \"Always resolves — never\n * throws\", and that total guarantee WAS the defect: the only way to always\n * resolve across a permission-store outage is to report a capability set the\n * resolver never actually read. It now throws exactly one error —\n * {@link AuthzStoreUnavailableError}, when a permission-store read was issued\n * and failed FOR A REASON THAT IS NOT AN UNPROVISIONED TABLE. Every other path\n * still resolves, including every MISSING-service path and every\n * never-provisioned one. A transport that fails closed on unexpected throws should re-raise this\n * one ({@link isAuthzStoreUnavailableError}) rather than degrade it to a\n * refusal — degrading it restores the disguise the ruling removed.\n */\nexport async function resolveAuthzContext(input: ResolveAuthzInput): Promise<ResolvedAuthzContext> {\n const { ql, headers } = input;\n const ctx: ResolvedAuthzContext = {\n positions: [],\n permissions: [],\n systemPermissions: [],\n org_user_ids: [],\n accessible_org_ids: [],\n };\n\n let userId: string | undefined;\n let tenantId: string | undefined;\n\n // 1. API key (explicit opt-in via header) takes precedence over session.\n const admission = await resolveApiKeyAdmission(ql, headers, input.nowMs, input.tenancyPosture);\n // [#8287] A REFUSED key stops here and does NOT fall through to the session\n // path. Falling through would be more permissive than the behaviour this\n // replaced (an API key already outranks a session), and a refusal that\n // quietly becomes a session login is not a refusal.\n if (admission.outcome === 'refused') {\n ctx.authRefusal = { reason: admission.reason, message: admission.message };\n return ctx;\n }\n const keyPrincipal = admission.outcome === 'admitted' ? admission.principal : undefined;\n if (keyPrincipal) {\n userId = keyPrincipal.userId;\n tenantId = keyPrincipal.tenantId;\n for (const scope of keyPrincipal.scopes) {\n if (!ctx.permissions.includes(scope)) ctx.permissions.push(scope);\n }\n }\n\n // 2. Session / Bearer path — fall back when no API key resolved a user.\n if (!userId && typeof input.getSession === 'function') {\n try {\n const sessionData = await input.getSession(headers);\n userId = sessionData?.user?.id ?? sessionData?.session?.userId;\n tenantId = tenantId ?? sessionData?.session?.activeOrganizationId;\n ctx.accessToken = sessionData?.session?.token ?? ctx.accessToken;\n if (sessionData?.user?.email) ctx.email = String(sessionData.user.email);\n } catch {\n // no auth configured / bad session → anonymous\n }\n }\n\n if (!userId) return ctx;\n ctx.userId = userId;\n if (tenantId) ctx.tenantId = tenantId;\n if (!ql || typeof ql.find !== 'function') return ctx;\n\n // The principal is now known — delegate ALL position/permission/RLS\n // aggregation to the shared userId-driven resolver. Seed it with the API-key\n // scopes already collected (step 1) and any session-supplied email so the\n // resulting order + email fallback are byte-identical to the logic this\n // replaced. `resolveUserAuthzGrants` is the single place that reads\n // `sys_member` / `sys_user_position` / `sys_*_permission_set`, so a non-HTTP\n // surface that already knows the user id (a `runAs:'user'` automation run,\n // #3356) can build the SAME envelope without re-implementing any of it.\n const grants = await resolveUserAuthzGrants(ql, userId, {\n tenantId,\n nowMs: input.nowMs,\n seedPermissions: ctx.permissions,\n seedEmail: ctx.email,\n });\n // [#8287] An API key stamped with an organization is only as good as its\n // owner's CURRENT membership in that organization. Checked here, at VERIFY\n // time, rather than by revoking keys when a membership ends: membership can\n // end through better-auth's org endpoints, SCIM deprovisioning, a direct\n // `sys_member` delete, or an ADR-0091 validity window simply lapsing — a\n // revoke-on-removal hook has to catch EVERY one of those or it silently\n // misses, while a verify-time check cannot be bypassed by an unhooked path.\n //\n // It is also free: `resolveUserAuthzGrants` has just read `sys_member` for\n // this very user to build `accessible_org_ids`, so this is a set membership\n // test on data already in hand — zero additional queries.\n //\n // Scoped to the walled postures on purpose. Under `single` there is no\n // organization boundary to cross, and a deployment with no membership rows\n // at all would otherwise refuse every stamped key it has.\n //\n // The result is NO PRINCIPAL, not a degrade to a user-only principal.\n // Degrading would hand back exactly the `200 + total 0` silent-empty this\n // card exists to kill — an ex-member's automation would keep answering\n // success while reading nothing.\n if (keyPrincipal?.tenantId && input.tenancyPosture) {\n const posture = input.tenancyPosture;\n if (postureEnforcesWall(posture) && !grants.accessible_org_ids.includes(keyPrincipal.tenantId)) {\n return {\n positions: [],\n permissions: [],\n systemPermissions: [],\n org_user_ids: [],\n accessible_org_ids: [],\n authRefusal: {\n reason: 'organization_membership_ended',\n message:\n 'This API key authenticates into an organization its owner is no longer a member of. '\n + 'The key was not revoked — the membership that backed it ended.',\n },\n };\n }\n }\n\n ctx.positions = grants.positions;\n ctx.permissions = grants.permissions;\n ctx.systemPermissions = grants.systemPermissions;\n ctx.org_user_ids = grants.org_user_ids;\n ctx.accessible_org_ids = grants.accessible_org_ids;\n if (grants.tabPermissions) ctx.tabPermissions = grants.tabPermissions;\n if (grants.posture) ctx.posture = grants.posture;\n if (grants.email && !ctx.email) ctx.email = grants.email;\n\n return ctx;\n}\n\n/** The authorization grants a KNOWN user holds — a subset of {@link ResolvedAuthzContext}. */\nexport interface UserAuthzGrants {\n positions: string[];\n permissions: string[];\n systemPermissions: string[];\n /** Fellow-org user IDs for RLS scoping of identity tables (`id IN (...)`). */\n org_user_ids: string[];\n /** [ADR-0105 D2] Organizations this user holds a currently-valid membership in. */\n accessible_org_ids: string[];\n tabPermissions?: Record<string, 'visible' | 'hidden' | 'default_on' | 'default_off'>;\n posture?: AuthzPosture;\n /** The user's unique email (`sys_user`), for `current_user.email` owner RLS. */\n email?: string;\n}\n\nexport interface ResolveUserAuthzGrantsOptions {\n /** Active org/tenant id — scopes org-bound grants (a null-org row is global). */\n tenantId?: string;\n /** Clock injection for grant validity windows (tests). */\n nowMs?: number;\n /**\n * Permission names the CALLER already resolved (e.g. API-key scopes) to seed\n * `permissions` BEFORE permission-set names are appended, so a mixed\n * API-key+session principal keeps every scope and the ordering is preserved.\n * Copied, never mutated.\n */\n seedPermissions?: string[];\n /** A caller-supplied email (e.g. from the session) that wins over the `sys_user` read. */\n seedEmail?: string;\n /**\n * ⭐ Force a FRESH resolution even when the #11971 grants cache is enabled\n * — the ruled bypass list of #11633 (leg B, maintainer acceptance\n * 2026-08-25). Two call sites carry it, for two ruled reasons:\n *\n * - `plugin-security/src/explain-engine.ts` (`buildContextForUser`): the\n * permission explainer is the tool an administrator uses to VERIFY that\n * a revocation took effect. An explainer answering from cache would\n * explain a state that no longer exists, and would do it at exactly the\n * moment someone is checking.\n * - `service-automation/src/plugin.ts` (`runAs:'user'` runs): automation\n * runs are not request-shaped and can be long-lived; they must not pin\n * an envelope.\n *\n * Bypassing reads NOTHING from the cache and writes NOTHING into it — a\n * bypassed resolution must not repopulate an entry the next cached caller\n * would then trust.\n */\n bypassGrantsCache?: boolean;\n}\n\n/**\n * resolveUserAuthzGrants — the userId-driven core of {@link resolveAuthzContext}.\n *\n * Given a KNOWN user id, aggregate the authorization grants that user holds:\n * org-admin positions (`sys_member`), platform-RBAC positions\n * (`sys_user_position`), user- and position-bound permission sets\n * (`sys_user_permission_set` / `sys_position_permission_set` →\n * `sys_permission_set`), the derived `platform_admin` built-in + posture rung,\n * fellow-org peers for identity-table RLS, and the env-side `ai_seat`.\n *\n * Factored out of `resolveAuthzContext` so a surface that already knows WHO the\n * principal is — with no HTTP request to resolve it from — can build the SAME\n * envelope through the ONE resolver, instead of re-reading `sys_member` /\n * `sys_user_position` / `sys_*_permission_set` itself. The motivating consumer\n * is a `runAs:'user'` automation run resolving the triggering user's grants\n * (#3356): the record-change hook session carries only a `userId`, so the\n * automation engine calls this to run the flow's data ops exactly as that user\n * — not the bare member/everyone fallback the missing grants used to leave it.\n *\n * Fail-closed like its parent: a missing engine yields an empty-but-valid\n * envelope.\n *\n * ⚠️ [#13279] \"and it never throws\" was removed from this sentence deliberately.\n * A permission-store read that is issued and FAILS now raises\n * {@link AuthzStoreUnavailableError} rather than contributing an empty grant\n * set, so a `runAs:'user'` automation cannot silently run with the authority of\n * a user whose grants were never read.\n */\nexport async function resolveUserAuthzGrants(\n ql: any,\n userId: string,\n opts: ResolveUserAuthzGrantsOptions = {},\n): Promise<UserAuthzGrants> {\n // [#11971 / #11633 leg B] The cross-request grants cache. OFF by default\n // (`OS_AUTHZ_GRANTS_CACHE_TTL_MS=0`, Fork 4) — `openUserGrantsCache` returns\n // `undefined` then, with zero side effects, so the shipped default resolves\n // byte-identically to the uncached implementation below. When enabled, a hit\n // serves a clone of an envelope this engine resolved earlier, retired by any\n // watched-object write, any non-`write` epoch bump, the earliest upcoming\n // ADR-0091 validity boundary, or the TTL — whichever comes first. The\n // attempt snapshots its generation and clock HERE, before any read is\n // issued, so a write landing mid-resolution kills the entry on arrival.\n const grantsCache = openUserGrantsCache(ql, userId, opts);\n if (grantsCache?.hit) return grantsCache.hit;\n\n const { tenantId } = opts;\n const grants: UserAuthzGrants = {\n positions: [],\n permissions: Array.isArray(opts.seedPermissions) ? [...opts.seedPermissions] : [],\n systemPermissions: [],\n org_user_ids: [userId],\n accessible_org_ids: [],\n };\n if (opts.seedEmail) grants.email = opts.seedEmail;\n if (!ql || typeof ql.find !== 'function') return grants;\n\n // sys_user is needed for both the `current_user.email` fallback (API-key auth,\n // where the session didn't supply an email) and the ai_seat synthesis below.\n // Read the row at most once per resolution — the two reads were a duplicate\n // query on the API-key path.\n let userRowLoaded = false;\n let userRow: any;\n const getUserRow = async (): Promise<any> => {\n if (!userRowLoaded) {\n userRowLoaded = true;\n const rows = await tryFind(ql, 'sys_user', { id: userId }, 1);\n userRow = rows[0];\n }\n return userRow;\n };\n\n // ── [#10825] Leg 1: every read that depends only on (userId, tenantId) is\n // issued CONCURRENTLY — sys_user, both sys_member reads, sys_user_position\n // and sys_user_permission_set used to run as five sequential round trips\n // (legs 6–9 + 13 of an authenticated request; cloud#1539 measured that LEGS,\n // not queries, are the latency multiplier). Same queries, same filters, same\n // limits, same tenancy scoping — only the waiting overlaps, so the rows each\n // downstream block sees are byte-identical to the sequential version\n // (equivalence pinned in resolve-authz-context.batch-equivalence.test.ts).\n // `getUserRow` stays memoized; issuing it here is not a new read on any\n // path that reads it at all — the email fallback below and §7's ai_seat\n // synthesis consume the same memo. The CONDITION mirrors the sequential\n // implementation exactly: `sys_user` was read iff the caller seeded no\n // email OR did not seed `ai_seat` — a fully-seeded API-key principal never\n // touched the table, and the batch must not start (equivalence suite pins\n // the query multiset per fixture).\n //\n // [#11663 L2] The CONFIG anchor adds a third reason to read `sys_user`: §6b's\n // config branch compares the caller's STORED email against the declared\n // administrator list, so the row is needed whenever that list is non-empty.\n // Resolved here — before the batch — precisely so the read joins the SAME\n // wave rather than opening a sixth sequential leg after it. The read stays\n // CONDITIONAL ON CONFIG on purpose (pin P2: empty/unset config answers \"not a\n // platform admin\" before touching any row), which is why a deployment that\n // has not declared administrators issues byte-identically the same queries it\n // issued before this leg — see the batch-equivalence goldens, which are\n // unchanged for exactly that reason.\n const platformAdminConfig = resolvePlatformAdminEmails();\n const needsUserRow =\n !grants.email || !grants.permissions.includes('ai_seat') || platformAdminConfig.emails.length > 0;\n const [, members, userPositionRows, orgMembersLeg, upsRowsAll] = await Promise.all([\n needsUserRow ? getUserRow() : Promise.resolve(undefined),\n tryFind(ql, 'sys_member', { user_id: userId }, 200),\n tryFind(ql, 'sys_user_position', { user_id: userId }, 200),\n tenantId ? tryFind(ql, 'sys_member', { organization_id: tenantId }, 1000) : Promise.resolve([] as any[]),\n tryFind(ql, 'sys_user_permission_set', { user_id: userId }, 100),\n ]);\n\n // Resolve the caller's unique email for `current_user.email` RLS owner\n // policies when the caller didn't supply it (e.g. API-key auth).\n if (!grants.email) {\n const u = await getUserRow();\n if (u?.email) grants.email = String(u.email);\n }\n\n // Single clock for every validity-window check in this resolution\n // (ADR-0091 D2 — a grant row outside [valid_from, valid_until) does not\n // resolve, fail-closed, with no background job involved).\n const nowMs = opts.nowMs ?? Date.now();\n\n // 3. Memberships via sys_member (better-auth). ONE read serves two purposes,\n // so the two facts can never disagree about what the user belongs to:\n //\n // (a) [ADR-0095 D3] Org-administration roles for the ACTIVE organization,\n // normalized to the canonical built-in names (owner→org_owner,\n // admin→org_admin, …). This is the ONE PROVISIONING boundary where a\n // better-auth role is read: it is projected into `positions` here, and\n // separately drives the `organization_admin` capability grant\n // (auto-org-admin-grant.ts). No enforcement code path reads the raw\n // role — posture/adjudication run off the resulting capability grants,\n // so the #2836 dual-track cannot recur.\n //\n // (b) [ADR-0105 D2] `accessible_org_ids` — EVERY organization the user\n // currently belongs to, regardless of which one is active. This is the\n // `group` posture's read reach (Layer 0 becomes `organization_id IN\n // (...)`), so it must span the whole membership set, not the active\n // org. Rows outside their ADR-0091 validity window do not resolve; the\n // columns are absent on `sys_member` today, and `isGrantActive` treats\n // an absent bound as unbounded, so this is a no-op until they exist and\n // correct the moment they do.\n // (read in Leg 1 above)\n const accessibleOrgIds = new Set<string>();\n for (const m of members) {\n if (!isGrantActive(m, nowMs)) continue;\n const org = m.organization_id ?? m.organizationId;\n if (typeof org === 'string' && org) accessibleOrgIds.add(org);\n }\n grants.accessible_org_ids = Array.from(accessibleOrgIds);\n\n // Positions come from the ACTIVE org's membership only: a role held in one\n // organization must not grant its capabilities while the caller operates in\n // another. With no active org, every membership contributes — exactly the\n // pre-D2 behavior of the org-less read.\n //\n // [ADR-0091 D2] Rows outside their validity window are dropped BEFORE the\n // role derivation — the same discipline §6 gives `sys_user_permission_set`,\n // so a lapsed membership can no more yield `org_owner` than an expired\n // `admin_full_access` can yield `platform_admin`. Maintainer ruling\n // 2026-08-22 (live session, item 2): a lapsed membership is NO MEMBERSHIP,\n // not merely no org access — so this half now answers the same question as\n // `accessible_org_ids` above, off the same rows, and (a)'s \"correct the\n // moment they do\" promise covers BOTH derivations rather than one. Fail\n // closed (D2). `sys_member` declares neither bound today and `isGrantActive`\n // reads an absent bound as unbounded, so no shipped row changes answer.\n const activeMembers = members.filter(\n (m) =>\n isGrantActive(m, nowMs)\n && (!tenantId || (m.organization_id ?? m.organizationId) === tenantId),\n );\n for (const m of activeMembers) {\n if (m.role && typeof m.role === 'string') {\n for (const raw of m.role.split(',').map((s: string) => s.trim()).filter(Boolean)) {\n const r = mapMembershipRole(raw);\n if (!grants.positions.includes(r)) grants.positions.push(r);\n }\n }\n }\n\n // 4. [ADR-0057 D4] Platform-owned RBAC role assignments (sys_user_position) — the\n // source of truth for custom roles, decoupled from sys_member.role.\n // `organization_id = null` = global (cross-tenant); else match active org.\n // (read in Leg 1 above)\n for (const ur of userPositionRows) {\n const org = ur.organization_id ?? null;\n if (org && tenantId && org !== tenantId) continue;\n if (!isGrantActive(ur, nowMs)) continue;\n const r = ur.position;\n if (typeof r === 'string' && r && !grants.positions.includes(r)) grants.positions.push(r);\n }\n\n // 5. Fellow-org user IDs so RLS can scope identity tables to collaborators.\n if (tenantId) {\n const orgMembers = orgMembersLeg; // (read in Leg 1 above)\n const ids = new Set<string>(\n orgMembers\n .map((m) => m.user_id ?? m.userId)\n .filter((v): v is string => typeof v === 'string' && v.length > 0),\n );\n ids.add(userId);\n grants.org_user_ids = Array.from(ids);\n }\n\n // 6. Permission sets — user-scoped grants (null org = global, else active org).\n // Rows outside their validity window are dropped BEFORE any derivation, so\n // an expired admin_full_access grant cannot yield platform_admin either.\n // (read in Leg 1 above)\n const upsRows = upsRowsAll.filter((r) => isGrantActive(r, nowMs));\n const psIds = new Set<string>(\n upsRows\n .filter((r) => {\n const org = (r.organization_id ?? r.organizationId) ?? null;\n return !(org && tenantId && org !== tenantId);\n })\n .map((r) => r.permission_set_id ?? r.permissionSetId)\n .filter(Boolean),\n );\n // platform_admin (ADR-0068 D2) is DERIVED from an UNSCOPED admin_full_access\n // USER grant — the single source of truth (no trusted stored boolean).\n const unscopedUserPsIds = new Set<string>(\n upsRows\n .filter((r) => ((r.organization_id ?? r.organizationId) ?? null) === null)\n .map((r) => r.permission_set_id ?? r.permissionSetId)\n .filter(Boolean),\n );\n let hasPlatformAdminGrant = false;\n\n // 5b. [ADR-0090 D5] Audience anchor: every AUTHENTICATED member implicitly\n // holds the built-in `everyone` position, so sets bound to it resolve\n // below exactly like any other position-bound grant — ADDITIVE, with no\n // \"only when the user has nothing else\" cliff.\n if (!grants.positions.includes('everyone')) grants.positions.push('everyone');\n\n // 6a. Position-bound permission sets (sys_position_permission_set): a position\n // carries its permission sets.\n //\n // [ADR-0049] A DEACTIVATED position grants nothing — the `deactivate_position`\n // dialog's promise (\"users keep their assignment but the position stops\n // granting permissions\"), enforced at the ONE place it is enforceable.\n // Downstream in plugin-security the position→set linkage is already\n // collapsed into a flat `permissions` list, so a set held via a\n // deactivated position is indistinguishable there from one granted\n // directly and filtering there would over-revoke.\n //\n // The name is dropped from `positions` too, not merely from the junction\n // read: `resolvePermissionSetsForContext` requests `positions` as\n // permission-set NAMES (position names are commonly reused as set names),\n // so a name left standing would resolve the same grant one layer down.\n // Only a name whose row is explicitly deactivated is dropped — a name\n // with no `sys_position` row at all (`org_owner`, a membership-derived\n // role) has no flag to read and is untouched.\n if (grants.positions.length > 0) {\n // [#10103] Scoped to the CALLER's organization. `sys_position` spells\n // its name index `unique: 'organization'` and its rows are materialized\n // per organization, so several organizations hold a row named\n // `everyone` (and one named after every declared position). Swept by\n // name alone, this read returned EVERY organization's rows, and the\n // junction read below then collected another organization's bindings —\n // a cross-organization grant bleed, measured reachable from one tenant's\n // resolution to another tenant's `everyone` binding. It also made the\n // sweep O(organizations) on a table that is read on every request.\n //\n // Scoped by threading the organization into the context rather than by\n // adding an `organization_id` predicate here: the driver's\n // `applyTenantScope` is the one governed spelling of this wall, and a\n // bare equality written at this call site would be a second, ungoverned\n // implementation of it — the exact shape that produced the defect this\n // card repairs. Per-request cost stays O(the caller's own organization's\n // catalog).\n //\n // Limit raised with it: the cap has to admit this organization's rows\n // alongside any organization-less ones the driver's compatibility arm\n // still returns, or a caller silently loses positions. Those\n // organization-less rows stay REACHABLE on purpose — they are not\n // reaped, and grants point at them by row id, so dropping them here\n // would revoke standing access silently.\n const positionRows = await tryFind(ql, 'sys_position', { name: { $in: grants.positions } }, 200, tenantId);\n const deactivatedNames = new Set<string>(\n positionRows.filter((r) => !isRowActive(r)).map((r) => r.name).filter(Boolean),\n );\n if (deactivatedNames.size > 0) {\n grants.positions = grants.positions.filter((n) => !deactivatedNames.has(n));\n }\n const positionIds = positionRows.filter((r) => isRowActive(r)).map((r) => r.id).filter(Boolean);\n if (positionIds.length > 0) {\n const rpsRows = await tryFind(ql, 'sys_position_permission_set', { position_id: { $in: positionIds } }, 500);\n for (const r of rpsRows) {\n const id = r.permission_set_id ?? r.permissionSetId;\n if (id) psIds.add(id);\n }\n }\n }\n\n // 6b. Resolve permission-set details (names → grants.permissions; system_permissions;\n // tab_permissions merged by highest visibility).\n if (psIds.size > 0) {\n const psRowsAll = await tryFind(ql, 'sys_permission_set', { id: { $in: Array.from(psIds) } }, 500);\n // [ADR-0049] A DEACTIVATED permission set grants nothing — the\n // `deactivate_permission_set` dialog's promise (\"existing assignments stay\n // in place but stop granting access\"). Dropped BEFORE any derivation, the\n // same discipline the validity window gets at §6, so `hasPlatformAdminGrant`\n // cannot be derived from a set that no longer grants either: a deactivated\n // `admin_full_access` must not keep conferring PLATFORM_ADMIN.\n const psRows = psRowsAll.filter((r) => isRowActive(r));\n const tabRank: Record<string, number> = { hidden: 0, default_off: 1, default_on: 2, visible: 3 };\n const mergedTabs: Record<string, 'visible' | 'hidden' | 'default_on' | 'default_off'> = {};\n for (const ps of psRows) {\n if (ps.name && !grants.permissions.includes(ps.name)) grants.permissions.push(ps.name);\n if (ps.name === ADMIN_FULL_ACCESS && unscopedUserPsIds.has(ps.id)) hasPlatformAdminGrant = true;\n const sysPerms = typeof ps.system_permissions === 'string'\n ? safeJsonParse(ps.system_permissions, [])\n : (ps.system_permissions ?? ps.systemPermissions);\n if (Array.isArray(sysPerms)) {\n for (const p of sysPerms) {\n if (typeof p === 'string' && !grants.systemPermissions.includes(p)) grants.systemPermissions.push(p);\n }\n }\n const tabs = typeof ps.tab_permissions === 'string'\n ? safeJsonParse(ps.tab_permissions, {})\n : (ps.tab_permissions ?? ps.tabPermissions);\n if (tabs && typeof tabs === 'object') {\n for (const [app, val] of Object.entries(tabs as Record<string, unknown>)) {\n if (typeof val !== 'string' || !(val in tabRank)) continue;\n const cur = mergedTabs[app];\n if (!cur || tabRank[val] > tabRank[cur]) {\n mergedTabs[app] = val as 'visible' | 'hidden' | 'default_on' | 'default_off';\n }\n }\n }\n }\n if (Object.keys(mergedTabs).length > 0) grants.tabPermissions = mergedTabs;\n }\n\n // 6b-config. [#11663 L2] The DEPLOYMENT-CONFIG anchor for PLATFORM_ADMIN —\n // the second route to the same `hasPlatformAdminGrant`, inside the same\n // derivation site. Ruled bundle 1A/2B/3A/6A/7A (design comment\n // 5394453215; maintainer acceptance 2026-08-25). Read `platform-admin.ts`\n // before changing anything here; the reasoning for every branch is there.\n //\n // ⚠️ The comparison reads `userRow.email` — the caller's OWN STORED row —\n // and never `grants.email`. `grants.email` is seeded from `opts.seedEmail`\n // (a caller/session-supplied string that deliberately WINS over the stored\n // read for RLS purposes), so deriving superuser standing from it would add\n // a new escalation channel inside the change meant to close one. This is\n // the single most important mechanical pin of this leg and it is pinned by\n // test: \"a session payload carrying a configured address over a sys_user\n // row that does not resolves NON-admin\".\n //\n // ADDITIVE, never subtractive: nothing above is revoked here (design §5\n // step 3). A deployment that has declared no administrators resolves\n // exactly as it did — `platformAdminConfig.emails` is empty, the branch\n // short-circuits before it looks at any row, and the legacy grant read\n // above remains the only anchor.\n const configConfersPlatformAdmin =\n platformAdminConfig.emails.length > 0\n && matchesConfiguredPlatformAdmin(await getUserRow(), platformAdminConfig);\n if (configConfersPlatformAdmin) {\n // [Choice 6A] The capability CONTENT comes from the one declaration in\n // `@objectstack/spec` — the same object plugin-security spreads into its\n // `admin_full_access` permission-set entry — so the derived envelope and\n // the declared set cannot drift. The NAME is pushed for the same reason\n // §6b pushes `ps.name`: downstream `resolvePermissionSets` resolves object\n // grants from the name, and a config-derived admin must carry the identical\n // envelope a grant-derived one carries.\n hasPlatformAdminGrant = true;\n if (!grants.permissions.includes(ADMIN_FULL_ACCESS)) grants.permissions.push(ADMIN_FULL_ACCESS);\n for (const p of ADMIN_FULL_ACCESS_CAPABILITIES.systemPermissions ?? []) {\n if (!grants.systemPermissions.includes(p)) grants.systemPermissions.push(p);\n }\n } else if (hasPlatformAdminGrant) {\n // [#11663 P5] Standing rests on the LEGACY grant row alone. Honoured — the\n // migration is loud, not breaking — with a once-per-process pointer at the\n // config line that re-anchors it. The row is read only if it was already\n // loaded, so this notice never adds a query (and so never moves the pinned\n // query multiset for a deployment that declared nothing). The posture read\n // below keeps that property: it reads the environment, never the engine.\n //\n // [#13667] …and the pointer is POSTURE-KEYED, matching the BOOT-side\n // detector (`plugin-security/src/bootstrap-platform-admin.ts` §2, which\n // spells the same `postureEnforcesWall(resolveTenancyPosture())`). Under\n // `single` — the DEFAULT posture — `bootstrapPlatformAdmin` MINTS this very\n // row to promote the first human user, and Choice 4A rules that promotion,\n // and its row, correct and UNCHANGED. Both halves of the notice are\n // therefore false for such a rig: the row is not \"removed in a later\n // release\", and re-anchoring through the config line gets the operator\n // nothing, because the `single` promotion is pinned NEVER to consult that\n // variable (`bootstrap-platform-admin-walled-owner.test.ts`, \"never\n // consults the owner-email variable\"). That holds whichever way Choice 4B\n // (#11979) is eventually ruled: under 4A the row is that rig's permanent\n // anchor, and under 4B the advice only becomes true once 4B actually lands.\n // So the migration window's loudness is scoped to the rigs actually in it —\n // the walled ones, where the row really is the LEGACY anchor.\n //\n // ⛔ This gates the NOTICE and nothing else. Standing is derived by the\n // `if (configConfersPlatformAdmin) / else if (hasPlatformAdminGrant)` chain\n // this sits inside, and that chain is deliberately untouched — the\n // condition is nested WITHIN the arm precisely so no arm of it can change\n // shape. A `single` rig keeps exactly the PLATFORM_ADMIN it had; it simply\n // stops being told to migrate off an anchor that is not going anywhere.\n //\n // The REQUESTED posture is the input, not the effective one — the same\n // source and the same fail-stricter direction the boot side reads: a\n // deployment that ASKED for a wall stays inside the migration window even\n // while running degraded (`OS_ALLOW_DEGRADED_TENANCY=1`).\n if (postureEnforcesWall(resolveTenancyPosture())) {\n reportLegacyPlatformAdminGrant({ userId, email: userRow?.email });\n }\n }\n\n // 6c. Project the derived platform_admin built-in role (leads the list).\n if (hasPlatformAdminGrant && !grants.positions.includes(BUILTIN_IDENTITY_PLATFORM_ADMIN)) {\n grants.positions.unshift(BUILTIN_IDENTITY_PLATFORM_ADMIN);\n }\n\n // 6d. [ADR-0095 D2/D3] Resolve the posture rung ONCE, from held CAPABILITY\n // grants — never from a better-auth role. `PLATFORM_ADMIN` from the\n // unscoped `admin_full_access` grant (the same `viewAllRecords`/\n // `modifyAllRecords` evidence the superuser bypass trusts); `TENANT_ADMIN`\n // from the `organization_admin` grant (auto-provisioned from the better-\n // auth owner/admin role at §3 above — a provisioning source, not an\n // enforcement input, closing the #2836 dual-track class). Enforcement\n // behavior is unchanged: the per-object Layer 0 exemption + per-side\n // superuser bypass still gate access; posture is the carried, explainable\n // tier. `EXTERNAL` is never derived (no external principal type yet).\n grants.posture = derivePosture({\n isPlatformAdmin: hasPlatformAdminGrant,\n // [ADR-0105 D4] Either org-admin capability set resolves the rung — the\n // wall-less variant differs only by withholding the superuser bits.\n isTenantAdmin: ORGANIZATION_ADMIN_GRANTS.some((n: string) => grants.permissions.includes(n)),\n });\n\n // 7. [ADR-0024] Env-side AI seat: synthesize the `ai_seat` capability from the\n // boolean sys_user.ai_access (sqlite returns 1/0; memory returns boolean).\n if (!grants.permissions.includes('ai_seat')) {\n const aiAccess = ((await getUserRow()) as { ai_access?: unknown } | undefined)?.ai_access;\n if (aiAccess === true || aiAccess === 1 || aiAccess === '1') grants.permissions.push('ai_seat');\n }\n\n // [#11971] Store the fresh envelope (miss path only; the argument list is\n // not evaluated when the cache is off/bypassed/declined). The boundary scan\n // covers exactly the rows `isGrantActive` was applied to — own memberships,\n // position assignments, user-bound set grants — INCLUDING currently-inactive\n // rows, because a future `valid_from` is a flip the timer must catch too.\n // Peer rows (`orgMembersLeg`) feed `org_user_ids` with no validity check, so\n // they contribute no boundary.\n grantsCache?.commit(\n grants,\n nextGrantValidityBoundary([...members, ...userPositionRows, ...upsRowsAll], nowMs),\n );\n\n return grants;\n}\n\n/**\n * hasPlatformAdminStanding — the ID-SHAPED platform-admin question, asked in\n * exactly one place.\n *\n * ADR-0068 D2 defined PLATFORM standing as one thing: an UNSCOPED\n * (`organization_id = null`) `sys_user_permission_set` grant on the\n * `admin_full_access` set, held **now**. Since #11663 L2 there is a SECOND\n * anchor beside it — a `sys_user` row whose VERIFIED email is on the\n * deployment's declared administrator list (`OS_PLATFORM_OWNER_EMAIL`) — and\n * this predicate answers for both, for free, because it is a projection rather\n * than a copy (see below). A surface that only knows a user id —\n * a session-payload derivation, a platform-operator route gate, an\n * impersonation oracle — asks here, so it never has to re-read the grant tables\n * itself, which is the prohibition this module's header states.\n *\n * It is a PROJECTION of {@link resolveUserAuthzGrants}, never a second\n * derivation: the answer is the `PLATFORM_ADMIN` rung of the posture ladder,\n * and that rung is derived from the unscoped-grant evidence and nothing else.\n * Everything that governs those grants therefore applies here by construction\n * and cannot drift from it — the ADR-0091 validity window (§6), the ADR-0049\n * `active` flag on the catalogue row (§6b), the system-identity read, and the\n * resolution of `admin_full_access` BY ID rather than by scanning a page of the\n * catalogue. Each of those was missing from a hand-written copy of this\n * predicate; none of them can be missing from a projection.\n *\n * ⛔ Read the RUNG — never `positions.includes(BUILTIN_IDENTITY_PLATFORM_ADMIN)`.\n * The positions list is wider on purpose: an ADR-0057 D4 `sys_user_position`\n * row may spell that very name, and a platform-RBAC assignment is not the D2\n * capability grant. The two readings genuinely differ, so the narrow one is the\n * one that gets a name here.\n *\n * ⛔ The options are deliberately NOT {@link ResolveUserAuthzGrantsOptions}.\n * That type carries caller-supplied seeds (`seedEmail`, `seedPermissions`) for\n * transports that already resolved part of a principal; an authorization\n * predicate that accepted them would let a caller supply part of its own\n * verdict. Clock injection is the only thing a caller may pass, so this\n * function's answer is a function of `(ql, userId)` and the stored rows alone.\n *\n * ⚠️ This is the PER-USER predicate. The POPULATION question (\"which user is\n * the platform admin?\" — `ensure-default-organization.ts`) is a different kind\n * and is deliberately not expressible through it; do not widen this to serve\n * it.\n *\n * Fail-CLOSED: an empty id, a missing engine, or any unreadable lookup answers\n * `false`. This backs security gates, and an unverifiable actor never passes.\n */\nexport async function hasPlatformAdminStanding(\n ql: any,\n userId: string,\n opts: { nowMs?: number } = {},\n): Promise<boolean> {\n if (!ql || typeof userId !== 'string' || userId.length === 0) return false;\n try {\n const grants = await resolveUserAuthzGrants(ql, userId, { nowMs: opts.nowMs });\n return grants.posture === 'PLATFORM_ADMIN';\n } catch {\n return false;\n }\n}\n\n// ── Localization (ADR-0053 Phase 2) ─────────────────────────────────────────\n\nfunction isValidTimeZone(tz: string): boolean {\n try { new Intl.DateTimeFormat('en-US', { timeZone: tz }); return true; } catch { return false; }\n}\nfunction coerceTimeZone(value: unknown): string | undefined {\n const s = typeof value === 'string' ? value.trim() : value != null ? String(value).trim() : '';\n return s && isValidTimeZone(s) ? s : undefined;\n}\nfunction coerceLocale(value: unknown): string | undefined {\n const s = typeof value === 'string' ? value.trim() : value != null ? String(value).trim() : '';\n return s || undefined;\n}\nfunction coerceCurrency(value: unknown): string | undefined {\n const s = typeof value === 'string' ? value.trim().toUpperCase() : '';\n return /^[A-Z]{3}$/.test(s) ? s : undefined;\n}\n\nexport interface ResolveLocalizationInput {\n ql: any;\n /**\n * Settings service occupant. Two methods are consumed, in this order:\n *\n * - `getMany(namespace, keys, { tenantId, userId })` — PREFERRED since\n * #10826, and what this resolver calls for all three localization keys in\n * ONE grouped read.\n * - `get(namespace, key, { tenantId, userId })` — the per-key fallback,\n * taken only when the occupant does not expose `getMany` (three parallel\n * reads; see the feature-detect below).\n *\n * `getMany` is OPTIONAL for an occupant: the branch is feature-detected, so\n * a service that predates it still resolves — at three reads instead of one.\n * Typed `any` deliberately (the occupant's shape varies by host); the\n * declaration above is the contract this resolver actually relies on, and it\n * is prose precisely because nothing type-checks it — `getService` is a cast\n * and `rest-server.ts` widens the provider's return to a bare promise.\n */\n settings?: any;\n tenantId?: string;\n userId?: string;\n}\n\ntype LocalizationResult = { timezone: string; locale: string; currency?: string };\n\n/**\n * Process-local TTL cache for the FAILED-READ outcome of\n * {@link resolveLocalizationContext} only (#10221; narrowed after a patch\n * round — see below).\n *\n * `resolveAuthzContext`'s #2409 de-dup already collapsed the THREE per-key\n * reads a request used to issue into one batched `sys_setting` query; what it\n * did not address is the SAME query repeating on EVERY request. On a fresh\n * environment (`sys_setting` not yet migrated / never written) that one query\n * fails every time, and `packages/drivers/driver-sql`'s\n * `backendStatementFault` — deliberately generic; see its doc — logs a\n * `[sql-driver] DATABASE_ERROR` line for EVERY failed read, so the identical\n * \"no such table: sys_setting\" warning repeats once per request and buries\n * real errors in between.\n *\n * `tryFind`-style catches already guaranteed the FUNCTIONAL fallback (catch →\n * `[]` → built-in `UTC` / `en-US` defaults) — this cache only stops the\n * FAILING query (and therefore the log line) from re-running every request.\n *\n * ## Why only the failure, not every result (patch round, CI red)\n *\n * The first version of this cache memoized every outcome — including a\n * SUCCESSFUL read — for 30s, mirroring `packages/plugins/plugin-audit/src/audit-writers.ts`\n * (`resolveWriteLocale`)'s existing TTL cache of this same read. That is safe\n * for audit-writer's use: audit trail enrichment is explicitly best-effort,\n * so a stale locale in a log line for up to 30s costs nothing observable.\n * It is NOT safe for `@objectstack/rest`'s use of this same function: analytics\n * date-bucketing reads the org timezone on every query, and\n * `packages/qa/dogfood/test/analytics-timezone.dogfood.test.ts` (the golden\n * regression for #1982/#2018) writes a NEW org timezone via the real settings\n * route and asserts the VERY NEXT analytics read buckets under it — a 30s-old\n * cached value broke that test (CI: `expected undefined to be 3`, the bucket\n * stayed on the previous timezone). Analytics bucketing is declared, tested\n * behavior, not best-effort enrichment — it cannot tolerate the same\n * staleness plugin-audit's cache is allowed to.\n *\n * So this cache is narrowed to memoize ONLY the case the underlying read\n * itself THREW (a backend fault — e.g. \"no such table\" — not a legitimate\n * empty/no-rows-yet result, which is a normal, cheap, un-failing read and\n * stays uncached so a fresh write is visible on the next request same as\n * before). That fully addresses #10221 — the log spam only exists on an env\n * where the read is actively failing — while never caching a value a caller\n * could observe going stale.\n *\n * [#11877] \"the underlying read itself THREW\" means the DIRECT `sys_setting`\n * read, and only it. The write condition used to be a single `failed` flag\n * that five SETTINGS-SERVICE legs also set (a thrown `getMany`, each of the\n * three older per-key `get`s, and the whole-block \"service unavailable\"\n * handler). Those legs are reachable inside the settings engine's BIND\n * WINDOW — `SettingsService.getMany` refuses all-or-nothing for a namespace\n * whose manifest is not yet registered — so a caller that deliberately\n * re-reads AFTER the bind (the #11580 repair re-resolves at\n * `kernel:bootstrapped`) was answered from the in-window memo for up to 30s,\n * silently keeping the very value the re-read existed to replace. And a\n * settings refusal standing alongside a SUCCESSFUL direct read memoized that\n * successful value — exactly the staleness the paragraph above forbids.\n *\n * Narrowing to the backend leg costs nothing #10221 bought: those refusals\n * throw out of an in-memory registry check BEFORE any query and before any\n * log line, so memoizing them suppressed neither — while #10221's own\n * environment (table not migrated yet) still memoizes, because the direct\n * read throws there whether or not a settings refusal stands in front of it.\n *\n * Keyed on the `ql` instance (one entry-set per environment engine, so two\n * environments/tenants sharing a process never see each other's cached\n * outcome) and then `tenantId|userId` beneath it, matching the audit writer's\n * key shape. A `ql` that isn't cacheable (missing/non-object) skips the cache\n * entirely — there is no query to dedupe in that case.\n */\nconst LOCALIZATION_FAILURE_CACHE_TTL_MS = 30_000;\n\n/**\n * ── Leg C of #11633: the SUCCESS side of this same cache (#11966) ───────────\n *\n * The docblock above records why a successful read was NOT cached: a 30s TTL\n * is far longer than the gap between a settings write and the next request, so\n * `analytics-timezone.dogfood.test.ts` went red. That verdict was on **TTL-only\n * caching** and it still stands, unamended. What changed is that this process\n * now has invalidation seams it did not have then, so a successful answer can\n * be bounded by a WRITE rather than by a clock:\n *\n * 1. **Primary — the settings change seam.** `SettingsService.subscribe(ns,\n * handler)` dispatches SYNCHRONOUSLY and in-process from the write path,\n * and it does so AFTER the row is persisted (`settings-service.ts` calls\n * `emitChange` below its `await this.upsertRow(...)`). One subscription per\n * settings occupant advances a generation counter; an entry resolved at an\n * older generation is dead on arrival.\n * ⚠️ There is **no module called a \"settings change bus\"** — #11633's term\n * for this seam maps to nothing in the tree. `subscribe()` is the seam.\n * 2. **Backstop — the engine write epoch** (#11968's substrate, declared in\n * `objectql/src/write-epoch.ts`). Needed because this resolver's own\n * fallback reads `sys_setting` DIRECTLY, so a seeder — or any other direct\n * engine write — emits no settings event at all. Read STRUCTURALLY, never\n * by import: `@objectstack/objectql` depends on this package, so the edge\n * cannot be reversed, and the substrate declared `WriteEpochLike`\n * separately for exactly this consumer.\n * 3. **TTL** — the residual bound, covering only what neither seam can see: a\n * peer node's write, on a deployment with no `authz.invalidated` bridge\n * attached. With a bridge, a peer's hint bumps the LOCAL epoch\n * (`authz-invalidation-bridge.ts` calls `epoch.bump('remote')`), so the\n * backstop narrows cross-node convergence for free.\n *\n * ⭐ **A success is cached ONLY when the engine exposes the write epoch.** That\n * is the load-bearing rule of this change. It is a rule about the CACHE, not\n * about the caller: a `ql` with no seam is a `ql` whose writes this cache\n * cannot see, and leg C's ruled requirement is that invalidation be\n * synchronous and in-process — \"a TTL alone does not satisfy it\". So instead of\n * degrading to the TTL-only shape that was already reverted once here, the\n * cache declines. Every existing test double takes that path and keeps its\n * exact query multiset; only a real engine caches.\n *\n * ⛔ **Invalidation retires SUCCESS entries only.** Dropping failure entries on\n * a write would hand #10221 straight back: on the environment that memo exists\n * for, `sys_setting` is missing, so a write to ANY object would retire the memo\n * and the failing query — with the driver's log line behind it — would resume\n * repeating once per request. No write can create a missing table, so there is\n * nothing there for a write to correct; the failure memo stays purely\n * TTL-bound and behaviourally identical to what #10221/#11877 shipped.\n */\nconst LOCALIZATION_CACHE_TTL_ENV = 'OS_LOCALIZATION_CACHE_TTL_MS';\nconst LOCALIZATION_SUCCESS_CACHE_DEFAULT_TTL_MS = 30_000;\n\n/**\n * Staleness bound for the success cache, in ms. `0` disables it — a real path\n * that restores the pre-#11966 query multiset exactly, not a degenerate TTL.\n *\n * Deployment config, never a settings row (#11633 §5): `sys_setting` is the\n * table this cache caches, so a knob living there would be served BY the cache\n * it governs.\n *\n * ⚠️ A malformed value resolves to `0` (off), which is the OPPOSITE arm from\n * `readAuthzGrantsCacheTtlMs`'s, and deliberately so. There, `0` is also the\n * default, so malformed-means-off changes nothing. Here the default is ON, so\n * the two candidate readings are \"off\" and \"30s\" — and folding `3OOO` (letter\n * O) into the default would hand the operator a LONGER staleness window than\n * the one they were trying to set. Off is the only arm whose failure mode is a\n * missed optimisation rather than an unasked-for window.\n */\nfunction localizationSuccessCacheTtlMs(\n env: Record<string, string | undefined> = typeof process !== 'undefined' ? process.env : {},\n): number {\n const raw = env[LOCALIZATION_CACHE_TTL_ENV];\n if (raw === undefined || raw.trim() === '') return LOCALIZATION_SUCCESS_CACHE_DEFAULT_TTL_MS;\n const parsed = Number(raw.trim());\n if (!Number.isFinite(parsed) || parsed < 0) return 0;\n return Math.floor(parsed);\n}\n\n/**\n * The engine's current write epoch, or `undefined` when this `ql` carries no\n * such seam. Mirrors `isWriteEpochLike` from `@objectstack/objectql` rather\n * than importing it — see point 2 of the docblock above for why the import is\n * not available in this direction.\n *\n * ⚠️ The whole surface is checked, not just `current`. A bare\n * `{ current: number }` on some unrelated double would otherwise read as a live\n * invalidation seam and license caching against a counter that nothing ever\n * bumps — precisely the state this guard exists to keep unreachable.\n */\nfunction readWriteEpoch(ql: unknown): number | undefined {\n if (!ql || typeof ql !== 'object') return undefined;\n const epoch = (ql as { writeEpoch?: unknown }).writeEpoch;\n if (!epoch || typeof epoch !== 'object') return undefined;\n const seam = epoch as { current?: unknown; bump?: unknown; subscribe?: unknown };\n if (\n typeof seam.current !== 'number' ||\n typeof seam.bump !== 'function' ||\n typeof seam.subscribe !== 'function'\n ) {\n return undefined;\n }\n return seam.current;\n}\n\n/** Per-settings-occupant invalidation state for the localization bucket. */\ninterface LocalizationSettingsState {\n /** Advanced by the occupant's change seam. Compared, never interpreted. */\n gen: number;\n}\n\nconst localizationSettingsStates = new WeakMap<object, LocalizationSettingsState>();\n\n/**\n * The state every call that passes NO settings occupant shares. A distinct\n * object rather than `undefined` so entry validity stays one identity\n * comparison: an answer resolved through a settings service must not be served\n * to a call made without one, or through a different one.\n */\nconst localizationNoSettingsState: LocalizationSettingsState = { gen: 0 };\n\n/**\n * Fetch — and, on first sight of an occupant, subscribe to — the invalidation\n * state for one settings service.\n *\n * #11633 §2.3: the change event is `{ namespace, key, scope, action, at }` with\n * **no tenant discriminator**, so a handler cannot know whose entry to drop and\n * the whole `localization` bucket has to go. A generation counter IS that drop,\n * in O(1), without walking a Map from inside a change handler.\n *\n * The subscription is deliberately never disposed: it holds one integer per\n * occupant, the occupant outlives this module's interest in it, and a disposer\n * would need a shutdown hook a pure resolver does not have. `subscribe` is\n * feature-detected — an occupant without the seam still gets the epoch backstop\n * and the TTL, it just loses the precise trigger.\n */\nfunction localizationSettingsState(settings: unknown): LocalizationSettingsState {\n if (!settings || typeof settings !== 'object') return localizationNoSettingsState;\n const existing = localizationSettingsStates.get(settings as object);\n if (existing) return existing;\n const state: LocalizationSettingsState = { gen: 0 };\n localizationSettingsStates.set(settings as object, state);\n const subscribe = (settings as { subscribe?: unknown }).subscribe;\n if (typeof subscribe === 'function') {\n try {\n (subscribe as (ns: string, handler: () => void) => unknown).call(\n settings,\n 'localization',\n () => {\n state.gen += 1;\n },\n );\n } catch {\n // An occupant whose seam refuses leaves the epoch backstop and the TTL.\n }\n }\n return state;\n}\n\n/**\n * One entry per `ql` → `tenantId|userId`. ONE table, two lifecycles — the two\n * docblocks above are why they must not collapse into a single rule:\n *\n * `failure` — #10221/#11877. TTL only; no invalidation ever retires it.\n * `success` — #11966 / #11633 leg C. Retired by the settings seam, by the\n * engine write epoch, or by the TTL — whichever comes first.\n */\ninterface LocalizationCacheEntry {\n value: LocalizationResult;\n expiresAt: number;\n kind: 'failure' | 'success';\n /** `success` only: the engine write epoch this value was read at. */\n epoch?: number;\n /** `success` only: the settings occupant this value was read through. */\n settings?: LocalizationSettingsState;\n /** `success` only: that occupant's generation at read time. */\n settingsGen?: number;\n}\n\nconst localizationCache = new WeakMap<object, Map<string, LocalizationCacheEntry>>();\n\n/**\n * The invalidation half of \"is this entry still the answer?\" — the caller\n * checks `expiresAt` separately, because the TTL applies to both kinds and\n * these rules apply to one.\n */\nfunction localizationEntryIsLive(\n entry: LocalizationCacheEntry,\n epoch: number | undefined,\n settings: LocalizationSettingsState,\n): boolean {\n if (entry.kind === 'failure') return true;\n return entry.epoch === epoch && entry.settings === settings && entry.settingsGen === settings.gen;\n}\n\nfunction putLocalizationEntry(ql: object, key: string, entry: LocalizationCacheEntry): void {\n const bucket = localizationCache.get(ql) ?? new Map<string, LocalizationCacheEntry>();\n bucket.set(key, entry);\n localizationCache.set(ql, bucket);\n}\n\n/**\n * Resolve workspace localization defaults (reference `timezone` / `locale` /\n * `currency`). Canonical path is the `localization` SettingsManifest (cascade:\n * platform default → global → tenant); falls back to direct tenant-scoped\n * `sys_setting` rows, then the built-ins `UTC` / `en-US`. Never throws.\n *\n * The DIRECT `sys_setting` read failing outright (backend fault — table\n * missing, connection refused, etc.) is memoized for\n * {@link LOCALIZATION_FAILURE_CACHE_TTL_MS} per `(ql, tenantId, userId)` so\n * the failing query — and the driver's log line for it — does not repeat\n * every request (#10221). A settings-service refusal is not a backend fault\n * and never populates that memo (#11877).\n *\n * A SUCCESSFUL read is cached too, since #11966 (leg C of #11633) — but only\n * when the engine carries the write-epoch seam, and only until the first of:\n * a `localization` settings change, an engine write, or\n * `OS_LOCALIZATION_CACHE_TTL_MS`. Both invalidations are synchronous and\n * in-process, which is what lets the success cache exist at all: the\n * dogfood analytics-bucketing test writes a new org timezone and reads it back\n * on the very next request, and it is kept unweakened as this leg's acceptance\n * criterion. See the leg-C docblock above for the full contract, including why\n * a `ql` with no seam declines to cache rather than falling back to the TTL.\n */\nexport async function resolveLocalizationContext(input: ResolveLocalizationInput): Promise<LocalizationResult> {\n const { ql, settings, tenantId, userId } = input;\n const cacheKey = `${tenantId ?? ''}|${userId ?? ''}`;\n const cacheable = Boolean(ql) && typeof ql === 'object';\n\n // ⭐ Both invalidation readings are taken BEFORE the resolve, and it is these\n // pre-read values that get stored with the answer. A write landing WHILE this\n // read is in flight therefore moves the epoch (or the generation) past what\n // the entry records, so the entry is already dead when it is written — the\n // safe direction. Reading them afterwards would stamp a pre-write value with\n // a post-write epoch and make that staleness permanent: the\n // clear-then-repopulate-from-a-stale-read failure #11633 §7 pin 2 names.\n const epoch = cacheable ? readWriteEpoch(ql) : undefined;\n const settingsState = localizationSettingsState(settings);\n\n if (cacheable) {\n const hit = localizationCache.get(ql)?.get(cacheKey);\n if (hit && hit.expiresAt > Date.now() && localizationEntryIsLive(hit, epoch, settingsState)) {\n return hit.value;\n }\n }\n\n const { value, backendFailed } = await resolveLocalizationContextUncached(input);\n if (!cacheable) return value;\n\n if (backendFailed) {\n putLocalizationEntry(ql, cacheKey, {\n value,\n expiresAt: Date.now() + LOCALIZATION_FAILURE_CACHE_TTL_MS,\n kind: 'failure',\n });\n return value;\n }\n\n const ttlMs = localizationSuccessCacheTtlMs();\n if (epoch !== undefined && ttlMs > 0) {\n putLocalizationEntry(ql, cacheKey, {\n value,\n expiresAt: Date.now() + ttlMs,\n kind: 'success',\n epoch,\n settings: settingsState,\n settingsGen: settingsState.gen,\n });\n } else {\n // Nothing to store — but the entry this read just superseded must not be\n // left behind either. (Reaching here means no LIVE entry was found above,\n // so this only ever drops a dead one.)\n localizationCache.get(ql)?.delete(cacheKey);\n }\n return value;\n}\n\nasync function resolveLocalizationContextUncached(\n input: ResolveLocalizationInput,\n): Promise<{ value: LocalizationResult; backendFailed: boolean }> {\n const { ql, settings, tenantId, userId } = input;\n // ONLY the direct `sys_setting` read below sets this. The settings-service\n // legs deliberately do not — see the cache doc above (#11877).\n let backendFailed = false;\n try {\n if (settings && typeof settings.get === 'function') {\n const sctx = { tenantId, userId } as any;\n // [#10826] ONE grouped namespace read instead of three: `getMany`\n // resolves all three keys over at most two `loadRows` calls (queries\n // 16–18 of 24 on the measured rig collapse to one). Same per-key\n // answers by the service's own equivalence contract. Feature-detected:\n // an older service without `getMany` keeps the three parallel `get`s\n // (still 1 leg — this is a query-count fix, per the card's calibration).\n // A thrown `getMany` lands in the same place a thrown `get` did — the\n // direct `$in` fallback below, which reads the exact same three keys.\n // Neither populates the failure memo: a settings refusal is not the\n // backend fault that memo is for (#11877; see the cache doc above).\n //\n // [#11222 item 4] ONE non-equivalence, inherent to batching and recorded\n // here because it is this CALLER's degradation, not the service's:\n // `getMany` validates every requested key up front and throws for the\n // WHOLE call, so a host that registered a PARTIAL `localization`\n // manifest (missing any of the three keys) loses all three at once,\n // where the per-key path would still have resolved the declared ones.\n // The `$in` fallback below then answers from tenant-scoped rows only —\n // it has no `global` scope layer and no `OS_LOCALIZATION_*` env\n // override. Degradation, never a wrong answer, and unreachable against\n // the in-repo `localizationSettingsManifest`, which declares all three.\n // The all-or-nothing rule itself is `SettingsService.getMany`'s own\n // contract and is documented there, not here.\n let tzRes: any; let localeRes: any; let currencyRes: any;\n if (typeof settings.getMany === 'function') {\n try {\n const many = await settings.getMany('localization', ['timezone', 'locale', 'currency'], sctx);\n tzRes = many.timezone;\n localeRes = many.locale;\n currencyRes = many.currency;\n } catch {\n // Settings refusal → fall through to the direct `$in` read below.\n // Not a backend fault, so it does not populate the memo (#11877).\n }\n } else {\n // Same rule as the batched arm above: a refused key falls through to\n // the direct `$in` read and does not populate the memo (#11877).\n [tzRes, localeRes, currencyRes] = await Promise.all([\n settings.get('localization', 'timezone', sctx).catch(() => undefined),\n settings.get('localization', 'locale', sctx).catch(() => undefined),\n settings.get('localization', 'currency', sctx).catch(() => undefined),\n ]);\n }\n const tz = coerceTimeZone(tzRes?.value);\n const locale = coerceLocale(localeRes?.value);\n const currency = coerceCurrency(currencyRes?.value);\n if (tz || locale || currency) {\n return { value: { timezone: tz ?? 'UTC', locale: locale ?? 'en-US', currency }, backendFailed: false };\n }\n }\n } catch {\n // Settings service unavailable → direct read. Still not a backend fault,\n // so it does not populate the memo either (#11877).\n }\n // One read for all three keys instead of a query per key (`$in` on `key`).\n // Inlined (rather than the shared `tryFind`) so a genuine backend fault —\n // as opposed to a legitimate empty result — is visible to the caller above,\n // which is the signal the failure-only cache keys off. `ql` is already\n // typed `any` (its shape varies by caller — REST's engine, a test double,\n // …), so the options literal below needs no `as any` of its own (#4918\n // query-options-erasure guard: that cast is a distinct, counted erasure\n // site, not implied by an already-`any` receiver).\n let rows: any[] = [];\n if (ql && typeof ql.find === 'function') {\n try {\n let result = await ql.find('sys_setting', {\n where: { namespace: 'localization', key: { $in: ['timezone', 'locale', 'currency'] }, scope: 'tenant' },\n limit: 10,\n context: { isSystem: true },\n });\n if (result && (result as any).value) result = (result as any).value;\n rows = Array.isArray(result) ? result : [];\n } catch {\n // THE backend fault the failure memo exists for (#10221).\n backendFailed = true;\n }\n }\n const valueOf = (k: string) => rows.find((r) => r.key === k)?.value;\n return {\n value: {\n timezone: coerceTimeZone(valueOf('timezone')) ?? 'UTC',\n locale: coerceLocale(valueOf('locale')) ?? 'en-US',\n currency: coerceCurrency(valueOf('currency')),\n },\n backendFailed,\n };\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Grant validity windows (ADR-0091 D1/D2).\n *\n * `sys_user_position` and `sys_user_permission_set` rows carry optional\n * `valid_from` / `valid_until` columns. A row outside its window MUST NOT\n * resolve — anywhere, symmetrically: `resolveAuthzContext`, the explain\n * engine's `buildContextForUser`, plugin-sharing's `expandPositionUsers`,\n * and (transitively) the delegated-admin gate's held-scope resolution.\n *\n * Correctness lives HERE, at resolution time — never in a cleanup job\n * (ADR-0049: no unenforced security properties). The window is half-open\n * `[from, until)` in UTC: a grant is inactive before `valid_from` and\n * inactive AT and AFTER `valid_until`. Null/absent bounds mean unbounded,\n * so pre-ADR-0091 rows behave exactly as before.\n *\n * Fail-closed: a bound that is PRESENT but unparseable disables the grant\n * (unlike API-key `isExpired`, which tolerates garbage — an API key is a\n * single credential, a grant row is standing authority).\n */\n\n/**\n * Coerce a stored timestamp to epoch milliseconds.\n * Returns `undefined` for absent (null/undefined/'') values — \"no bound\" —\n * and `NaN` for present-but-unparseable values, which callers treat as\n * out-of-window (fail closed).\n */\nfunction toEpochMs(value: unknown): number | undefined {\n if (value == null || value === '') return undefined;\n if (typeof value === 'number') {\n // Heuristic: seconds vs milliseconds epoch (same rule as api-key.ts).\n return value < 1e12 ? value * 1000 : value;\n }\n if (value instanceof Date) return value.getTime();\n if (typeof value === 'string') return Date.parse(value);\n return Number.NaN;\n}\n\n/** The validity-window shape shared by both user-grant tables (ADR-0091 D1). */\nexport interface GrantValidityWindow {\n valid_from?: unknown;\n valid_until?: unknown;\n}\n\n/**\n * True when a grant row is inside its validity window at `nowMs`.\n * The single predicate every resolver uses (ADR-0091 D2):\n * `(valid_from is null or valid_from <= now) and (valid_until is null or valid_until > now)`.\n */\nexport function isGrantActive(row: GrantValidityWindow | null | undefined, nowMs: number): boolean {\n if (!row) return false;\n const from = toEpochMs((row as any).valid_from ?? (row as any).validFrom);\n // NaN comparisons are always false, so an unparseable bound fails closed.\n if (from !== undefined && !(nowMs >= from)) return false;\n const until = toEpochMs((row as any).valid_until ?? (row as any).validUntil);\n if (until !== undefined && !(nowMs < until)) return false;\n return true;\n}\n\n/**\n * The earliest FUTURE validity boundary among `rows`, in epoch ms — or\n * `undefined` when no consulted row can change its `isGrantActive` verdict by\n * clock movement alone.\n *\n * [#11971, #11633 §4 leg B constraint B.1] A cached grants envelope freezes a\n * validity decision: a grant whose `valid_until` passes DURING the cache TTL\n * keeps resolving until the entry expires, and a `valid_from` that arrives\n * during it stays unresolved. **No write occurs at either boundary**, so\n * write-invalidation is structurally blind to this class and the entry's\n * expiry timer is the only mechanism that can catch it. The grants cache\n * therefore expires an entry at `min(ttl, nextBoundary)` — this function is\n * the `nextBoundary` half, computed with the same parser `isGrantActive`\n * reads bounds with, so the two can never disagree about when a row flips.\n *\n * Both directions of flip count: a future `valid_until` (active → expired)\n * AND a future `valid_from` (not-yet-active → active). Bounds that are\n * absent, already past, or present-but-unparseable contribute nothing — an\n * unparseable bound fails the row closed *permanently* (see `isGrantActive`),\n * so there is no future instant at which its verdict changes.\n */\nexport function nextGrantValidityBoundary(\n rows: Iterable<GrantValidityWindow | null | undefined>,\n nowMs: number,\n): number | undefined {\n let next: number | undefined;\n for (const row of rows) {\n if (!row) continue;\n const from = toEpochMs((row as any).valid_from ?? (row as any).validFrom);\n const until = toEpochMs((row as any).valid_until ?? (row as any).validUntil);\n for (const bound of [from, until]) {\n // NaN (unparseable) fails both comparisons and is skipped — fail-closed\n // rows never flip, so they never set a boundary.\n if (bound !== undefined && bound > nowMs && (next === undefined || bound < next)) {\n next = bound;\n }\n }\n }\n return next;\n}\n\n/**\n * True when a grant row carries a `valid_until` that has already passed —\n * i.e. it WAS active and expired (not merely not-yet-active). The explain\n * engine uses this to report the dedicated \"held until … — expired\"\n * contributor state (ADR-0091 D2).\n */\nexport function isGrantExpired(row: GrantValidityWindow | null | undefined, nowMs: number): boolean {\n if (!row) return false;\n const until = toEpochMs((row as any).valid_until ?? (row as any).validUntil);\n if (until === undefined) return false;\n return !(nowMs < until);\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * ── The boot-time authorization-cache posture statement (#11968, #11633 §3) ──\n *\n * ⭐ **Non-optional**, by the 2026-08-25 ruling on #11633 (Fork 2 → B): whenever\n * a grants cache is enabled and there is **no** cross-node invalidation bus, the\n * deployment is told so, **out loud**, at boot.\n *\n * ## Why a statement and not a refusal\n *\n * A per-process cache bounded only by its TTL is a legitimate configuration —\n * #11633 §3 rules the TTL, not the bus, as the correctness contract, so a\n * single-node deployment (or one that simply accepts the window) is correct\n * with no bus at all. What is NOT acceptable is arriving there **without\n * noticing**: that is the shape of #4785, where a control was silently disabled\n * by configuration and nothing said so. The metadata bridge logs its own\n * absence at `debug` and that is right for metadata — a missed\n * `metadata.changed` costs a stale schema until reload and loses no data. Here\n * the same silence would cost a permission honoured past its revocation.\n *\n * ⇒ Enabled cache + no bus is a `warn`, every boot, naming the window it just\n * accepted. Not a refusal — a statement.\n *\n * ## The three postures, and the reason `disabled` is silent\n *\n * - `disabled` — no cache is enabled. **Silent.** There is no window to\n * state, and a line every boot on the shipped default\n * (TTL `0`, Fork 4) would train operators to ignore it —\n * which is how the loud line stops being loud.\n * - `ttl-only` — cache enabled, no cross-node bus. **LOUD (`warn`).**\n * - `bus-narrowed` — cache enabled, bus bridged. `info`, so the bridge's\n * presence is on the record next to its absence.\n *\n * Both arms are pinned in `authz-cache-posture.test.ts`: a statement that\n * appears always is no more useful than one that never appears.\n */\n\n/** Deployment variable that turns the grants cache on. `0` (default) = off. */\nexport const AUTHZ_GRANTS_CACHE_TTL_ENV = 'OS_AUTHZ_GRANTS_CACHE_TTL_MS';\n\n/**\n * What the local node has, in cross-node terms, for delivering\n * `authz.invalidated`.\n *\n * ⚠️ `in-process` is a distinct state on purpose, and it is the one that would\n * otherwise go unnoticed: `Runtime` auto-registers a **memory** cluster service\n * by default, so \"is a `cluster` service registered?\" answers *yes* on the\n * shipped default while the bus fans out to exactly nobody\n * (`service-cluster/src/memory/pubsub.ts`: *\"No cross-process delivery\"*; the\n * split-brain guard calls the same set `IN_PROCESS_DRIVERS`). A posture check\n * that asked only whether a service exists would therefore stay silent in\n * precisely the multi-replica deployment it exists to warn.\n */\nexport type AuthzInvalidationBusState =\n /** A cross-node transport is attached and carrying the channel. */\n | 'bridged'\n /** A cluster service exists, but its driver does not cross a process boundary. */\n | 'in-process'\n /** No cluster service, or no engine seam to attach one to. */\n | 'absent';\n\n/** The posture a boot resolves to. */\nexport type AuthzCachePosture = 'disabled' | 'ttl-only' | 'bus-narrowed';\n\nexport interface AuthzCachePostureInput {\n /** Configured grants-cache TTL in ms. `<= 0` means the cache is off. */\n ttlMs: number;\n /** What the node has for cross-node invalidation. */\n bus: AuthzInvalidationBusState;\n /** Cluster driver name, when one is registered. Surfaced in the message. */\n driver?: string;\n}\n\nexport interface AuthzCachePostureStatement {\n posture: AuthzCachePosture;\n /** True when this must be said at `warn`. See the module doc. */\n loud: boolean;\n /** The statement itself. Empty only for the silent `disabled` posture. */\n message: string;\n}\n\n/**\n * Resolve the posture. Pure — it reads its inputs and nothing else, so both\n * arms of the acceptance criterion (\"appears exactly when a cache flag is on\n * without a bus, and not otherwise\") are testable without a boot.\n */\nexport function resolveAuthzCachePosture(\n input: AuthzCachePostureInput,\n): AuthzCachePostureStatement {\n const { ttlMs, bus, driver } = input;\n\n if (!(ttlMs > 0)) {\n return { posture: 'disabled', loud: false, message: '' };\n }\n\n if (bus === 'bridged') {\n return {\n posture: 'bus-narrowed',\n loud: false,\n message:\n `[authz-cache] grants cache ENABLED (ttl=${ttlMs}ms) with the ` +\n `\"authz.invalidated\" bridge attached` +\n (driver ? ` (cluster driver \"${driver}\")` : '') +\n '. The bus narrows the TYPICAL convergence to one network hop; the TTL ' +\n 'remains the correctness bound, because no shipped driver delivers ' +\n 'better than at-most-once (cluster.mdx §4.2).',\n };\n }\n\n const why =\n bus === 'in-process'\n ? `the cluster driver \"${driver ?? 'memory'}\" is in-process and does not ` +\n 'fan out across replicas'\n : 'no cluster service is registered on this node';\n\n return {\n posture: 'ttl-only',\n loud: true,\n message:\n `[authz-cache] grants cache ENABLED (ttl=${ttlMs}ms) with NO ` +\n `\"authz.invalidated\" invalidation bus — ${why}. A grant revoked on ` +\n `another replica is honoured by this one for up to ${ttlMs}ms. That is a ` +\n 'supported configuration, not an error: the TTL is the correctness bound ' +\n 'and it still holds. It is stated because a silently-absent invalidation ' +\n 'bridge is how a security control gets disabled without anyone noticing ' +\n `(#4785). To narrow the typical window, configure a remote cluster driver; ` +\n `to remove it entirely, set ${AUTHZ_GRANTS_CACHE_TTL_ENV}=0.`,\n };\n}\n\n/** The reading of {@link AUTHZ_GRANTS_CACHE_TTL_ENV}, malformed input included. */\nexport interface AuthzGrantsCacheTtlReading {\n /** The effective TTL. `0` whenever the cache is off — including malformed. */\n ttlMs: number;\n /** The raw value read, when one was set. */\n raw?: string;\n /** True when a value was set but could not be read as a non-negative number. */\n malformed: boolean;\n}\n\n/**\n * Read the grants-cache TTL from deployment config.\n *\n * Deployment config, never a settings row (#11633 §5): the knob that bounds a\n * cache must not itself be served through a cached path, and operator-level\n * configuration comes from the environment.\n *\n * Default `0` — the grants cache is **off** unless a deployment turns it on and\n * accepts the staleness window explicitly (#11633 Fork 4, ruled 2026-08-25).\n *\n * ⚠️ A malformed value resolves to `0` but is reported as malformed rather than\n * folded into \"off\": `OS_AUTHZ_GRANTS_CACHE_TTL_MS=5OOO` (letter O) silently\n * meaning \"disabled\" is the same silent-disable class the posture statement\n * exists to prevent.\n */\nexport function readAuthzGrantsCacheTtlMs(\n env: Record<string, string | undefined> = typeof process !== 'undefined'\n ? process.env\n : {},\n): AuthzGrantsCacheTtlReading {\n const raw = env[AUTHZ_GRANTS_CACHE_TTL_ENV];\n if (raw === undefined || raw.trim() === '') {\n return { ttlMs: 0, malformed: false };\n }\n const parsed = Number(raw.trim());\n if (!Number.isFinite(parsed) || parsed < 0) {\n return { ttlMs: 0, raw, malformed: true };\n }\n return { ttlMs: Math.floor(parsed), raw, malformed: false };\n}\n\n/** Minimal sink shape — `warn` is the member every logger in this repo has. */\nexport interface AuthzPostureSink {\n warn(message: string, meta?: Record<string, unknown>): void;\n info?(message: string, meta?: Record<string, unknown>): void;\n debug?(message: string, meta?: Record<string, unknown>): void;\n}\n\n/**\n * State the posture at boot. `warn` for the loud arm, `info` for the bridged\n * one, and nothing at all when no cache is enabled (see the module doc for why\n * silence is the right default rather than a courtesy line).\n *\n * A malformed TTL value is warned about on its own, because \"we read your\n * setting as off\" is exactly what a deployment must not have to infer.\n */\nexport function reportAuthzCachePosture(\n input: AuthzCachePostureInput & { malformedTtl?: { raw?: string } },\n sink: AuthzPostureSink,\n): AuthzCachePostureStatement {\n if (input.malformedTtl) {\n sink.warn(\n `[authz-cache] ${AUTHZ_GRANTS_CACHE_TTL_ENV}=` +\n `${JSON.stringify(input.malformedTtl.raw ?? '')} is not a non-negative ` +\n 'number; the grants cache is treated as DISABLED. Set a millisecond ' +\n 'count, or 0 to disable it deliberately.',\n );\n }\n\n const statement = resolveAuthzCachePosture(input);\n if (statement.posture === 'disabled') return statement;\n if (statement.loud) sink.warn(statement.message);\n else sink.info?.(statement.message);\n return statement;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * ── Leg B of #11633: the cross-request grants cache (#11971) ────────────────\n *\n * Caches the `UserAuthzGrants` envelope `resolveUserAuthzGrants` produces —\n * the answer to \"what may this user do\", resolved on every authenticated\n * request. Ruled shape (maintainer acceptance on #11633, 2026-08-25, verbatim\n * 「接受你的建议,继续」), none of it re-litigable here:\n *\n * - **Fork 4 — default OFF.** `OS_AUTHZ_GRANTS_CACHE_TTL_MS` defaults to `0`\n * and `0` is a REAL path: nothing is wired, nothing subscribes, nothing is\n * stored, and the query pattern is byte-identical to the uncached one.\n * A deployment that turns it on accepts the staleness window explicitly.\n * - **Fork 1 → A — coarse invalidation.** Any write to a watched object\n * retires EVERY entry for that engine. The seam cannot say whose entry a\n * `where`-shaped update touches without reading the row back (#11633\n * §2.2), and `org_user_ids` depends on OTHER users' rows — a `sys_member`\n * write must retire the organization's entries, not the writer's, which\n * coarse invalidation gets right by construction. Keyed invalidation is\n * ⛔ not a scheduled follow-up; it first needs a write-heavy-tenant\n * measurement.\n * - **Expiry-boundary rule.** An entry expires at `min(ttl, nextBoundary)`:\n * ADR-0091 validity windows flip with **no write at the boundary**, so\n * write-invalidation is structurally blind there and the timer is the only\n * mechanism for that class (`nextGrantValidityBoundary`).\n * - **The TTL is the correctness contract; every invalidation signal only\n * narrows the typical window.** `authz-invalidation-channel.ts` carries\n * the full statement: no shipped cluster driver delivers better than\n * at-most-once, so a missed cross-node hint is EXPECTED and the entry's\n * expiry is what bounds the staleness it leaves behind.\n *\n * ## What invalidates, and through which seam\n *\n * 1. **Local writes — the engine middleware seam, filtered to the watched\n * set.** The engine's write epoch advances on EVERY write to ANY object\n * (`objectql/src/engine.ts`), and consuming it raw would revive the\n * measured keying trap the ruling names: `sys_session.last_activity_at`\n * is written on a once-a-minute cadence per active session\n * (`enforceSessionControls`), and a watched object with a background\n * write cadence silently converts a cache into a non-cache. `sys_session`\n * does not feed the grants envelope, so this cache registers its own\n * engine middleware and retires entries only on writes to\n * {@link GRANTS_CACHE_WATCHED_OBJECTS} — the tables the resolver reads,\n * plus `sys_user` (`ai_access`, and the #11663 config-anchor email).\n * The bump lands AFTER the write completes (settings-seam discipline:\n * `emitChange` below `upsertRow`), so a resolution in flight across a\n * write stamps a pre-write generation and is dead on arrival — the safe\n * direction.\n * 2. **Everything the middleware cannot see — the epoch's non-`write`\n * reasons, wholesale.** `'metadata'` (a permission set can be DECLARED,\n * so a Studio edit is a permission change with no row written —\n * plugin-security bumps the engine epoch for it), `'remote'` (a peer's\n * `authz.invalidated` hint, which carries no object and whose only\n * correct reading is \"retire everything\"), and `'manual'`. Local\n * `'write'` bumps are deliberately ignored HERE because seam 1 already\n * handled them with object precision.\n *\n * ## Caching requires the seams — a `ql` without them declines\n *\n * Same load-bearing rule as leg C (`resolve-authz-context.ts`): a `ql` that\n * exposes no write epoch or no `registerMiddleware` is a `ql` whose writes\n * this cache cannot observe, and the acceptance criterion — a grant/revoke is\n * observed by the NEXT request on the writing node, by invalidation and not\n * by TTL — is unmeetable against it. Rather than degrade to the TTL-only\n * shape, the cache declines and every such caller keeps its exact uncached\n * behaviour. Every existing test double takes this path.\n *\n * ## Keying — seeds are part of the answer, so seeds are part of the key\n *\n * One entry per `(userId, tenantId, seedEmail, seedPermissions)`. #11633 §4\n * B.2 sketches the alternative — cache the seedless envelope, re-apply seeds\n * outside — and conditions it on seeding being \"a pure prepend, which must be\n * *pinned*, not assumed\". Measured here before implementing: it is NOT a pure\n * prepend. Seeds flow into derivations, not just into the array — a seed\n * named in `ORGANIZATION_ADMIN_GRANTS` moves the ADR-0095 posture rung, and\n * seeded `email`/`ai_seat` suppress the `sys_user` read (`needsUserRow`), so\n * a seedless resolution issues a query the seeded path must not issue (the\n * multiset half of pin 5). Re-deriving those outside the resolver would be a\n * second copy of authorization logic — the drift shape #10348 exists to end.\n * Keying on the seeds keeps every cached answer bit-identical to its own\n * uncached resolution by construction, at the cost of one entry per key-scope\n * combination — bounded by active principals, and retired wholesale anyway.\n *\n * `nowMs` is deliberately NOT in the key: time is not identity. A caller's\n * clock participates through entry expiry (`expiresAt` is compared against\n * the RESOLVING call's clock), which is what makes the validity-boundary pin\n * testable with an injected clock.\n *\n * ## Served values are clones\n *\n * `resolveAuthzContext` assigns the envelope's arrays into the request\n * context, and downstream enforcement is free to mutate what it was handed.\n * Entries therefore store a private `structuredClone` and every hit serves a\n * fresh one — a caller's mutation can never corrupt the cached answer.\n */\n\nimport type {\n ResolveUserAuthzGrantsOptions,\n UserAuthzGrants,\n} from './resolve-authz-context.js';\nimport { readAuthzGrantsCacheTtlMs } from './authz-cache-posture.js';\n\n/**\n * The watched set — the objects whose rows feed the grants envelope, derived\n * in #11633 §4 (leg B) by reading the resolver, and ruled with two measured\n * keying traps attached:\n *\n * ⛔ `sys_session` stays OUT. It does not feed the envelope, and its\n * `last_activity_at` is written once a minute per active session — watching\n * it would retire the whole cache on that cadence (the \"background write\n * cadence converts a cache into a non-cache\" trap).\n *\n * `metadata.changed` is the eighth trigger and is not an object: it arrives\n * as an epoch bump with reason `'metadata'` (see the module doc).\n */\nexport const GRANTS_CACHE_WATCHED_OBJECTS: ReadonlySet<string> = new Set([\n 'sys_member',\n 'sys_user_position',\n 'sys_user_permission_set',\n 'sys_position',\n 'sys_position_permission_set',\n 'sys_permission_set',\n 'sys_user',\n]);\n\ninterface GrantsCacheEntry {\n /** A private clone of the resolved envelope. Never handed out directly. */\n value: UserAuthzGrants;\n /** The generation this value was resolved AT (read before the reads). */\n gen: number;\n /** `min(resolvedAt + ttl, nextValidityBoundary)` on the resolving clock. */\n expiresAt: number;\n}\n\ninterface GrantsCacheState {\n /**\n * Coarse retirement counter. An entry is live only while this has not\n * moved since the entry's resolution began. Advanced by seam 1 (watched\n * writes, post-completion) and seam 2 (non-`write` epoch reasons).\n */\n gen: number;\n entries: Map<string, GrantsCacheEntry>;\n}\n\n/**\n * Per-engine state. WeakMap-keyed on the `ql` instance for the same reason as\n * leg C: two environments/tenant engines sharing a process must never see each\n * other's entries, and a dropped engine takes its cache with it.\n *\n * `null` records a `ql` whose wiring FAILED partway — poisoned, never cached,\n * so a half-attached invalidation seam can never stand behind an entry.\n */\nconst grantsCacheStates = new WeakMap<object, GrantsCacheState | null>();\n\n/** Structural mirror of `WriteEpochLike` — see `readWriteEpoch` in leg C for\n * why the import direction is unavailable (`@objectstack/objectql` depends on\n * this package). The WHOLE surface is checked so a bare `{ current }` on some\n * unrelated double cannot license caching against a counter nothing bumps. */\ninterface EpochSeam {\n readonly current: number;\n bump(reason: string): unknown;\n subscribe(listener: (epoch: number, reason: string) => void): unknown;\n}\n\ninterface MiddlewareSeamQl {\n writeEpoch?: unknown;\n registerMiddleware?: unknown;\n find?: unknown;\n}\n\nconst WRITE_OPERATIONS = new Set(['insert', 'update', 'delete']);\n\n/**\n * Fetch — and on first sight of an engine, wire — the invalidation state.\n * Returns `undefined` for a `ql` without both seams (declines, uncached path)\n * and for one whose wiring threw (poisoned; see {@link grantsCacheStates}).\n *\n * ⚠️ Only ever called with a non-zero TTL in hand: with the cache OFF this\n * module must leave NO footprint on the engine — no middleware, no epoch\n * subscription — so that `0` stays a real path, not a degenerate TTL\n * (#11633 §7 pin 7).\n */\nfunction grantsCacheState(ql: object): GrantsCacheState | undefined {\n const existing = grantsCacheStates.get(ql);\n if (existing !== undefined) return existing ?? undefined;\n\n const seamQl = ql as MiddlewareSeamQl;\n const epoch = seamQl.writeEpoch as Partial<EpochSeam> | null | undefined;\n const hasEpoch =\n !!epoch &&\n typeof epoch === 'object' &&\n typeof epoch.current === 'number' &&\n typeof epoch.bump === 'function' &&\n typeof epoch.subscribe === 'function';\n if (!hasEpoch || typeof seamQl.registerMiddleware !== 'function') {\n // Not memoized: probing is cheap and a double's shape is static anyway.\n return undefined;\n }\n\n const state: GrantsCacheState = { gen: 0, entries: new Map() };\n try {\n // Seam 1 — watched writes, object-filtered, bumped AFTER the write\n // completes (and on a throw: a partial write is a write; over-invalidation\n // is the safe direction and costs one re-read).\n (seamQl.registerMiddleware as (\n fn: (\n ctx: { object?: unknown; operation?: unknown },\n next: () => Promise<void>,\n ) => Promise<void>,\n ) => void)(async (ctx, next) => {\n if (\n typeof ctx?.operation !== 'string' ||\n !WRITE_OPERATIONS.has(ctx.operation) ||\n typeof ctx?.object !== 'string' ||\n !GRANTS_CACHE_WATCHED_OBJECTS.has(ctx.object)\n ) {\n return next();\n }\n try {\n await next();\n } finally {\n state.gen += 1;\n }\n });\n\n // Seam 2 — everything object-less: 'metadata' (declared permission sets),\n // 'remote' (peer hints carry no object; wholesale is the only correct\n // response), 'manual'. Local 'write' bumps are seam 1's job, already done\n // with object precision — retiring on them here would re-import the\n // sys_session trap this module exists to keep out. Never disposed, like\n // leg C's settings subscription: it holds one integer per engine and the\n // engine outlives this module's interest in it.\n (epoch as EpochSeam).subscribe((_epoch, reason) => {\n if (reason !== 'write') state.gen += 1;\n });\n } catch {\n // Wiring failed partway — poison this ql rather than cache behind a seam\n // that may be half-attached. Entries never existed, so nothing to drop.\n grantsCacheStates.set(ql, null);\n return undefined;\n }\n\n grantsCacheStates.set(ql, state);\n return state;\n}\n\n/** Seeds and identity in, collision-free key out. JSON, not delimiters — a\n * seed permission is caller-supplied text and must not be able to alias\n * another caller's key by containing a separator. */\nfunction grantsCacheKey(userId: string, opts: ResolveUserAuthzGrantsOptions): string {\n return JSON.stringify([\n userId,\n opts.tenantId ?? null,\n opts.seedEmail ?? null,\n Array.isArray(opts.seedPermissions) ? opts.seedPermissions : [],\n ]);\n}\n\nconst cloneGrants = (grants: UserAuthzGrants): UserAuthzGrants => structuredClone(grants);\n\n/**\n * One attempted cache interaction, opened at the top of\n * `resolveUserAuthzGrants` and committed (on a miss) with the resolved\n * envelope. The generation and clock are snapshotted at OPEN — before any\n * read is issued — so a write landing while the resolution is in flight\n * moves `state.gen` past what `commit` stamps and the entry is dead on\n * arrival (leg C's clear-then-repopulate discipline, #11633 §7 pin 2).\n */\nexport interface GrantsCacheAttempt {\n /** A live entry's envelope, already cloned for the caller. */\n hit?: UserAuthzGrants;\n /**\n * Store a freshly resolved envelope. `nextBoundaryMs` is the earliest\n * upcoming ADR-0091 validity boundary among the rows consulted\n * (`nextGrantValidityBoundary`, computed by the resolver), capping the expiry\n * below the TTL — the expiry-boundary rule.\n */\n commit(grants: UserAuthzGrants, nextBoundaryMs: number | undefined): void;\n}\n\n/**\n * Open the grants cache for one resolution. Returns `undefined` — the fully\n * uncached path, zero side effects — when any of these holds:\n *\n * - the caller is on the ruled bypass list (`bypassGrantsCache`);\n * - `OS_AUTHZ_GRANTS_CACHE_TTL_MS` is unset, `0`, or malformed (Fork 4:\n * OFF is the default and a real path);\n * - `ql` is not an engine-shaped object, or lacks the write-epoch /\n * middleware seams the invalidation contract requires (declines — see\n * the module doc).\n */\nexport function openUserGrantsCache(\n ql: unknown,\n userId: string,\n opts: ResolveUserAuthzGrantsOptions,\n): GrantsCacheAttempt | undefined {\n if (opts.bypassGrantsCache) return undefined;\n const { ttlMs } = readAuthzGrantsCacheTtlMs();\n if (ttlMs <= 0) return undefined;\n if (!ql || typeof ql !== 'object' || typeof (ql as MiddlewareSeamQl).find !== 'function') {\n return undefined;\n }\n const state = grantsCacheState(ql);\n if (!state) return undefined;\n\n const key = grantsCacheKey(userId, opts);\n const now = opts.nowMs ?? Date.now();\n const genAtOpen = state.gen;\n\n const existing = state.entries.get(key);\n if (existing) {\n if (existing.gen === state.gen && existing.expiresAt > now) {\n return { hit: cloneGrants(existing.value), commit: () => {} };\n }\n // Dead entry (retired or expired) — drop it now rather than letting it\n // shadow the fresh value if the commit below never stores (e.g. a\n // boundary already inside this instant).\n state.entries.delete(key);\n }\n\n return {\n commit(grants, nextBoundaryMs) {\n const expiresAt = Math.min(now + ttlMs, nextBoundaryMs ?? Number.POSITIVE_INFINITY);\n if (expiresAt <= now) return;\n state.entries.set(key, { value: cloneGrants(grants), gen: genAtOpen, expiresAt });\n },\n };\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * platform-admin.ts — the CONFIG half of the platform-admin derivation\n * (#11663 leg L2, design comment 5394453215 §2/§3/§4, maintainer acceptance\n * 2026-08-25 「接受你的建议,继续」, bundle 1A/2B/3A/4A/5A/6A/7A).\n *\n * ## What this module answers\n *\n * \"Is the principal behind THIS `sys_user` row one of the platform\n * administrators the DEPLOYMENT declared?\" — the deployment-config anchor for\n * `PLATFORM_ADMIN`, beside the stored-grant anchor (ADR-0068 D2) that\n * `resolve-authz-context.ts` §6b already reads. Both routes meet at the ONE\n * derivation site; there is deliberately no second one.\n *\n * ## The ruled shape, and why each half is not negotiable here\n *\n * - **Choice 1A — one variable.** `OS_PLATFORM_OWNER_EMAIL`\n * ({@link PLATFORM_OWNER_EMAIL_ENV}), the name plugin-auth's walled boot\n * refusal and plugin-security's `bootstrapPlatformAdmin` already quote — the\n * latter in its fail-closed backstop for an undeclared or refused config, and\n * in the config-derived standing it logs beside it. ⚠️ That second site is no\n * longer an ELEVATION refusal: since the #11663 platform-admin re-anchor\n * (leg L4) the walled `bootstrapPlatformAdmin` writes no grant row and\n * elevates nobody — it reports. A second spelling is a second door: generated\n * configs and docs would carry both and one of them would silently do nothing.\n * - **Choice 2B — a comma-separated LIST**, because a single configured address\n * is a single point of human failure and losing that mailbox leaves a\n * deployment with no administrator and no in-product recovery. One separator,\n * one normalization (`trim().toLowerCase()`), duplicates collapsed, blank\n * entries dropped — and **any UNPARSEABLE entry fails the WHOLE variable\n * closed** ({@link parsePlatformAdminEmails}). ⛔ Never skip-and-continue: a\n * dropped malformed entry turns a config typo into either a silent lockout or,\n * worse, a silently NARROWER administrator set nobody notices.\n * - **Choice 3A — live read per derivation**, with a per-process memo keyed on\n * the RAW string ({@link resolvePlatformAdminEmails}), so a rolled process\n * picks up a revocation with no special path and the hot authorization path\n * still re-parses only when the operator's value actually changes. ⛔ There is\n * no runtime mutation endpoint and none may be added: an endpoint whose only\n * job is to change who is a superuser is the highest-value target on the\n * platform and precisely the surface an agent could be talked into calling.\n * - **Verified-email match ONLY.** An unverified account holding a configured\n * address confers nothing ({@link matchesConfiguredPlatformAdmin} consults\n * `isEmailVerifiedUserRow`, whose allow-list reads an ABSENT column as\n * unverified).\n * - **Empty/unset = ZERO platform admins, fail closed.** The derivation answers\n * `false` on an empty list before it looks at any row.\n *\n * ## ⚠️ The single most important mechanical pin\n *\n * The address compared here is the one on the caller's **own stored `sys_user`\n * row**, never `grants.email`. `resolveUserAuthzGrants` seeds `grants.email`\n * from `opts.seedEmail` — a CALLER/SESSION-supplied string that wins over the\n * stored read — so deriving superuser standing from it would open a new\n * escalation channel inside the very change meant to close one. That is why\n * {@link matchesConfiguredPlatformAdmin} takes a ROW and reads `row.email`\n * itself, and why `resolve-authz-context.ts` hands it `getUserRow()`.\n */\n\nimport { isEmailVerifiedUserRow, PLATFORM_OWNER_EMAIL_ENV, resolvePlatformOwnerEmail } from '@objectstack/types';\n\n/**\n * The one separator {@link parsePlatformAdminEmails} splits on (Choice 2B).\n * Same shape as `OS_CORS_ORIGIN`, the existing comma-separated precedent.\n */\nexport const PLATFORM_ADMIN_EMAIL_SEPARATOR = ',';\n\n/**\n * The ONE normalization, applied to both sides of every comparison: trim, then\n * lowercase. Email domains are case-insensitive and every mailbox this platform\n * issues is too, so an operator who types `Ada@Example.com` and a row storing\n * `ada@example.com` must be one administrator, not two half-matches.\n */\nexport function normalizePlatformAdminEmail(value: unknown): string {\n return typeof value === 'string' ? value.trim().toLowerCase() : '';\n}\n\n/**\n * Is one already-normalized entry a usable address?\n *\n * Deliberately a SHAPE check, not RFC-5322 and not zod's `.email()`. Measured\n * before choosing (zod 4.4.3, the version this package resolves):\n * `z.string().email()` rejects `a@b.c` (its domain pattern demands a\n * two-character-or-longer final label) and `admin@localhost`. Both are\n * addresses a deployment can legitimately declare — `a@b.c` is this leg's own\n * acceptance-criterion value — and refusing one fails the WHOLE variable\n * closed under Choice 2B, i.e. it LOCKS THE DEPLOYMENT OUT of its own\n * administration. The hazard this predicate exists to catch is an operator\n * typo (a forgotten separator, a pasted sentence, a bare name), not a\n * standards deviation, so it asks only what a `sys_user.email` must minimally\n * be to ever match: one `@`, something either side of it, and no whitespace.\n *\n * ⛔ Do not tighten this into a \"real\" email validator. Over-strictness here is\n * not a stricter contract, it is an unrecoverable lockout on a value nobody can\n * fix from inside the product.\n */\nfunction isParseableAddress(entry: string): boolean {\n if (/\\s/.test(entry)) return false;\n const at = entry.indexOf('@');\n if (at <= 0) return false; // absent, or an empty local part\n if (entry.indexOf('@', at + 1) !== -1) return false; // more than one `@`\n return at < entry.length - 1; // a non-empty domain part\n}\n\n/** The parsed state of `OS_PLATFORM_OWNER_EMAIL` for one raw value. */\nexport interface PlatformAdminEmailConfig {\n /**\n * Normalized, de-duplicated administrator addresses in the order the operator\n * declared them. EMPTY when the variable is unset, blank, or refused — those\n * three are one outcome by design (zero config-derived administrators), and\n * they are told apart by {@link refusal} rather than by a second empty value.\n */\n readonly emails: readonly string[];\n /**\n * The SAME administrators as {@link emails} and index-aligned with it, each\n * spelled as the operator typed it — trimmed only, never lowercased (exactly\n * what `resolvePlatformOwnerEmail()` used to hand a single-value reader).\n *\n * It exists so that no consumer ever has a reason to split {@link raw} a\n * second time. Two readers need the as-typed form and neither may re-parse\n * to get it: the platform-admin STANDING surface's by-email `sys_user`\n * lookup queries the verbatim spelling alongside the normalized one (an\n * imported/legacy row may not be stored lowercased, and a driver `where` is\n * an exact match) — that lookup is `resolvePlatformAdminStanding` in\n * plugin-security's `platform-admin-service.ts`, which inherited the\n * two-spelling discipline from the elevation gate the #11663 re-anchor\n * (leg L4) retired — and the walled boot diagnostic quotes the addresses\n * back to the operator, who should see what they wrote.\n */\n readonly declaredSpellings: readonly string[];\n /** What the operator actually typed, when the variable was set to anything. */\n readonly raw?: string;\n /**\n * Set when the variable was DECLARED but refused, naming the offending entry.\n * `emails` is empty in that case: the whole variable fails closed, never the\n * one entry (Choice 2B).\n */\n readonly refusal?: string;\n}\n\nconst EMPTY_CONFIG: PlatformAdminEmailConfig = Object.freeze({\n emails: Object.freeze([]) as readonly string[],\n declaredSpellings: Object.freeze([]) as readonly string[],\n});\n\n/**\n * Parse one raw `OS_PLATFORM_OWNER_EMAIL` value into the administrator list.\n *\n * Pure — no env read, no logging — so the whole parse is testable as a\n * function of its input. {@link resolvePlatformAdminEmails} is the env-reading,\n * memoizing, once-per-value-loud wrapper around it.\n */\nexport function parsePlatformAdminEmails(raw: string | undefined): PlatformAdminEmailConfig {\n if (raw == null) return EMPTY_CONFIG;\n const text = String(raw);\n if (text.trim() === '') return EMPTY_CONFIG;\n\n const emails: string[] = [];\n const declaredSpellings: string[] = [];\n for (const piece of text.split(PLATFORM_ADMIN_EMAIL_SEPARATOR)) {\n const entry = normalizePlatformAdminEmail(piece);\n // Blanks are DROPPED, not refused: a trailing separator or a line wrapped\n // for readability is a formatting habit, not a typo that changes who\n // administers the deployment.\n if (entry === '') continue;\n if (!isParseableAddress(entry)) {\n return {\n emails: Object.freeze([]) as readonly string[],\n declaredSpellings: Object.freeze([]) as readonly string[],\n raw: text,\n refusal:\n `${PLATFORM_OWNER_EMAIL_ENV} entry ${JSON.stringify(piece)} is not an email address, so the `\n + 'WHOLE variable is refused and this deployment has ZERO config-derived platform '\n + 'administrators. The entry is not skipped on purpose: silently dropping it would leave '\n + 'a narrower administrator set than the operator declared, with nothing to notice. Fix '\n + `the entry, or remove it — ${PLATFORM_OWNER_EMAIL_ENV} takes one address or a `\n + 'comma-separated list of them.',\n };\n }\n // Duplicates collapse; first declaration wins the position — and the\n // spelling that wins it is the one kept, so the two arrays stay aligned.\n if (!emails.includes(entry)) {\n emails.push(entry);\n declaredSpellings.push(piece.trim());\n }\n }\n\n return {\n emails: Object.freeze(emails) as readonly string[],\n declaredSpellings: Object.freeze(declaredSpellings) as readonly string[],\n raw: text,\n };\n}\n\n/**\n * Sink for the refusal notice. `console` by default so the loudness does not\n * depend on any host wiring it up — a deployment that declared administrators\n * and got none must never find that out silently. Swappable for tests.\n */\nexport interface PlatformAdminConfigSink {\n error(message: string): void;\n warn(message: string): void;\n}\n\nconst defaultSink: PlatformAdminConfigSink = {\n error: (m) => console.error(m),\n warn: (m) => console.warn(m),\n};\nlet sink: PlatformAdminConfigSink = defaultSink;\n\n/** Redirect this module's notices (tests). Returns the previous sink. */\nexport function setPlatformAdminConfigSink(next: PlatformAdminConfigSink | undefined): PlatformAdminConfigSink {\n const prev = sink;\n sink = next ?? defaultSink;\n return prev;\n}\n\n/**\n * The per-process memo (Choice 3A). Keyed on the RAW string, so a value the\n * operator has not changed is parsed once and a value they HAVE changed is\n * re-read on the very next derivation — 3A's semantics at 3B's cost, with no\n * cached second copy of the answer to drift from `process.env`.\n *\n * `NOT_MEMOIZED` is a sentinel rather than `undefined` because `undefined` is\n * itself a legal memo key (the variable unset).\n */\nconst NOT_MEMOIZED = Symbol('platform-admin-config-not-memoized');\nlet memoKey: string | undefined | typeof NOT_MEMOIZED = NOT_MEMOIZED;\nlet memoValue: PlatformAdminEmailConfig = EMPTY_CONFIG;\n\n/**\n * Resolve the deployment's declared platform administrators — live from the\n * environment, memoized on the raw string, and LOUD exactly once per distinct\n * refused value.\n *\n * Silence for an UNSET variable is deliberate and is not the same decision:\n * every `single`-posture deployment runs that way by design (Choice 4A leaves\n * first-user promotion in place there), and warning on the shipped default is\n * how a log people read becomes a log people skim. A walled posture with the\n * variable unset already REFUSES BOOT one layer up, in plugin-auth.\n */\nexport function resolvePlatformAdminEmails(): PlatformAdminEmailConfig {\n const raw = resolvePlatformOwnerEmail();\n if (memoKey !== NOT_MEMOIZED && memoKey === raw) return memoValue;\n\n const parsed = parsePlatformAdminEmails(raw);\n memoKey = raw;\n memoValue = parsed;\n // Once per distinct raw value, which for a real deployment is once per\n // process: the memo boundary IS the \"have we said this already\" boundary, so\n // this can never become a per-request line.\n if (parsed.refusal) sink.error(`[authz] ${parsed.refusal}`);\n return parsed;\n}\n\n/** Drop the memo — for tests that drive several values through one process. */\nexport function resetPlatformAdminEmailMemo(): void {\n memoKey = NOT_MEMOIZED;\n memoValue = EMPTY_CONFIG;\n}\n\n/**\n * Does this stored `sys_user` row belong to a declared platform administrator?\n *\n * Fail-closed on every axis: an empty/refused config answers `false` without\n * looking at the row at all, an address that is not on the list answers\n * `false`, and an address that IS on the list but whose `email_verified`\n * column does not read verified answers `false` too. The last one is the point\n * of the whole leg — an unverified account holding a configured address confers\n * nothing, so an attacker who registers the operator's address before the\n * operator does gains no standing by it.\n *\n * ⚠️ `row` MUST be the caller's own stored `sys_user` row. See this module's\n * header: `grants.email` is caller-seedable and reading it here would be an\n * escalation channel.\n */\nexport function matchesConfiguredPlatformAdmin(\n row: unknown,\n config: PlatformAdminEmailConfig,\n): boolean {\n if (config.emails.length === 0) return false;\n if (!row || typeof row !== 'object') return false;\n if (!isConfiguredPlatformAdminEmail((row as { email?: unknown }).email, config)) return false;\n return isEmailVerifiedUserRow(row);\n}\n\n/**\n * [#13147] Is this bare ADDRESS one of the declared administrators?\n *\n * The membership half of {@link matchesConfiguredPlatformAdmin}, spelled once\n * and exported, because the row-and-verified predicate above is not the shape\n * every reader of `OS_PLATFORM_OWNER_EMAIL` needs:\n *\n * - the walled platform-admin STANDING surface must keep the two halves\n * SEPARATE — `resolvePlatformAdminStanding`\n * (`plugin-security/platform-admin-service.ts`, reported at boot by\n * `bootstrap-platform-admin.ts`) answers `registered` and `verified` as two\n * independent per-entry fields, so the operator's log can tell \"not\n * registered yet\" apart from \"registered, NOT verified\". ⚠️ That reason\n * predates the #11663 re-anchor and survives it: the pair used to be the\n * retired elevation gate's `walled_owner_not_registered` /\n * `walled_owner_not_verified` reasons — the mechanism moved, the need to\n * keep the halves apart did not;\n * - the creation-time operator stamp (`plugin-auth`) is handed an email\n * STRING by better-auth, before any row exists to read;\n * - the Layer 0 wall bypass takes a fast negative on the session's\n * server-resolved email before it spends a `sys_user` read.\n *\n * ⛔ Those readers must NOT hand-roll `config.emails.includes(x.toLowerCase())`\n * instead. That expression is where a seventh dialect gets born: it silently\n * drops the trim, and a stray space in one list entry then makes an\n * administrator vanish with nothing to notice. One membership expression, one\n * normalization ({@link normalizePlatformAdminEmail}), one place to fix.\n *\n * Fail-closed like everything else here: an empty or refused config answers\n * `false` without looking at the candidate, and a blank/non-string candidate\n * answers `false` against any config.\n *\n * ⚠️ This is a match against CONFIGURATION only — it says nothing about whether\n * the address is verified, or whether the caller actually holds it. Standing\n * still requires {@link matchesConfiguredPlatformAdmin} over the caller's own\n * stored row; see this module's header for why `grants.email` is never it.\n */\nexport function isConfiguredPlatformAdminEmail(\n email: unknown,\n config: PlatformAdminEmailConfig,\n): boolean {\n if (config.emails.length === 0) return false;\n const candidate = normalizePlatformAdminEmail(email);\n return candidate !== '' && config.emails.includes(candidate);\n}\n\n/**\n * [#11663 P5] The migration pointer for the LEGACY anchor.\n *\n * Nothing is revoked in this leg: an unscoped, in-window `admin_full_access`\n * grant still confers `PLATFORM_ADMIN` exactly as it did (design §5 step 3 —\n * config-derived standing is ADDED, which is what makes this safe to land ahead\n * of every deployment setting the variable). What changes is that the row is now\n * the OLD anchor, so a holder whose standing rests on it alone is told, once,\n * which config line re-anchors them before the row route is removed.\n *\n * Once per process, naming ONE holder. Deliberately not once per holder: this\n * runs inside the authorization path, and an unbounded per-user ledger there is\n * a memory surface for something whose whole job is to say \"go look at the\n * configuration\". The population question (who ALL the administrators are) is\n * the audit surface's, filed as its own leg.\n */\nlet legacyGrantPointerSaid = false;\n\nexport function reportLegacyPlatformAdminGrant(input: {\n userId: string;\n email?: unknown;\n}): void {\n if (legacyGrantPointerSaid) return;\n legacyGrantPointerSaid = true;\n const email = normalizePlatformAdminEmail(input.email);\n sink.warn(\n `[authz] user ${input.userId} holds PLATFORM_ADMIN through the legacy unscoped `\n + `'admin_full_access' grant row, not through ${PLATFORM_OWNER_EMAIL_ENV}. The grant row is `\n + 'the OLD anchor and is honoured for now; it is removed in a later release. Re-anchor this '\n + `deployment by declaring its administrators in configuration: ${PLATFORM_OWNER_EMAIL_ENV}=`\n + `${email || '<the administrator\\'s verified email address>'}`\n + ' (comma-separated for several), and make sure each account\\'s email is VERIFIED — an '\n + 'unverified account holding a configured address is not an administrator. Reported once '\n + 'per process; further holders are not listed.',\n );\n}\n\n/** Drop the once-per-process latch — for tests. */\nexport function resetLegacyPlatformAdminGrantReport(): void {\n legacyGrantPointerSaid = false;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * ── The monotonic posture ladder (ADR-0095 D2/D3) ───────────────────────────\n *\n * The principal-tiering enum resolved ONCE in `resolveAuthzContext`\n * (`PLATFORM_ADMIN > TENANT_ADMIN > MEMBER > EXTERNAL`). This module owns two\n * things and deliberately nothing more:\n *\n * 1. **Derivation (D3).** {@link derivePosture} maps held *capability grants*\n * — never a better-auth role — to a rung. `PLATFORM_ADMIN` derives from the\n * unscoped `admin_full_access` grant (the `viewAllRecords`/`modifyAllRecords`\n * evidence the superuser bypass already trusts); `TENANT_ADMIN` from the\n * `organization_admin` grant. The better-auth `role='admin'` is upstream a\n * *provisioning source* of those grants (`auto-org-admin-grant.ts`), so it\n * never re-enters adjudication here — the #2836 dual-track class is closed\n * by construction.\n *\n * 2. **The rung → injection-rule mapping + its tested invariants (D2).** Each\n * rung maps to EXACTLY ONE row-visibility injection rule\n * ({@link POSTURE_INJECTION_RULE}). {@link postureVisibleRows} is the\n * REFERENCE MODEL of those rules over a synthetic row-set — it locks the two\n * properties the ADR requires as invariants: strict nesting (rung n's\n * visible set ⊇ rung n−1's) and the EXTERNAL deny-by-default semantics\n * (explicit shares only, OWD never widens it).\n *\n * This module is NOT the enforcement path. The effective read/write filter is\n * `Layer0(tenant) AND Layer1(business RLS)`, computed in `@objectstack/plugin-\n * security` (`tenant-layer.ts` + `security-plugin.ts`), and the real behavior\n * guard is the `authz-matrix-gate` unit snapshot + the dogfood conformance\n * matrix. The reference model here exists so the ladder's *mathematical*\n * properties can be asserted at the unit layer without an enforcement boot, and\n * so the EXTERNAL rung — which has no enforcement path yet — cannot be\n * reinvented differently when portal/external membership arrives.\n */\n\nimport type { AuthzPosture } from '@objectstack/spec/security';\n\n/**\n * The rung ordering, high privilege → low, matching the spec enum's numeric\n * values (`PLATFORM_ADMIN=3 … EXTERNAL=0`). Visibility grows monotonically UP\n * this ladder (see {@link postureVisibleRows}).\n */\nexport const POSTURE_LADDER = [\n 'PLATFORM_ADMIN',\n 'TENANT_ADMIN',\n 'MEMBER',\n 'EXTERNAL',\n] as const satisfies readonly AuthzPosture[];\n\n/** Numeric rank per rung (mirrors the spec `AuthzPosture` enum values). */\nexport const POSTURE_RANK: Record<AuthzPosture, number> = {\n PLATFORM_ADMIN: 3,\n TENANT_ADMIN: 2,\n MEMBER: 1,\n EXTERNAL: 0,\n};\n\n/**\n * The ONE row-visibility injection rule each rung maps to (ADR-0095 D2). Prose,\n * because the machine artifacts live in enforcement (Layer 0 + the per-rung\n * Layer 1 rule); this is the enumerable contract the explain track reports and\n * {@link postureVisibleRows} models.\n */\nexport const POSTURE_INJECTION_RULE: Record<AuthzPosture, string> = {\n PLATFORM_ADMIN:\n 'Layer 0 exemption where the object posture permits (private / platform-global / better-auth-managed) — crosses the tenant wall; org-scoped like TENANT_ADMIN on ordinary tenant business objects.',\n TENANT_ADMIN:\n 'All rows within the active organization (organization_id == ctx.tenantId); no ownership / depth / sharing narrowing.',\n MEMBER:\n 'Business RLS within the organization — ownership (owner / unit depth), the OWD baseline, and explicit sharing.',\n EXTERNAL:\n 'Explicitly shared rows ONLY — OWD baselines and sharing rules never apply; a misconfiguration can only shrink visibility, never widen it.',\n};\n\n/** Capability-grant evidence the posture derivation consumes (ADR-0095 D3). */\nexport interface PostureEvidence {\n /**\n * Holds the UNSCOPED platform-admin capability grant (`admin_full_access` →\n * `viewAllRecords`/`modifyAllRecords`) — the same evidence the superuser\n * bypass trusts. NOT a better-auth role.\n */\n isPlatformAdmin: boolean;\n /**\n * Holds the org-admin capability grant (`organization_admin`, tenant-scoped\n * `viewAllRecords`/`modifyAllRecords`). Provisioned from the better-auth\n * owner/admin role upstream, consumed here only as a held capability.\n */\n isTenantAdmin: boolean;\n}\n\n/**\n * Resolve the principal's posture rung from held capability grants (ADR-0095 D3).\n *\n * Returns `PLATFORM_ADMIN` | `TENANT_ADMIN` | `MEMBER`. It NEVER returns\n * `EXTERNAL`: no external principal type exists yet (the sharing chain has no\n * portal/guest-share concept — ADR-0095 W4). The `EXTERNAL` rung, its injection\n * rule, and its semantics are defined and test-locked ({@link postureVisibleRows},\n * {@link POSTURE_INJECTION_RULE}) so that when portal/external membership lands\n * (ADR-0093) the derivation gains an EXTERNAL branch HERE without the rung being\n * reinvented. `MEMBER` is the authenticated-principal floor.\n */\nexport function derivePosture(evidence: PostureEvidence): AuthzPosture {\n if (evidence.isPlatformAdmin) return 'PLATFORM_ADMIN';\n if (evidence.isTenantAdmin) return 'TENANT_ADMIN';\n return 'MEMBER';\n}\n\n// ── Reference visibility model (invariant lock, NOT enforcement) ─────────────\n\n/** A synthetic record for the ladder reference model. */\nexport interface LadderRow {\n id: string;\n /** The row's tenant. `undefined` = a non-tenant (platform-global) row. */\n organization_id?: string;\n /** The row's owner (drives the MEMBER ownership disjunct). */\n owner_id?: string;\n /**\n * Whether an OWD-derived source would admit this row for a member (public\n * baseline / criteria sharing). EXTERNAL deliberately ignores this field.\n */\n owdVisible?: boolean;\n /** User ids this row is EXPLICITLY shared to (the only EXTERNAL source). */\n sharedTo?: readonly string[];\n}\n\n/** The principal the reference model evaluates a rung for. */\nexport interface LadderPrincipal {\n userId: string;\n /** The principal's active organization (undefined for an unscoped principal). */\n organizationId?: string;\n}\n\nfunction isSharedTo(row: LadderRow, userId: string): boolean {\n return (row.sharedTo ?? []).includes(userId);\n}\n\n/** EXTERNAL rung: explicitly shared rows ONLY — never OWD, never org-wide. */\nfunction externalVisible(rows: readonly LadderRow[], p: LadderPrincipal): LadderRow[] {\n return rows.filter((r) => isSharedTo(r, p.userId));\n}\n\n/**\n * MEMBER rung: business RLS within the org — ownership OR OWD baseline OR\n * explicit sharing. Composed as `EXTERNAL ∪ (in-org ownership/OWD)` so the\n * EXTERNAL ⊆ MEMBER leg of the nesting invariant holds by construction.\n */\nfunction memberVisible(rows: readonly LadderRow[], p: LadderPrincipal): LadderRow[] {\n const shared = new Set(externalVisible(rows, p));\n return rows.filter(\n (r) =>\n shared.has(r) ||\n (r.organization_id === p.organizationId && (r.owner_id === p.userId || r.owdVisible === true)),\n );\n}\n\n/**\n * TENANT_ADMIN rung: all rows in the active organization. Composed as\n * `MEMBER ∪ (all in-org)` so MEMBER ⊆ TENANT_ADMIN holds by construction.\n */\nfunction tenantAdminVisible(rows: readonly LadderRow[], p: LadderPrincipal): LadderRow[] {\n const member = new Set(memberVisible(rows, p));\n return rows.filter((r) => member.has(r) || r.organization_id === p.organizationId);\n}\n\n/** PLATFORM_ADMIN rung: crosses the tenant wall — every row (⊇ TENANT_ADMIN trivially). */\nfunction platformAdminVisible(rows: readonly LadderRow[]): LadderRow[] {\n return [...rows];\n}\n\n/**\n * Reference model of the per-rung injection rule: the visible-row set a rung\n * would resolve to over `rows` for `principal`. Used to lock the ADR-0095 D2\n * invariants (strict nesting + EXTERNAL deny-by-default). NOT an enforcement\n * path — see the module header.\n */\nexport function postureVisibleRows(\n posture: AuthzPosture,\n rows: readonly LadderRow[],\n principal: LadderPrincipal,\n): LadderRow[] {\n switch (posture) {\n case 'PLATFORM_ADMIN':\n return platformAdminVisible(rows);\n case 'TENANT_ADMIN':\n return tenantAdminVisible(rows, principal);\n case 'MEMBER':\n return memberVisible(rows, principal);\n case 'EXTERNAL':\n return externalVisible(rows, principal);\n }\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * The `active` flag on the RBAC grant catalogues — `sys_permission_set` and\n * `sys_position` (ADR-0049 enforce-or-remove).\n *\n * Both objects ship a Deactivate action whose confirmation dialog promises, in\n * four locales, that access stops:\n *\n * > Deactivate this permission set? Existing assignments stay in place but stop\n * > granting access until re-activated.\n * > Deactivate this position? Users keep their assignment but the position stops\n * > granting permissions until re-activated.\n *\n * Nothing read the column, so the promise was false: deactivation changed a\n * badge in Setup and the assignments kept granting. Correctness lives at\n * RESOLUTION time — the same placement ADR-0091 chose for validity windows,\n * and for the same reason: a catalogue flag that is only honoured by a cleanup\n * job is an unenforced security property.\n *\n * ## Why the predicate is \"explicitly deactivated\", not \"explicitly active\"\n *\n * Absent means ACTIVE. Only a stored value that really reads false takes the\n * grant away. Two measured reasons, and they point the same way:\n *\n * 1. **Deployed data.** `active` carries `defaultValue: true`, but a row that\n * predates the column, arrived through a migration, or was projected with a\n * `fields` list that omitted it carries no value at all. Requiring `true`\n * would revoke every such row's grants the moment this lands — a silent\n * mass revocation nobody asked for, which is the opposite of the one thing\n * the Deactivate dialog promises. `isGrantActive` reads absent bounds as\n * unbounded for exactly this reason (ADR-0091 D2).\n * 2. **Storage shapes.** SQLite stores booleans as 1/0 and presents them back\n * as numbers unless the driver knows the column is boolean; the memory\n * driver round-trips real booleans; a JSON/text column can hand back\n * `'false'`. `row.active === false` alone therefore misses the deactivated\n * row on the primary driver — an enforcement hole shaped exactly like the\n * bug this predicate closes. Pushing `active: true` into a driver `where`\n * has the same problem from the other side AND drops the NULL rows of (1),\n * so the filter runs in memory over rows the resolver already fetched:\n * zero new queries, identical answer on every driver.\n *\n * The false-set below is closed on purpose. An unrecognised value (a stray\n * string, an object) is NOT a deactivation — nothing writes one, and inventing\n * a revocation out of junk data is the failing-open-into-a-lockout direction.\n */\n\n/** The stored spellings of a deactivated row, across every driver in the tree. */\nconst DEACTIVATED_VALUES: readonly unknown[] = [false, 0, '0', 'false'];\n\n/** A catalogue row that may carry the `active` flag (`sys_permission_set`, `sys_position`). */\nexport interface ActivatableRow {\n active?: unknown;\n}\n\n/**\n * True unless the row carries an `active` column that is explicitly OFF.\n *\n * The ONE predicate every reader of `sys_permission_set.active` /\n * `sys_position.active` uses, so the resolver that enforces the flag and the\n * break-glass guard that simulates a write to it can never disagree about what\n * \"deactivated\" means.\n */\nexport function isRowActive(row: ActivatableRow | null | undefined): boolean {\n if (!row) return false;\n const value = (row as { active?: unknown }).active;\n if (value === undefined || value === null) return true;\n return !DEACTIVATED_VALUES.includes(value);\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * assembleExecutionContext — the SINGLE assembly of an inbound request's\n * {@link ExecutionContext}, shared by every transport entry point.\n *\n * `resolveAuthzContext` (next door) already made AUTHORIZATION resolution\n * single-sourced. The step AFTER it — turning the resolved\n * {@link ResolvedAuthzContext} into the `ExecutionContext` envelope that\n * reaches enforcement — stayed hand-written per transport, and that duplication\n * produced a measured defect family:\n *\n * - **#6071 — field drift.** The REST copy never set `principalKind`, so every\n * enforcement judgment reading it (explain's guest⇒EXTERNAL floor, the\n * security plugin's agent baseline, the perf-disclosure gate) was silently\n * never-true on that face.\n * - **#6206 / #6551 — dropped fields.** The share-link copies omitted\n * `accessible_org_ids`, and the `group` posture's Layer 0 wall reads it\n * directly: real 403s for callers who should have been let through. Both\n * surfaces were since converted to pass the WHOLE envelope through.\n *\n * Both defects are the same shape: a field exists on `ExecutionContext`, one\n * copy carries it, another silently does not. This module makes that shape\n * unrepresentable by CLOSING the field set with a type\n * ({@link ExecutionContextEntryFields}) — every field a transport entry point\n * decides must be decided HERE, explicitly, and a new `ExecutionContext` field\n * fails to compile until it is either assembled or listed as\n * non-entry-resolved.\n *\n * ## Two named entries — the anonymous face is genuinely divergent (#6216)\n *\n * The maintainer ruling of 2026-08-08 on #6216 (Option A) settled the one\n * question that blocked convergence: what an anonymous request yields.\n *\n * - {@link assembleExecutionContext} — the DEFAULT, fail-closed entry. No\n * resolved principal → `undefined`, and the surface answers 401. This is the\n * REST face's contract, unchanged.\n * - {@link assembleExecutionContextOrGuest} — the EXPLICIT guest entry. No\n * resolved principal → a first-class guest envelope\n * (`principalKind: 'guest'`, `positions: ['guest']`), which the runtime /\n * MCP dispatcher has always produced and whose consumers are live\n * (`plugin-security/explain-engine.ts`: guest ⇒ `EXTERNAL` posture). A\n * surface adopts this entry ONLY when its product semantics serve anonymous\n * principals.\n *\n * Neither surface's runtime behaviour changes. What changes is that the\n * divergence is now NAMED API rather than drift — and the same is true of the\n * per-face values below (`accessToken`, `oauth`, `localization`): they are\n * REQUIRED inputs, so a face cannot silently omit one, and what a face chooses\n * to withhold it withholds on the record.\n *\n * Options B (guest everywhere — turns REST's anonymous 401s into authz-shaped\n * denials and makes anonymous-serving the DEFAULT posture of a new surface) and\n * C (no-ctx everywhere — deletes the guest principal, the `guest` position and\n * explain's `EXTERNAL` floor) were both considered and rejected: each breaks a\n * live consumer side.\n */\n\nimport type { ExecutionContext } from '@objectstack/spec/kernel';\n\nimport type { AuthGate } from './auth-gate.js';\nimport type { ResolvedAuthzContext } from './resolve-authz-context.js';\n\n/**\n * `ExecutionContext` fields a transport ENTRY POINT does not resolve — they are\n * per-operation flags, engine internals, or attribution supplied further down\n * the stack. Listing one here is a deliberate, reviewable statement that no\n * request-identity resolution produces it; everything NOT listed is part of the\n * closed entry set below and must be assembled.\n *\n * - `actor` / `attributedUserId` — audit attribution, set by the host or the\n * hook layer (ADR-0014 D2, #4586), never by identity resolution.\n * - `rlsMembership` — engine-side RLS scoping cache.\n * - `transaction` / `traceId` — per-operation handles.\n * - `flowRunId`, `skipTriggers`, `skipAutomations`, `seedReplay`,\n * `skipStateMachine`, `preserveAudit` — per-write behaviour flags,\n * server-constructed at the call site.\n */\ntype NonEntryExecutionContextField =\n | 'actor'\n | 'attributedUserId'\n | 'rlsMembership'\n | 'transaction'\n | 'traceId'\n | 'flowRunId'\n | 'skipTriggers'\n | 'skipAutomations'\n | 'seedReplay'\n | 'skipStateMachine'\n | 'preserveAudit';\n\n/**\n * The CLOSED field set every transport entry point must decide. Derived from\n * `ExecutionContext` itself, so adding a field to `ExecutionContextSchema`\n * widens this union automatically — and the assembly below stops compiling\n * until the new field is either assembled or declared non-entry-resolved.\n */\nexport type EntryExecutionContextField = Exclude<\n keyof ExecutionContext,\n NonEntryExecutionContextField\n>;\n\n/**\n * One value per closed field. Every key is REQUIRED (`-?`) while the VALUE may\n * be `undefined` — the decision may not be omitted, only made explicitly. This\n * is the type that makes the #6071 drift class unrepresentable.\n */\nexport type ExecutionContextEntryFields = {\n [K in EntryExecutionContextField]-?: ExecutionContext[K];\n};\n\n/**\n * Emission order of the assembled envelope, and a second, independent\n * exhaustiveness bite: `satisfies` rejects a stale name, and\n * `_ENTRY_FIELDS_EXHAUSTIVE` below rejects a missing one.\n */\nexport const ENTRY_EXECUTION_CONTEXT_FIELDS = [\n 'positions',\n 'permissions',\n 'systemPermissions',\n 'isSystem',\n 'principalKind',\n 'onBehalfOf',\n 'audience',\n 'userId',\n 'tenantId',\n 'email',\n 'accessToken',\n 'tabPermissions',\n 'posture',\n 'authGate',\n 'org_user_ids',\n 'accessible_org_ids',\n 'oauthScopes',\n 'timezone',\n 'locale',\n 'currency',\n] as const satisfies readonly EntryExecutionContextField[];\n\n/**\n * Compile-time proof that {@link ENTRY_EXECUTION_CONTEXT_FIELDS} covers the\n * whole closed set. A new `ExecutionContext` field that is neither assembled\n * nor listed as non-entry-resolved makes this `never`, and the initializer\n * below fails to compile.\n */\ntype MissingEntryField = Exclude<\n EntryExecutionContextField,\n (typeof ENTRY_EXECUTION_CONTEXT_FIELDS)[number]\n>;\nconst _ENTRY_FIELDS_EXHAUSTIVE: [MissingEntryField] extends [never] ? true : never = true;\nvoid _ENTRY_FIELDS_EXHAUSTIVE;\n\n/**\n * OAuth 2.1 access-token provenance. Reaches the assembler from the `/mcp`\n * dispatch door ALONE (`acceptOAuthAccessToken`) — OAuth bearers carry coarse\n * tool-family scopes enforced at MCP tool dispatch, so honouring them on\n * another surface would bypass that scope model entirely.\n */\nexport interface OAuthTokenProvenance {\n /** The human `sub` the token was issued for. */\n userId: string;\n /** Granted scopes, surfaced on the envelope so MCP can narrow tool families. */\n scopes: string[];\n /**\n * The authorized client (`azp`). Present ⇒ this is an AI AGENT acting on\n * behalf of the human `userId`; absent ⇒ the token names no client and the\n * principal stays human (the scopes are still surfaced).\n */\n clientId?: string;\n /**\n * The agent's OWN permission CEILING, derived from {@link scopes} by the door\n * that speaks the OAuth scope vocabulary\n * (`scopesToAgentPermissionSets`, `@objectstack/spec/ai`).\n *\n * Interpreted THERE and not here on purpose: the scope vocabulary is\n * MCP-domain knowledge and `@objectstack/core` is the microkernel — it should\n * not acquire a dependency on the AI subdomain (concretely, every package\n * whose test config aliases `@objectstack/core` to its source would then have\n * to resolve `@objectstack/spec/ai` too, down to `driver-memory`). What the\n * ceiling REPLACES on the envelope is decided below, once, for every face —\n * and that is the part that drifted.\n */\n scopePermissions: string[];\n /**\n * Whether the token carries the user's consent to let this agent invoke\n * actions on their behalf — the `actions:execute` scope\n * (`MCP_OAUTH_SCOPE_ACTIONS`), evaluated at the same door for the same\n * reason.\n */\n delegatesActions: boolean;\n}\n\n/** Reference localization for an authenticated principal (@see resolveLocalizationContext). */\nexport interface EntryLocalization {\n timezone?: string;\n locale?: string;\n currency?: string;\n}\n\n/**\n * Everything the shared assembly needs. Every key is REQUIRED so a face cannot\n * silently omit one — a face that has no value for an input passes `undefined`\n * on the record, which is what turns the remaining divergences into named API.\n */\nexport interface ExecutionContextAssemblyInput {\n /** The shared authorization envelope (@see resolveAuthzContext). */\n authz: ResolvedAuthzContext;\n /**\n * OAuth access-token provenance, or `undefined` on a face that does not\n * accept one. Only the `/mcp` dispatch door passes a value; REST passes\n * `undefined` — which is why `principalKind: 'agent'`, `onBehalfOf` and\n * `oauthScopes` are not representable there.\n */\n oauth: OAuthTokenProvenance | undefined;\n /**\n * Resolved reference localization, or `undefined` when the face resolved\n * none (anonymous requests have no scope to resolve against).\n */\n localization: EntryLocalization | undefined;\n /**\n * The request's OWN locale preference (`Accept-Language`, `?locale`, …),\n * which wins over the workspace default; `undefined` when the caller\n * expresses none. Each face extracts it its own way — the PRECEDENCE lives\n * here so the two cannot disagree about it (#3957).\n */\n requestLocale: string | undefined;\n /**\n * The session bearer to carry on the envelope, surfaced to hooks as\n * `session.accessToken` (`objectql/engine.ts` `buildSession`,\n * `spec/data/hook.zod.ts`).\n *\n * A NAMED per-face divergence, preserved deliberately (#6216): the runtime /\n * MCP dispatcher passes `authz.accessToken`; the REST face has never carried\n * it and passes `undefined`, because widening a published hook surface to\n * expose the session token on a second transport is a product decision, not a\n * refactor. Being a required input, the choice is on the record at each face\n * instead of being an omission nobody can see.\n */\n accessToken: string | undefined;\n /**\n * [ADR-0069] The AUTHENTICATION-policy gate posture resolved for this\n * request's session (expired password / enforced MFA), or `undefined` when\n * the face resolves none — normalize a session user through\n * `normalizeAuthGate` rather than copying its `authGate` verbatim.\n *\n * A NAMED per-face divergence, on the same footing as {@link accessToken}\n * (#7280):\n *\n * - the **REST** face lifts it onto the envelope, because that is where its\n * consumer reads it (`RestServer.enforceAuth` → `403 { code, message }`);\n * - the **runtime / MCP dispatcher** passes `undefined`, because it enforces\n * the same ADR-0069 gate at its OWN seam (`HttpDispatcher.enforceAuthGate`\n * re-reads the session and calls `evaluateAuthGate` there) and never reads\n * `context.authGate` — carrying it would be a second, unread copy.\n *\n * Until #7280 declared it, this posture reached the envelope through an\n * `as any` spread AFTER assembly, which put it outside this closed set\n * entirely — the blind spot the set exists to remove.\n */\n authGate: AuthGate | undefined;\n}\n\n/** Drop `undefined`-valued keys, emitting in the closed set's declared order. */\nfunction emit(fields: ExecutionContextEntryFields): ExecutionContext {\n const ctx: Record<string, unknown> = {};\n for (const key of ENTRY_EXECUTION_CONTEXT_FIELDS) {\n const value = fields[key];\n if (value !== undefined) ctx[key] = value;\n }\n return ctx as ExecutionContext;\n}\n\n/**\n * Decide every field of the closed set. The one branch is the principal's\n * PROVENANCE (agent / human / guest); everything else is a single expression\n * shared by every face.\n */\nfunction entryFields(\n input: ExecutionContextAssemblyInput,\n anonymous: boolean,\n): ExecutionContextEntryFields {\n const { authz, oauth, localization, requestLocale, accessToken, authGate } = input;\n\n // [ADR-0090 D10 — agent principal] An OAuth access token naming an authorized\n // client (`azp`) is an AI agent acting ON BEHALF OF the human `sub`. The\n // agent's OWN grants are its scope-derived CEILING, NOT the user's — so the\n // user-derived positions/permissions/systemPermissions are REPLACED with that\n // ceiling. The human stays the delegator (`onBehalfOf`), and the security\n // engine intersects the two so the agent can never exceed EITHER its\n // consented scope OR the user's own reach (confused-deputy prevention).\n // `userId` stays the human so owner-stamping and `current_user.*` RLS resolve\n // to them.\n const agent = !anonymous && oauth?.clientId ? oauth : undefined;\n\n return {\n // [ADR-0090 D9/D10] Principal taxonomy at the HTTP entry: a session-backed\n // request is a human principal; a sessionless one is a guest, holding the\n // built-in `guest` position implicitly and exclusively. Internal engine\n // calls that construct bare contexts never pass through here, so the\n // security plugin's empty-context skip path keeps its meaning.\n positions: agent ? [] : anonymous ? ['guest'] : authz.positions,\n permissions: agent ? agent.scopePermissions : authz.permissions,\n // [ADR-0090 D10] System capabilities on the agent principal gate business\n // ACTION invocation (`actionPermissionError` reads `ctx.systemPermissions`)\n // — a door SEPARATE from the object CRUD/FLS/RLS intersection, which is\n // driven by the resolved ceiling SETS (they carry no caps, so cap-gated\n // OBJECT access stays denied to the agent regardless of this line). The\n // `actions:execute` scope IS the user's consent to let this agent invoke\n // actions on their behalf; without it the agent holds none.\n systemPermissions: agent\n ? agent.delegatesActions\n ? (authz.systemPermissions ?? [])\n : []\n : authz.systemPermissions,\n isSystem: false,\n principalKind: agent ? 'agent' : anonymous ? 'guest' : 'human',\n onBehalfOf: agent ? { userId: authz.userId!, principalKind: 'human' } : undefined,\n // [ADR-0090 D10/D11 — P1 shape] No transport resolves an external\n // (portal/partner) audience yet; `undefined` reads as 'internal'. Named\n // here rather than excluded so the gap is visible in the closed set instead\n // of being invisible outside it — when an external principal type lands,\n // this is the line that must change, on every face at once.\n audience: undefined,\n userId: authz.userId,\n tenantId: authz.tenantId,\n email: authz.email,\n accessToken,\n tabPermissions: authz.tabPermissions,\n // [ADR-0095 D2 / #2947] The derived posture rung, carried so every\n // transport presents enforcement the SAME value. Present only for an\n // authenticated principal (guest → absent).\n posture: authz.posture,\n // [ADR-0069 / #7280] The AUTHENTICATION-policy gate, carried for the seam\n // that reads it off the envelope (REST's `enforceAuth`). Anonymous → never:\n // a guest has no authenticated session for a policy gate to attach to, so\n // \"gated guest\" is not a state this entry can emit even if a face passed\n // one.\n authGate: anonymous ? undefined : authGate,\n /** Fellow-org user IDs for RLS scoping of identity tables. */\n org_user_ids: authz.org_user_ids,\n // [ADR-0105 D2] The caller's org access set — the `group` posture's Layer 0\n // wall reads it directly, so every transport must carry it (#6206).\n accessible_org_ids: authz.accessible_org_ids,\n // OAuth provenance: surface the token's granted scopes so the MCP\n // dispatcher can narrow the exposed tool families (undefined for every\n // other provenance = not scope-limited).\n oauthScopes: oauth && authz.userId === oauth.userId ? oauth.scopes : undefined,\n // Anonymous → no localization (no scope to resolve against); the engine\n // default stands. [#3957] The request's OWN language preference wins over\n // the workspace default, so a rejection message is not rendered in English\n // beside the Chinese label of the very field it names.\n timezone: anonymous ? undefined : localization?.timezone,\n locale: anonymous ? undefined : (requestLocale ?? localization?.locale),\n currency: anonymous ? undefined : localization?.currency,\n };\n}\n\n/**\n * The DEFAULT, fail-closed entry (#6216 Option A). An unauthenticated request\n * yields NO context — the surface answers 401. Every surface uses this one\n * unless serving anonymous principals is part of its product semantics.\n */\nexport function assembleExecutionContext(\n input: ExecutionContextAssemblyInput,\n): ExecutionContext | undefined {\n if (!input.authz.userId) return undefined;\n return emit(entryFields(input, false));\n}\n\n/**\n * The EXPLICIT guest entry (#6216 Option A). An unauthenticated request becomes\n * a first-class guest principal — `principalKind: 'guest'`, `positions:\n * ['guest']` — which enforcement consumers read today\n * (`plugin-security/explain-engine.ts`: guest ⇒ `EXTERNAL` posture).\n *\n * Adopt this ONLY on a surface that genuinely serves anonymous principals: the\n * built-in `guest` position is the declared vocabulary for \"what anonymous may\n * do\", and handing a guest envelope to a surface that previously answered 401\n * converts an authentication failure into an authorization evaluation.\n */\nexport function assembleExecutionContextOrGuest(\n input: ExecutionContextAssemblyInput,\n): ExecutionContext {\n return emit(entryFields(input, !input.authz.userId));\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * ADR-0069 — authentication-policy session gate.\n *\n * Some auth policies (password expiry, enforced MFA) must block an\n * authenticated user from PROTECTED RESOURCES until they remediate, while\n * still letting them reach the auth endpoints (change-password, two-factor\n * enrollment, sign-out) and a few UI-bootstrap reads.\n *\n * The posture is computed ONCE, in the auth `customSession` enrichment, and\n * attached to the session user as `user.authGate = { code, message }`. The\n * transport seams (REST middleware, dispatcher) then call\n * {@link evaluateAuthGate} to decide whether THIS request is blocked. Keeping\n * the allow-list + decision in one pure function means the seams can never\n * drift on what is blocked.\n */\n\nimport type { ExecutionContext } from '@objectstack/spec/kernel';\n\n/**\n * The gate posture, DERIVED from its declaration on `ExecutionContextSchema`\n * (`packages/spec/src/kernel/execution-context.zod.ts`) rather than restated\n * here (#7280).\n *\n * It was a hand-written interface while the envelope field was undeclared, so\n * the two could have drifted with nothing to catch it — the exact class of\n * defect the closed entry field set (#6216) exists to make unrepresentable.\n * One declaration, one type.\n */\nexport type AuthGate = NonNullable<ExecutionContext['authGate']>;\n\n/** Message used when a session's gate names a `code` but no usable `message`. */\nconst DEFAULT_AUTH_GATE_MESSAGE = 'Access is blocked by an authentication policy.';\n\n/**\n * Normalize the `authGate` a better-auth session user carries into the shape\n * `ExecutionContextSchema` declares — or `null` when there is no gate.\n *\n * The session user crosses an external boundary as `any`, so this is where the\n * declared contract is actually met: a gate naming no string `code` is not a\n * gate, and a missing/blank `message` is filled with the default rather than\n * riding onto the envelope (and into a `403` body) as `undefined`. Both\n * consumers normalize HERE, at the one producer, instead of tolerating a loose\n * shape downstream: {@link evaluateAuthGate} for the seams that decide per\n * path, and REST's `computeExecCtx` for the seam that lifts the posture onto\n * the execution context.\n */\nexport function normalizeAuthGate(sessionUser: any): AuthGate | null {\n const gate = sessionUser?.authGate;\n if (!gate || typeof gate.code !== 'string') return null;\n return {\n code: gate.code,\n message:\n typeof gate.message === 'string' && gate.message ? gate.message : DEFAULT_AUTH_GATE_MESSAGE,\n };\n}\n\n// Endpoints a gated user MUST still reach to remediate or bootstrap the\n// remediation UI. Matched against the request path (query stripped). Covers\n// both REST (`/api/v1/auth/…`) and dispatcher (`/auth/…`) path shapes.\nconst ALLOW_PREFIXES = ['/api/v1/auth/', '/api/auth/', '/auth/'];\nconst ALLOW_SUFFIXES = ['/health', '/ready', '/discovery', '/me/apps', '/me/localization'];\n\n/** True when `path` is exempt from the auth gate (auth + remediation + health). */\nexport function isAuthGateAllowlisted(rawPath: string | undefined | null): boolean {\n if (!rawPath) return true;\n // Strip query + trailing slashes WITHOUT a regex (avoids ReDoS on a\n // path of many '/'). char 47 = '/'.\n let path = rawPath.split('?')[0] || '/';\n let end = path.length;\n while (end > 1 && path.charCodeAt(end - 1) === 47) end--;\n path = path.slice(0, end) || '/';\n // Any path with an `/auth/` segment is an auth endpoint (covers project-\n // scoped mounts like `/api/v1/environments/:env/auth/...`).\n if (path.includes('/auth/')) return true;\n for (const p of ALLOW_PREFIXES) {\n if (path.startsWith(p) || path === p.replace(/\\/$/, '')) return true;\n }\n for (const s of ALLOW_SUFFIXES) {\n if (path.endsWith(s)) return true;\n }\n return false;\n}\n\n/**\n * Returns the active gate when `sessionUser` carries an `authGate` AND `path`\n * is not allow-listed; otherwise null. Anonymous users (no `authGate`) and\n * allow-listed paths always pass.\n */\nexport function evaluateAuthGate(sessionUser: any, path: string): AuthGate | null {\n const gate = normalizeAuthGate(sessionUser);\n if (!gate) return null;\n if (isAuthGateAllowlisted(path)) return null;\n return gate;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * #2567 — the single anonymous-deny decision, shared by every HTTP seam.\n *\n * ADR-0056 D2 made the platform deny anonymous callers by default. Phase 1 gated\n * each surface separately, with every seam hand-rolling the same\n * `!userId && !isSystem → 401` check. This centralises that DECISION into one\n * pure, tested function — the exact pattern {@link ./auth-gate.ts} established\n * for the ADR-0069 auth-policy gate: keeping the decision in one function means\n * the seams can never drift on who is denied.\n *\n * ## Do NOT keep a route list here (#10835)\n *\n * This paragraph used to name the Phase-1 surfaces inline — \"REST `/data`,\n * dispatcher `/graphql` + `/meta`, raw-hono `/data`\" — and TWO of those three\n * outlived the routes they named:\n *\n * - **`/graphql` is gone.** Removed with the GraphQL surface itself\n * (`packages/runtime/src/http-dispatcher.ts`: \"/graphql removed — GraphQL is\n * not in the product plan\", #2462 follow-on). No `handleGraphQL` survives\n * anywhere in `packages/runtime`, and `/graphql` is now ordinary catch-all\n * traffic.\n * - **raw-hono `/data` is gone.** Those routes were deleted as a duplicate\n * surface in v17 (#4073); `packages/adapters/hono` mounts only `/discovery`,\n * `/auth/*` and the terminal `${prefix}/*` catch-all.\n *\n * A stale list here is worse than no list, because the seams it names are the\n * thing a reader opens this file to learn: a dead route reads as a live surface.\n * That is not hypothetical — the `handleGraphQL` test doubles removed alongside\n * this edit had become the only non-CHANGELOG hits in `packages/**`, and so read\n * to the next author grepping the name as evidence the runtime still had the\n * method.\n *\n * The live enumeration is mechanical, so defer to it rather than restating it:\n * `packages/qa/dogfood/test/authz-conformance.matrix.ts` and its companion\n * `authz-conformance.test.ts` ratchet a CURATED table of HTTP/transport entry\n * points out of source — deleting a `shouldDenyAnonymous` call makes its row\n * STALE, a new ungated route in one of those files is UNCLASSIFIED, and either\n * breaks CI. Read the ratchet, or read the call sites.\n *\n * ## The `requireAuth` opt-out is gone (#3963)\n *\n * This used to take a `requireAuth` posture and no-op when it was falsy, so a\n * deployment could open its ENTIRE data plane with one config key. That key is\n * retired: auth is a kernel concern, and every surface that legitimately serves\n * a caller with no session derives its own narrow authorization from a\n * DECLARATION instead of from the deployment posture —\n *\n * - control plane (`/auth/*`, `/health`, `/ready`, `/discovery`, the ADR-0069\n * remediation paths) → the {@link isAuthGateAllowlisted} allowlist, below;\n * - public form submission → `publicFormGrant` (ADR-0056 Option A), derived\n * from the form view's own declaration;\n * - share links → the capability token, validated then read as SYSTEM;\n * - a `book.audience: 'public'` read → the ADR-0046 §6.7 audience gate (#3963);\n * - MCP → an OAuth token or API key, never anonymous.\n *\n * Those run UPSTREAM of this function and set the execution context (a `userId`,\n * or `isSystem`) or bypass the seam entirely, so this only ever inspects the\n * already-resolved context. Nothing else gets in.\n */\n\nimport { isAuthGateAllowlisted } from './auth-gate.js';\n\n/** HTTP status every seam returns for an anonymous-denied request. */\nexport const ANONYMOUS_DENY_STATUS = 401 as const;\n/** Stable machine code (mirrors the REST `enforceAuth` seam). ADR-0112: SCREAMING, a `StandardErrorCode` member. */\nexport const ANONYMOUS_DENY_CODE = 'UNAUTHENTICATED' as const;\n/** Human-facing message. */\nexport const ANONYMOUS_DENY_MESSAGE = 'Authentication is required to access this endpoint.';\n/**\n * The **REST seam's** 401 body — flat `{ error, code, message }`. NOT the\n * platform's only one; see the two-envelope table below before you reuse this\n * shape.\n *\n * Two consumers write it verbatim, both flat-family seams: `@objectstack/rest`'s\n * `enforceAuth` (`rest-server.ts` —\n * `res.status(ANONYMOUS_DENY_STATUS).json(ANONYMOUS_DENY_BODY)`), which owns the\n * `/data/*` and `/meta` surfaces, and `@objectstack/runtime`'s\n * `mountRouteOnServer` (`dispatcher-plugin.ts` — the endpoint-route 401 arm,\n * #9823), which answers declared routes mounted on the HTTP server.\n *\n * ## Two live envelopes, one denial (#5632)\n *\n * Every HTTP seam shares the DECISION ({@link shouldDenyAnonymous}) and the\n * semantics ({@link ANONYMOUS_DENY_STATUS} / {@link ANONYMOUS_DENY_CODE} /\n * {@link ANONYMOUS_DENY_MESSAGE}). What differs is the **wrapper**:\n *\n * - **REST seam** — `@objectstack/rest` `enforceAuth`, this constant, verbatim:\n * `{ error: 'UNAUTHENTICATED', code: 'UNAUTHENTICATED', message: '…' }`.\n * The machine code lives in the top-level `code` key — the same documented\n * key every other REST error family answers (#9487, maintainer-ruled\n * ADDITIVE: `error` keeps carrying the code value it always has, so no\n * existing reader breaks). There is no `success` key and no nesting.\n * - **Dispatcher seams** — the five runtime domains `domains/ai.ts`,\n * `domains/meta.ts`, `domains/security.ts`, `domains/actions.ts` and\n * `domains/automation.ts` do NOT use this constant. Each calls\n * `deps.error(ANONYMOUS_DENY_MESSAGE, ANONYMOUS_DENY_STATUS, { code: ANONYMOUS_DENY_CODE })`,\n * so the wire body is the dispatcher's standard wrapper:\n * `{ success: false, error: { code, message, httpStatus } }`.\n *\n * Both shapes are **live and sanctioned** — ADR-0112's 2026-07-30 amendment\n * (#4007) records the flat and wrapped envelopes as the two live ones, and\n * assigns retiring one of them to the envelope-convergence line (#3843 family).\n * Converging them is a breaking wire change; it is not this module's to make,\n * and this constant must not be read as if it had already happened. The #9487\n * `code` key does NOT settle that question either way (ADR-0112 D5 stays\n * open): it aligns the flat family to the `{ error, code }` shape the other\n * flat REST error families already answer, without moving or removing a key.\n *\n * ## Reading this from a consumer (human or AI author)\n *\n * Read the envelope the seam you called DECLARES — flat from `/data` + `/meta`,\n * wrapped from a dispatcher-mounted surface. Do **not** write a tolerant\n * `body.error?.code ?? body.error` chain that swallows both: that fallback is\n * precisely where an envelope regression hides, and this docstring claiming to\n * be \"the single shape every seam returns\" is what used to invite it (#5632).\n *\n * Both shapes are pinned against a real booted showcase by\n * `packages/qa/dogfood/test/showcase-anonymous-deny-surfaces.dogfood.test.ts`,\n * which classifies every anonymous 401 into exactly one of the two families and\n * fails on a third dialect or on a seam that changes family.\n */\nexport const ANONYMOUS_DENY_BODY = {\n error: ANONYMOUS_DENY_CODE,\n code: ANONYMOUS_DENY_CODE,\n message: ANONYMOUS_DENY_MESSAGE,\n} as const;\n\nexport interface AnonymousDenyInput {\n /** Resolved caller id, if any. */\n userId?: string | null;\n /** Internal system context (never set on inbound HTTP; cannot be forged). */\n isSystem?: boolean;\n /** HTTP method — `OPTIONS` (CORS preflight) always passes. */\n method?: string | null;\n /**\n * OPTIONAL request path. When a NON-EMPTY string, a control-plane path\n * (auth / health / ready / discovery — see {@link isAuthGateAllowlisted}) is\n * exempt. Body-routed seams (GraphQL) have no meaningful path and pass\n * `undefined`; see the guard below for why that is load-bearing.\n */\n path?: string | null;\n}\n\n/**\n * True when the request MUST be rejected with 401. The one decision every HTTP\n * seam shares.\n */\nexport function shouldDenyAnonymous(input: AnonymousDenyInput): boolean {\n if (typeof input.method === 'string' && input.method.toUpperCase() === 'OPTIONS') {\n return false; // CORS preflight\n }\n if (input.userId || input.isSystem) return false; // authenticated / system\n // Control-plane exemption — ONLY for a real, non-empty path.\n //\n // ⚠️ `isAuthGateAllowlisted(undefined)` returns `true` (it treats \"no path\"\n // as allow-listed for the auth-gate's purposes). A body-routed seam such as\n // GraphQL has no meaningful request path; if it passed `undefined` straight\n // through, the allowlist would exempt EVERY anonymous query and silently\n // reopen exactly the hole #2567 closes. The non-empty guard is mandatory.\n if (typeof input.path === 'string' && input.path.length > 0 && isAuthGateAllowlisted(input.path)) {\n return false;\n }\n return true;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * ADMIN_STANDING_SURFACE — what `resolveAuthzContext` READS when it decides\n * who is an administrator, declared beside the resolver that reads it.\n *\n * ## Why this file exists (#8734)\n *\n * `plugin-auth`'s break-glass guard (`last-admin-guard.ts`, ADR-0024 D5.2)\n * decides whether a pending write can empty the administrator population by\n * testing the payload against three standing-key lists — `MEMBER_STANDING_KEYS`,\n * `GRANT_STANDING_KEYS`, `PERMISSION_SET_STANDING_KEYS`. Those lists are not an\n * independent design artifact: they are a CACHE of the columns this resolver\n * consumes. A payload touching none of them is skipped without any reads, so a\n * column this resolver starts reading and the guard's list omits is a write\n * class the guard silently stops judging — the one write class that can lock an\n * installation out of its own administration, with no in-product recovery.\n *\n * Nothing bound the two together. The correspondence was carried by a comment,\n * and it had already gone false once: #6084 wrote, beside the list, that\n * everything a permission-set write touches other than `name` — naming `active`\n * explicitly — is invisible to \"who is an administrator\". That was true when\n * written. #8613 made `active` a resolution-time predicate (a DEACTIVATED\n * `admin_full_access` set confers nothing, §6b below), and the sentence became\n * false. It was caught by one agent reading the comment closely enough to\n * notice it contradicted the code being written. Nothing mechanical would have\n * caught it: the guard's own tests stay green, because the guard is simply never\n * consulted for that write.\n *\n * ## What this file is, and what it is NOT\n *\n * It is a MEASUREMENT, not a wish. Its column lists are asserted equal to what\n * the resolver actually reads at runtime, by\n * `admin-standing-surface.test.ts`, which drives the real\n * `resolveAuthzContext` over a recording engine and collects every property\n * access and every `where` key per table. That is deliberate: a hand-written\n * list of \"columns the derivation reads\" is the same artifact as the comment\n * that went stale, one indirection along. Observation is also the only reading\n * that survives the derivation moving INTO a helper — `active` is read by\n * `isRowActive(ps)` and the window bounds by `isGrantActive(row, now)`, neither\n * of which names a column at the resolver's own call site.\n *\n * It is NOT a projection the resolver consumes. `ql.find` here returns whole\n * rows and the reads are ordinary property accesses on untyped rows, so nothing\n * in this file can FORCE the resolver to read only what it declares. The force\n * comes from the observation test: add a read, and this declaration is red\n * until it is updated; update this declaration, and `plugin-auth`'s\n * correspondence test is red until every new column is either in a standing-key\n * list or explicitly excluded with a reason.\n *\n * ## Reading the entries\n *\n * Every table this resolution path reads is listed — including the ones that\n * CANNOT confer administrator standing, each with the reason it cannot. That is\n * the table-level half of the same guarantee: a resolver that starts deriving\n * administrator standing from a new table would otherwise be invisible to a\n * column-set comparison, because the new table appears in neither side's list.\n *\n * ## ⚠️ Tables are no longer the whole surface (#11663 L2)\n *\n * Since the platform-admin re-anchor's core leg, one input to the administrator\n * derivation is NOT a table at all: the deployment's declared administrator\n * list, read from the environment on every resolution\n * (`security/platform-admin.ts`). A file that listed only tables would go on\n * being perfectly accurate about the tables while silently claiming the\n * derivation reads nothing else — the same shape as the stale comment this file\n * replaced, one level up. {@link ADMIN_STANDING_NON_TABLE_INPUTS} is the place\n * that says so, and it is deliberately a SEPARATE export rather than a\n * pseudo-row in the table map: the map is compared for equality against\n * observed table reads, and a pseudo-row would have to be excluded from that\n * comparison by name, which is exactly the kind of special case that rots.\n */\n\n/** How a table this resolver reads relates to \"who is an administrator\". */\nexport interface AdminStandingTable {\n /**\n * `derives` — a write to this table can change the administrator population,\n * so `last-admin-guard.ts` must carry a standing-key list for it.\n * `reads-only` — this resolver reads the table for something else entirely.\n */\n readonly role: 'derives' | 'reads-only';\n /** Why the row above is the right classification. Prose, but pinned to a measured table. */\n readonly reason: string;\n /**\n * Every column this resolver reads on the table — property accesses and\n * `where` keys alike, in every spelling it actually touches. Declared for\n * `derives` tables only; asserted equal to the observed set.\n */\n readonly columns?: readonly string[];\n}\n\n/**\n * The measured read surface of the administrator derivation.\n *\n * Scope, stated so the gate cannot be read as claiming more than it measures:\n * this is the SESSION/user-id resolution path — `resolveAuthzContext` with a\n * principal, and therefore all of `resolveUserAuthzGrants`. The API-key\n * ADMISSION path (`resolveApiKeyAdmission`) is outside it on purpose: it\n * authenticates a principal and seeds `permissions` with the key's scopes, and\n * confers no administrator standing of its own — `hasPlatformAdminGrant` is set\n * from a `sys_permission_set` row reached through an UNSCOPED\n * `sys_user_permission_set` grant (§6b) or from the deployment config matched\n * against the caller's own STORED `sys_user` row (§6b-config), never from a\n * scope string and never from the caller-seedable `grants.email`.\n */\nexport const ADMIN_STANDING_SURFACE: Readonly<Record<string, AdminStandingTable>> = {\n sys_permission_set: {\n role: 'derives',\n reason:\n 'The row `admin_full_access` is resolved BY NAME from (§6b) — `platform_admin` is the '\n + \"POSITION that row derives, not the row's own name. Renaming it, deleting it or switching \"\n + 'it off (ADR-0049 `active`, read here since #8613) un-makes every GRANT-derived platform '\n + 'admin at once, with no identity table touched. ⚠️ It does NOT un-make a CONFIG-derived '\n + 'one (§6b-config, #11970): that route sets the same standing from '\n + \"`ADMIN_FULL_ACCESS_CAPABILITIES` in `@objectstack/spec` and matches the caller's own \"\n + 'stored `sys_user` row, so it touches an identity table and never reads this one. With '\n + '`OS_PLATFORM_OWNER_EMAIL` unset the first sentence is the whole truth; with it declared, '\n + 'this row stops being the single point that un-makes every administrator.',\n columns: [\n 'id',\n 'name',\n 'active',\n 'system_permissions',\n 'systemPermissions',\n 'tab_permissions',\n 'tabPermissions',\n ],\n },\n\n sys_user_permission_set: {\n role: 'derives',\n reason:\n 'The grant that makes a user a platform admin: an UNSCOPED, in-window (ADR-0091) grant of '\n + '`admin_full_access` (§6). Re-pointing it, scoping it to an organization or moving it out '\n + 'of its window revokes the standing while leaving the row in place.',\n columns: [\n 'user_id',\n 'permission_set_id',\n 'permissionSetId',\n 'organization_id',\n 'organizationId',\n 'valid_from',\n 'validFrom',\n 'valid_until',\n 'validUntil',\n ],\n },\n\n sys_member: {\n role: 'derives',\n reason:\n 'Organization owner/admin standing (§3). The graded `role` is projected into `positions` '\n + 'here and separately drives the `organization_admin` capability grant, which is what the '\n + 'posture ladder reads; the break-glass guard counts the same rows one step earlier, by '\n + 'grade (ADR-0108). Either way a downgrade of the last graded membership is a write that '\n + 'can empty the administrator population.',\n columns: [\n 'user_id',\n 'userId',\n 'organization_id',\n 'organizationId',\n 'role',\n 'valid_from',\n 'validFrom',\n 'valid_until',\n 'validUntil',\n ],\n },\n\n sys_user: {\n role: 'derives',\n reason:\n '[#11663 L2] RECLASSIFIED from `reads-only`. This table used to be read only for the '\n + '`current_user.email` RLS fallback and the ADR-0024 `ai_seat` synthesis (§7), and the '\n + 'note here said so: \"Neither confers administrator standing.\" That sentence is now FALSE. '\n + 'The config anchor (§6b-config) matches the row\\'s own `email` against the deployment\\'s '\n + 'declared administrator list and requires `email_verified` to read verified, so a write '\n + 'that changes either column takes platform-admin standing away from a config-derived '\n + 'administrator — an address change and an email_verified reset are both ordinary, '\n + 'reachable writes, and neither touches a grant table. `banned` stays absent from the '\n + 'column list because the resolver still never reads it; the guard watches the ban/delete '\n + 'WRITE SHAPES on this table for its own reasons, which is a different question from what '\n + 'this resolver consumes.',\n columns: [\n 'id',\n 'email',\n 'email_verified',\n 'ai_access',\n ],\n },\n\n sys_user_position: {\n role: 'reads-only',\n reason:\n 'ADR-0057 D4 platform-RBAC position assignments (§4). A position can carry permission sets '\n + '(see `sys_position_permission_set`) but never platform-admin standing — §6b requires the '\n + 'set to be reached through an unscoped USER grant (`unscopedUserPsIds`), so a '\n + 'position-bound `admin_full_access` resolves the set name into `permissions` and leaves '\n + '`hasPlatformAdminGrant` false.',\n },\n\n sys_position: {\n role: 'reads-only',\n reason:\n 'Read to drop DEACTIVATED positions (ADR-0049, §6a). Same reason as `sys_user_position`: '\n + 'the position path cannot reach `hasPlatformAdminGrant`.',\n },\n\n sys_position_permission_set: {\n role: 'reads-only',\n reason:\n 'Position-bound permission sets (§6a). Contributes ids to `psIds` — and therefore names to '\n + '`permissions` — but not to `unscopedUserPsIds`, which is the set §6b tests for '\n + 'platform-admin standing.',\n },\n};\n\n/** A derivation input that is not a table — see {@link ADMIN_STANDING_NON_TABLE_INPUTS}. */\nexport interface AdminStandingNonTableInput {\n /** How the value reaches the resolver, e.g. `env` for a process environment variable. */\n readonly kind: 'env';\n /** The exact spelling an operator sets — quotable verbatim in a refusal message. */\n readonly name: string;\n /** What it decides, and what a break-glass guard can and cannot do about it. */\n readonly reason: string;\n}\n\n/**\n * [#11663 L2] Inputs to the administrator derivation that no table write can\n * reach — declared here so this file's silence about them cannot be read as\n * \"the derivation reads only tables\".\n *\n * The practical consequence is the one worth writing down: a break-glass guard\n * simulates a pending WRITE, and there is no write to simulate for any of\n * these. Standing that rests on one of them is taken away by changing the\n * deployment's configuration and rolling the process, which is deliberately\n * outside every in-product path — including every path an agent could be talked\n * into calling. That is the whole point of the config anchor, and it is also\n * the reason a guard cannot promise to prevent this class of lockout: it can\n * only refuse the writes it can see.\n */\nexport const ADMIN_STANDING_NON_TABLE_INPUTS: readonly AdminStandingNonTableInput[] = [\n {\n kind: 'env',\n name: 'OS_PLATFORM_OWNER_EMAIL',\n reason:\n 'The deployment\\'s declared platform administrator(s) — one address or a comma-separated '\n + 'list, matched case-insensitively against `sys_user.email` and conferring standing only '\n + 'when that row\\'s `email_verified` reads verified (§6b-config). Read live on every '\n + 'derivation with a per-process memo keyed on the raw string, so a rolled process picks up '\n + 'a change with no special path. Unset, blank, or carrying any unparseable entry means '\n + 'ZERO config-derived administrators, fail closed. No runtime write reaches it, so no '\n + 'break-glass guard can simulate a change to it: revocation is a configuration change plus '\n + 'a process roll, by design.',\n },\n];\n\n/** The tables a write to which can change who is an administrator. */\nexport function adminStandingTables(): string[] {\n return Object.entries(ADMIN_STANDING_SURFACE)\n .filter(([, t]) => t.role === 'derives')\n .map(([name]) => name)\n .sort();\n}\n\n/**\n * The columns this resolver reads on `table`, or `undefined` when the table is\n * not part of the administrator derivation.\n */\nexport function adminStandingColumns(table: string): readonly string[] | undefined {\n const entry = ADMIN_STANDING_SURFACE[table];\n return entry?.role === 'derives' ? entry.columns : undefined;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * [#7678] The `?status=` vocabulary of the audience-binding suggestion list\n * (ADR-0090 D5/D9) — ONE owner for a rule that had exactly one implementation\n * and two seams needing it.\n *\n * The predicate was written for the runtime dispatcher's `/security` domain and\n * lived there, private. The **live** REST route\n * (`rest-server.ts` → `registerSecurityEndpoints`) is a second seam onto the\n * same service call and never had it, so `?status=garbage` reached the service,\n * matched no row, and answered **200 with an empty list** — which reads as\n * \"there are no suggestions\", a plausible and actionable-looking answer, rather\n * than \"your filter was not a status\". That silent arm is the defect; the two\n * seams disagreeing about one contract is the cause.\n *\n * So this module is the convergence, not a copy: `domains/security.ts` and\n * `rest-server.ts` both import from here, and the vocabulary — including the\n * refusal wording — exists once.\n *\n * The record is keyed BY the contract type on purpose (carried over from the\n * original): adding a status to `AudienceBindingSuggestionFilter` leaves a key\n * missing here and renaming one leaves a key excess, and either way this fails\n * to compile. A plain `['pending', …]` array would silently drift.\n */\n\nimport type { AudienceBindingSuggestionFilter } from '@objectstack/spec/contracts';\n\n/** The `status` arm of {@link AudienceBindingSuggestionFilter}, named. */\nexport type AudienceBindingSuggestionStatus = NonNullable<AudienceBindingSuggestionFilter['status']>;\n\n/** The accepted `?status=` values, keyed by the contract type (see module note). */\nexport const AUDIENCE_BINDING_SUGGESTION_STATUSES: Record<AudienceBindingSuggestionStatus, true> = {\n pending: true,\n confirmed: true,\n dismissed: true,\n};\n\n/**\n * The same vocabulary as a list — for refusal messages, and for tests that must\n * enumerate every valid value FROM the type rather than hand-picking one.\n */\nexport const AUDIENCE_BINDING_SUGGESTION_STATUS_VALUES = Object.keys(\n AUDIENCE_BINDING_SUGGESTION_STATUSES,\n) as readonly AudienceBindingSuggestionStatus[];\n\n/**\n * Is `value` one of the three statuses the contract declares? Case-sensitive on\n * purpose — the contract's values are lowercase, so `PENDING` is not a status\n * and gets the same refusal as `garbage`.\n */\nexport const isAudienceBindingSuggestionStatus = (\n value: string,\n): value is AudienceBindingSuggestionStatus =>\n Object.prototype.hasOwnProperty.call(AUDIENCE_BINDING_SUGGESTION_STATUSES, value);\n\n/** The refusal wording, shared so both seams answer an unknown status identically. */\nexport const unknownAudienceBindingSuggestionStatusMessage = (value: string): string =>\n `Unknown status filter '${value}' — expected one of: ${AUDIENCE_BINDING_SUGGESTION_STATUS_VALUES.join(', ')}`;\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * [#7284] The `__` operation-private-key convention — one owner, on the\n * CONSUMER side.\n *\n * `assemble-execution-context.ts` next door is the single place an\n * `ExecutionContext` is BUILT at a transport entry point (#6216). This file is\n * its counterpart at the other end: the single place one is stripped back down\n * before being forwarded to a question it was not resolved for.\n *\n * ## What a `__` key is\n *\n * plugin-security's middleware STAMPS keys onto the operation context, resolved\n * for the object of the operation IN FLIGHT. They are middleware-private\n * vocabulary, not fields of `ExecutionContext`, and every one of them is read as\n * a WIDENING input by whoever consumes it:\n *\n * - the ADR-0057 D1 access DEPTH the sharing owner-match expands to —\n * `__readScope` / `__writeScope`, plus the ADR-0090 D10 delegator halves\n * `__delegatorReadScope` / `__delegatorWriteScope`, stamped in place by\n * `security-plugin.ts` (`sc.__readScope = …`);\n * - the engine's internal privilege markers on the same channel —\n * `__expandRead` marks a read as a lookup EXPANSION sub-read (it no longer\n * relaxes any gate — #7626 removed that waiver — but it still travels with\n * one operation and must not be inherited by another), `__referentialFieldClear`\n * authorizes the referential-clear write. [#13644] The latter also has a\n * DECLARED, read-only projection — `HookContext.referentialFieldClear`\n * (`@objectstack/spec/data`), populated by objectql's `update()` assembly\n * and carried across the sandbox boundary by contract — which is what an\n * APP reads; the `__` key here remains the engine/middleware authorization\n * channel, and this file's stripping rule is unchanged by the projection.\n *\n * plugin-security is the PRODUCER of that vocabulary and would be the most\n * honest owner of the rule for consuming it, but none of the three consumers\n * depends on it and a string-prefix filter does not justify three new dependency\n * edges onto a plugin (the trade the filing card priced, #7284). `@objectstack/\n * spec` is fenced off by Prime Directive #2. `@objectstack/core` is the only\n * candidate every consumer already depends on, so the rule lives here and the\n * producer stays free of reverse edges.\n *\n * ## Why a consumer must drop them\n *\n * A caller's envelope carries a depth resolved for the object the middleware\n * last saw. A consumer that forwards that envelope to ask about a DIFFERENT\n * object applies one object's widening to another object's question — the exact\n * stale-scope leak `resolveWriteScopeForSharing` was extracted to prevent (\"a\n * stale value can never leak in through a spread\", `security-plugin.ts`).\n *\n * The leak is not hypothetical and does not require the consumer to be careless:\n * plugin-security only OVERWRITES `__readScope` when it actually resolves\n * permission sets for the new object (`if (permissionSets.length > 0)`), so a\n * stale depth SURVIVES into a question it was never resolved for whenever that\n * branch does not fire. A REST request that touched another object before\n * reaching, say, `/reports/:id/run` hands over an envelope the middleware has\n * already written into.\n *\n * Dropping is safe in the one direction that matters: the middleware re-stamps\n * the depth for THIS object when it resolves any set, so the only thing dropping\n * can do is leave the sharing owner-match at its narrowest (`own`) — the safe\n * direction.\n *\n * ## Why by PREFIX and never by a name list\n *\n * The `__` convention is what marks a key as belonging to the operation in\n * flight. A hand-maintained list of the six names above would go stale the day\n * the middleware stamps a seventh, and it would go stale SILENTLY — a forwarded\n * key nobody remembered to add reads exactly like a key that was meant to be\n * forwarded. The prefix is the contract; the names are its current membership.\n *\n * ⛔ The corollary, for whoever changes the middleware: a key that is\n * operation-private MUST carry the `__` prefix. Stamping one without it makes it\n * invisible to every consumer at once, and there is no compiler error.\n *\n * ## Known consumers\n *\n * `plugin-audit` (`comment-access-hooks.ts`, #7141), `service-storage`\n * (`attachment-access-hooks.ts`, #7145) and `plugin-reports`\n * (`report-service.ts`, #7204) — each forwarding a caller envelope to a gate or\n * a read that asks about a parent/target object rather than about the object the\n * middleware resolved for. Each of the three grew its own byte-equivalent copy\n * of this file by hand before #7284 gave the rule a home; `operation-private-\n * keys.pin.test.ts` is what now catches a fourth.\n */\n\nimport type { ExecutionContext } from '@objectstack/spec/kernel';\n\n/**\n * The prefix marking a key as private to the operation plugin-security has in\n * flight. See this module's header for why the convention is a prefix and not a\n * list of names.\n */\nexport const OPERATION_PRIVATE_KEY_PREFIX = '__';\n\n/**\n * The caller's execution envelope, minus the operation-private keys.\n *\n * A FRESH object every time, and that is load-bearing in BOTH directions:\n *\n * - outbound — a callee that stamps its own `__writeScope` onto what it\n * receives (which is exactly what plugin-security does before it calls the\n * sharing service) can never write back into the operation context the caller\n * was handed;\n * - inbound — the engine's middleware stamps a depth for the object it is about\n * to read onto whatever it is handed, so forwarding a caller's envelope BY\n * REFERENCE would write that depth back into the request context the route\n * goes on using.\n *\n * ⛔ Never `return exec;` on the \"nothing to strip\" path. The copy is the point,\n * not an optimisation to skip when the envelope happens to be clean — the two\n * hazards above are about the callee's future writes, not about the current\n * contents.\n *\n * Note what this deliberately does NOT do: it strips, it never SYNTHESISES a\n * depth for the new object. Absent depth leaves the sharing owner-match at its\n * narrowest (`own`), which is the safe direction and byte-for-byte what the\n * five-field projections these call sites replaced produced.\n *\n * @param exec the caller's envelope, as a bare record\n * @returns a new envelope carrying only the non-operation-private keys\n */\nexport function withoutOperationPrivateKeys(exec: Record<string, unknown>): ExecutionContext {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(exec)) {\n if (key.startsWith(OPERATION_PRIVATE_KEY_PREFIX)) continue;\n out[key] = value;\n }\n return out as ExecutionContext;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * ── The `authz.invalidated` cluster channel (#11968, #11633 §3) ─────────────\n *\n * The cross-node half of the authorization invalidation substrate: one channel\n * name, one payload shape, and the contract statement that governs how both may\n * be read. It carries no cache and no consumer — leg B (#11967) is the first.\n *\n * ## ⭐ THE TTL IS THE CORRECTNESS CONTRACT. THIS CHANNEL IS NOT.\n *\n * A message on this channel is a **hint**, and **a missed message is EXPECTED**.\n * That is not a caveat about an unreliable network; it is the shipped\n * guarantee, measured rather than assumed:\n *\n * - `content/docs/kernel/cluster.mdx` §4.2, on `at-least-once`:\n * *\"**No shipped driver provides this yet.** The `redis` driver publishes\n * over plain Redis pub/sub, which is *at-most-once* — fire-and-forget, no\n * persistence, no replay for a node that was down at publish time.\"*\n * - `@objectstack/service-cluster-redis`'s own `publish` docblock:\n * *\"there is no delivery guarantee to subscribers and no replay for a node\n * that was down or slow at publish time. This is acceptable **only** for\n * events that are pure cache-invalidation hints, never the source of\n * truth.\"*\n * - The `memory` driver does not cross a process boundary at all\n * (`service-cluster/src/memory/pubsub.ts`, and the split-brain guard's\n * `IN_PROCESS_DRIVERS`).\n *\n * So a dropped message on an at-most-once transport is a staleness window with\n * **no upper bound**, and no amount of care at the publish site changes that.\n * What bounds it is the **TTL** every cached authorization answer must carry:\n * a peer that never hears the message still converges when its entry expires.\n *\n * ⇒ **A consumer that would be incorrect if a message were lost is misusing\n * this channel.** The channel exists for one thing: moving the *typical*\n * convergence from \"one TTL\" down to \"one network hop\". It never moves the\n * worst case, and it is never the mechanism that makes a cached authorization\n * answer safe.\n *\n * ⚠️ For the same reason this channel is **best-effort at the publish site\n * too**: a publish failure is logged and swallowed, never propagated into the\n * write that triggered it. A grant revocation must not fail because a cache\n * hint could not be delivered — the TTL already covers exactly that case.\n *\n * `IPubSub`'s own interface docblock (`@objectstack/spec/contracts`) states the\n * same thing from the contract side — delivery is whatever the configured\n * driver declares, no shipped driver exceeds at-most-once, and handlers must be\n * idempotent **and** tolerate loss. That docblock, `cluster.mdx` §4.2 and the\n * redis driver agree; there is no disagreement here for a later reader to go\n * looking for.\n *\n * ## Why a new channel on the existing bus, and not a new transport\n *\n * `MetadataClusterBridgePlugin` already shows the whole shape — a channel on\n * `IPubSub`, bridged by a plugin that late-binds at `kernel:ready` and does\n * nothing when the services it needs are absent. Reusing it adds no dependency\n * and no new failure mode. The one thing metadata's channel does NOT have to\n * carry is what makes this one different: a missed `metadata.changed` costs a\n * stale schema until reload and loses no data, while a missed\n * `authz.invalidated` would cost a permission honoured past its revocation —\n * which is why the bound lives in the TTL and why the absence of this bridge is\n * stated out loud at boot ({@link ../security/authz-cache-posture.js}).\n */\n\n/**\n * The cluster channel authorization-cache invalidation hints travel on.\n *\n * Named as a fact about authorization, not about any one cache, because a\n * second consumer must reuse this channel rather than mint a parallel one —\n * two channels would be two chances to miss a bridge.\n */\nexport const AUTHZ_INVALIDATED_CHANNEL = 'authz.invalidated';\n\n/**\n * Why an authorization epoch advanced. Coarse on purpose (#11633 §2.2, Fork 1 →\n * A): the engine seam sees `update`/`delete` expressed as a `where`, from which\n * the affected user or organization is frequently **not derivable without\n * reading the row back**. So the substrate carries \"something authorization-\n * relevant changed\", never \"whose entry to drop\", and a consumer retires its\n * whole bucket. Keyed invalidation is gated behind a measurement of a\n * write-heavy tenant and is explicitly not the starting point.\n */\nexport type AuthzInvalidationReason =\n /** A write (`insert` / `update` / `delete`) passed the engine middleware seam. */\n | 'write'\n /** A metadata change — a permission set can be DECLARED, so no row is written. */\n | 'metadata'\n /** A hint received from a peer node on this channel. */\n | 'remote'\n /** An explicit bump by a host that knows something the seam cannot see. */\n | 'manual';\n\n/**\n * The payload on {@link AUTHZ_INVALIDATED_CHANNEL}.\n *\n * Deliberately tiny and deliberately NOT a description of what to invalidate:\n * see {@link AuthzInvalidationReason} for why the seam cannot supply that. A\n * receiver's only correct response is to retire its authorization cache\n * wholesale — and to remain correct if this message never arrives.\n *\n * ⛔ Not a `packages/spec` contract type. #11633 §5 reserves a declared shape\n * for the invalidation event to the spec seat and does not pre-commit it; this\n * is the runtime shape the substrate publishes today.\n */\nexport interface AuthzInvalidatedPayload {\n /**\n * Publishing node, for loopback suppression — a node must not act on its own\n * hint. Mirrors `ClusterMetadataChangedPayload.originNode`.\n */\n originNode?: string;\n /** The publisher's local epoch after the bump. Diagnostic only. */\n epoch: number;\n /** What advanced the epoch. Diagnostic only — see the type's doc. */\n reason: AuthzInvalidationReason;\n /** Wall-clock publish time, ms since epoch. Best-effort. */\n at: number;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Timezone-aware calendar utilities (ADR-0053 Phase 2).\n *\n * The one primitive everything else builds on is {@link calendarPartsInTz}:\n * the year/month/day an instant falls on *as seen in a reference timezone*.\n * It uses `Intl.DateTimeFormat().formatToParts()` so DST transitions are\n * handled by the platform's tz database — never hand-rolled offset math, which\n * is the classic source of off-by-one-hour bucket errors.\n *\n * This lives in `@objectstack/core` (not `@objectstack/formula`) because both\n * the ObjectQL aggregation engine and the analytics service need it and both\n * already depend on core, whereas neither depends on formula's public surface.\n * (`@objectstack/formula` keeps its own private copy for `today()`/`daysFromNow`\n * to avoid a layering dependency on core.)\n */\n\n/** Calendar-day parts in a reference timezone. `month` is 1-12. */\nexport interface CalendarParts {\n year: number;\n month: number;\n day: number;\n}\n\n/**\n * A wall clock as a human writes it — calendar day plus an optional\n * time-of-day, with **no zone attached**. `2026-08-01 06:00:00` is this shape:\n * it names a reading on a clock, and only a reference timezone turns it into an\n * instant. Omitted time components default to 0, so {@link CalendarParts} alone\n * is midnight.\n */\nexport interface WallClockParts extends CalendarParts {\n /** 0-23. */\n hour?: number;\n minute?: number;\n second?: number;\n millisecond?: number;\n}\n\n/**\n * The year/month/day an instant falls on in `tz`. Throws if `tz` is not a\n * valid IANA zone (callers treat that as a fall-through to UTC).\n */\nexport function calendarPartsInTz(d: Date, tz: string): CalendarParts {\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone: tz,\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n }).formatToParts(d);\n const get = (t: string) => Number(parts.find((p) => p.type === t)?.value);\n return { year: get('year'), month: get('month'), day: get('day') };\n}\n\n/**\n * The calendar-day parts of an instant, in `tz` when it's a real non-UTC zone,\n * otherwise in UTC. Never throws: an unset, `'UTC'`, or invalid zone falls back\n * to the UTC calendar day. This is the safe entry point for bucketing code that\n * must degrade to the historical UTC behavior rather than error.\n */\nexport function calendarPartsInTzOrUtc(d: Date, tz?: string): CalendarParts {\n if (tz && tz !== 'UTC') {\n try {\n return calendarPartsInTz(d, tz);\n } catch {\n // unknown zone → fall through to UTC\n }\n }\n return {\n year: d.getUTCFullYear(),\n month: d.getUTCMonth() + 1,\n day: d.getUTCDate(),\n };\n}\n\n/**\n * The UTC instant (epoch ms) at which calendar day `ymd` (`YYYY-MM-DD`) *begins*\n * in reference timezone `tz` — i.e. local **midnight** of that day rendered as a\n * UTC instant. The inverse direction of {@link calendarPartsInTz}.\n *\n * DST-safe: the zone offset is read from the platform tz database via\n * `Intl.DateTimeFormat` (never hand-computed), and a two-pass resolution settles\n * the rare case where the offset differs side-to-side of the target instant. An\n * unset, `'UTC'`, invalid, or unparseable input returns plain UTC midnight.\n *\n * Used by date-bucket drill ranges (#1752): a `datetime` field buckets on the\n * reference-tz calendar, so its bucket boundary is that tz's midnight instant.\n *\n * Date-only by contract: a `YYYY-MM-DD HH:mm:ss` argument is still `NaN` here.\n * Callers holding a wall clock with a time-of-day want\n * {@link zonedWallClockToUtcMs}, which this delegates its zone arithmetic to.\n */\nexport function zonedDateStartToUtcMs(ymd: string, tz?: string): number {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(ymd);\n if (!m) return NaN;\n return zonedWallClockToUtcMs(\n { year: Number(m[1]), month: Number(m[2]), day: Number(m[3]) },\n tz,\n );\n}\n\n/**\n * The UTC instant (epoch ms) at which a **wall clock** reading happens in\n * reference timezone `tz` — the general inverse of {@link calendarPartsInTz},\n * of which {@link zonedDateStartToUtcMs} is the midnight special case.\n *\n * `2026-08-01 06:00:00` in `Asia/Shanghai` is `2026-07-31T22:00:00Z`: a\n * different day, month and quarter. That gap is why this direction exists as a\n * shared primitive at all — bulk import (#8485) reads offset-free spreadsheet\n * cells, which are wall clocks and nothing more, and `new Date(cell)` resolves\n * them against the **process** `TZ`, i.e. a host setting rather than the\n * tenant's configured zone.\n *\n * DST-safe: the zone offset is read from the platform tz database via\n * `Intl.DateTimeFormat` (never hand-computed), and a two-pass resolution settles\n * the case where the offset differs side-to-side of the target instant. Two\n * wall clocks are not a bijection with instants, and this function resolves\n * both degenerate cases to the **earlier candidate instant** — in both, the\n * final pass reads the offset on the DST side of the transition (measured, not\n * merely intended — `datetime.test.ts` pins both):\n * - a clock reading the zone **skips** (spring forward: `02:30` on a US\n * spring-forward day) settles on the *post*-transition offset (EDT, −04),\n * which places the instant just **before** the gap: it reads `01:30` EST\n * locally, not `03:30` EDT. Note this is the opposite of Temporal's\n * `'compatible'` disambiguation, which pushes a gap reading forward;\n * - a clock reading that happens **twice** (fall back: `01:30` on a US\n * fall-back day) resolves to its first occurrence, the one still on the\n * pre-transition DST offset (EDT, −04).\n *\n * A spreadsheet cell naming a wall clock that its zone never had is ambiguous\n * by construction; what matters for an import is that the answer is\n * deterministic and host-independent, which both branches above are.\n *\n * FALLBACK — an unset, `'UTC'`, or invalid `tz` reads the wall clock **as UTC**,\n * never as the process-local clock. Every caller of this family already degrades\n * that way ({@link zonedDateStartToUtcMs}, and the export renderer's cell path),\n * and a host `TZ` fallback would reintroduce exactly the deployment-dependent\n * instant this primitive exists to remove. A parts object that produces an\n * invalid date (`NaN` components) returns `NaN`, as `Date.UTC` does.\n */\nexport function zonedWallClockToUtcMs(parts: WallClockParts, tz?: string): number {\n const wallAsUtc = Date.UTC(\n parts.year,\n parts.month - 1,\n parts.day,\n parts.hour ?? 0,\n parts.minute ?? 0,\n parts.second ?? 0,\n parts.millisecond ?? 0,\n );\n if (!tz || tz === 'UTC' || Number.isNaN(wallAsUtc)) return wallAsUtc;\n try {\n // The tz offset (local − UTC, in ms) at instant `t`: read t's wall clock in\n // `tz`, re-interpret those parts as UTC, and subtract t. `formatToParts`\n // resolves no finer than a second, so `t` is truncated to a whole second\n // first — otherwise a sub-second wall clock leaks its milliseconds into the\n // \"offset\" and shifts the answer by them (a real defect while generalising\n // this from the date-only form, where ms was always 0).\n const offsetAt = (t: number): number => {\n const whole = Math.floor(t / 1000) * 1000;\n const p = new Intl.DateTimeFormat('en-US', {\n timeZone: tz,\n hourCycle: 'h23',\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n }).formatToParts(new Date(whole));\n const g = (k: string) => Number(p.find((x) => x.type === k)?.value);\n return (\n Date.UTC(g('year'), g('month') - 1, g('day'), g('hour'), g('minute'), g('second')) - whole\n );\n };\n // Want U such that localParts(U) == the wall clock, i.e. U = wallAsUtc − offset(U).\n // Iterate from the zero-offset guess; converges in ≤2 steps off a DST edge.\n const off1 = offsetAt(wallAsUtc - offsetAt(wallAsUtc));\n return wallAsUtc - off1;\n } catch {\n return wallAsUtc; // unknown zone → the wall clock read as UTC\n }\n}\n\n/**\n * Calendar-day bound semantics (ADR-0053 D-D) now live in `@objectstack/spec`,\n * beside the date-macro vocabulary they give meaning to — the fifth consumer\n * (`@objectstack/formula`'s RLS write-side `check` evaluator) cannot depend on\n * this package, and a second copy of the rule is exactly the divergence #3777\n * catalogued.\n *\n * Re-exported here so the published `@objectstack/core` surface is unchanged\n * for the drivers and analytics strategies that already import it from here.\n */\nexport { nextUtcCalendarDay, utcInstantMs } from '@objectstack/spec/data';\n\n/**\n * Granularity of a canonical date-bucket key. Mirrors `@objectstack/spec`'s\n * `DateGranularity` enum but kept as a local literal union so this low-level\n * package needs no dependency on spec.\n */\nexport type BucketGranularity = 'day' | 'week' | 'month' | 'quarter' | 'year';\n\n/**\n * ISO-8601 week label (Mon-start weeks, week 1 = the week of the first\n * Thursday) of a UTC calendar day. The forward-direction companion used to\n * *validate* a reconstructed week boundary; it mirrors the week branch of\n * `@objectstack/objectql`'s `bucketDateValue` (kept in lockstep by the\n * round-trip parity test in objectql).\n */\nfunction isoWeekLabelUtc(d: Date): string {\n const target = new Date(d.getTime());\n const dayNum = (target.getUTCDay() + 6) % 7; // Mon=0..Sun=6\n target.setUTCDate(target.getUTCDate() - dayNum + 3); // shift to that week's Thursday\n const firstThursday = new Date(Date.UTC(target.getUTCFullYear(), 0, 4));\n const weekNo =\n 1 +\n Math.round(\n ((target.getTime() - firstThursday.getTime()) / 86400000 -\n 3 +\n ((firstThursday.getUTCDay() + 6) % 7)) /\n 7,\n );\n return `${target.getUTCFullYear()}-W${String(weekNo).padStart(2, '0')}`;\n}\n\n/**\n * The half-open calendar span `[start, end)` of a canonical date-bucket KEY,\n * as `YYYY-MM-DD` strings (`start` inclusive, `end` exclusive — the next\n * bucket's first day).\n *\n * The input MUST be the canonical key produced by `bucketDateValue` /\n * `buildDateBucketExpr` (`2026`, `2026-Q2`, `2026-06`, `2026-06-15`,\n * `2026-W23`) — NEVER a localized / humanized display label. The span is pure,\n * timezone-naive calendar arithmetic; a caller that needs instant bounds for a\n * `datetime` field in a reference timezone layers that on top (and, per\n * ADR-0053, a `date` field compares against these `YYYY-MM-DD` bounds directly).\n *\n * Returns `null` for the empty bucket, an unparseable key, or a key that is\n * shape-valid but out of range (e.g. `2026-13`, a `-W53` in a 52-week year,\n * `2026-02-30`). Callers drop the range and fall back to an unscoped (superset)\n * drill rather than emit a wrong bound.\n *\n * `key` admits `null` because that IS the empty bucket's key on both aggregation\n * paths (#3839); callers pass a grouped row's dimension value straight through\n * rather than casting a lie.\n */\nexport function bucketKeyToCalendarRange(\n key: string | null | undefined,\n granularity: BucketGranularity,\n): { start: string; end: string } | null {\n if (typeof key !== 'string' || key.length === 0) return null;\n const fmt = (dt: Date) =>\n `${String(dt.getUTCFullYear()).padStart(4, '0')}-${String(dt.getUTCMonth() + 1).padStart(\n 2,\n '0',\n )}-${String(dt.getUTCDate()).padStart(2, '0')}`;\n\n switch (granularity) {\n case 'year': {\n const m = /^(\\d{4})$/.exec(key);\n if (!m) return null;\n const y = Number(m[1]);\n return { start: fmt(new Date(Date.UTC(y, 0, 1))), end: fmt(new Date(Date.UTC(y + 1, 0, 1))) };\n }\n case 'quarter': {\n const m = /^(\\d{4})-Q([1-4])$/.exec(key);\n if (!m) return null;\n const y = Number(m[1]);\n const startMonth = (Number(m[2]) - 1) * 3; // Q1→0, Q2→3, Q3→6, Q4→9\n return {\n start: fmt(new Date(Date.UTC(y, startMonth, 1))),\n end: fmt(new Date(Date.UTC(y, startMonth + 3, 1))), // Date.UTC rolls Q4 into next year\n };\n }\n case 'month': {\n const m = /^(\\d{4})-(\\d{2})$/.exec(key);\n if (!m) return null;\n const mo = Number(m[2]);\n if (mo < 1 || mo > 12) return null;\n const y = Number(m[1]);\n return {\n start: fmt(new Date(Date.UTC(y, mo - 1, 1))),\n end: fmt(new Date(Date.UTC(y, mo, 1))),\n };\n }\n case 'day': {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(key);\n if (!m) return null;\n const y = Number(m[1]);\n const mo = Number(m[2]);\n const d = Number(m[3]);\n const start = new Date(Date.UTC(y, mo - 1, d));\n if (fmt(start) !== key) return null; // reject an impossible day that rolled over\n return { start: key, end: fmt(new Date(Date.UTC(y, mo - 1, d + 1))) };\n }\n case 'week': {\n const m = /^(\\d{4})-W(\\d{2})$/.exec(key);\n if (!m) return null;\n const isoYear = Number(m[1]);\n const week = Number(m[2]);\n if (week < 1 || week > 53) return null;\n // Monday of ISO week 1 is the Monday on/before Jan 4; add (week-1) weeks.\n const jan4 = new Date(Date.UTC(isoYear, 0, 4));\n const jan4Dow = (jan4.getUTCDay() + 6) % 7; // Mon=0..Sun=6\n const start = new Date(jan4.getTime());\n start.setUTCDate(jan4.getUTCDate() - jan4Dow + (week - 1) * 7);\n if (isoWeekLabelUtc(start) !== key) return null; // reject -W53 overflow etc.\n const end = new Date(start.getTime());\n end.setUTCDate(start.getUTCDate() + 7);\n return { start: fmt(start), end: fmt(end) };\n }\n default:\n return null;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * `bulkWrite` — the shared batched-write helper used by BOTH the seed loader\n * (`@objectstack/metadata-protocol`) and the data-import runner\n * (`@objectstack/rest`), so neither reimplements batching, transient-error\n * retry, or per-row degradation. See framework#2678.\n *\n * ObjectQL's engine already does the efficient thing when handed an ARRAY —\n * one `driver.bulkCreate` round-trip plus parent-deduplicated summary\n * recompute (`engine.insert(object, rows[])`) — but seed/import fed it one\n * record at a time, so neither got the benefit. This module re-chunks rows\n * into batches and drives them through a caller-supplied batch-write\n * function, adding:\n *\n * - transient-error retry (network blip / timeout) with exponential\n * backoff, so a dropped connection doesn't silently drop the row (the\n * 2026-07-06 HotCRM incident: a turso `fetch failed` mid-seed dropped rows\n * silently because nothing retried);\n * - per-row degradation when a batch fails for a non-transient (logical /\n * validation) reason, so one bad row can't fail the other N-1 — needed\n * because `driver.bulkCreate` is a single multi-row statement/`Promise.all`\n * on every driver in this repo (sql, memory, mongodb): one bad row fails\n * the whole call;\n * - a stable per-row result keyed by the row's original index, so callers\n * can reassemble output in input order even though rows are processed in\n * batches (and a batch's flush may be interleaved with other, immediate,\n * per-row work such as updates).\n *\n * Delivery semantics: **at-least-once**. Transient retry and per-row\n * degradation both RE-RUN a write whose outcome was unknown — e.g. a turso\n * `fetch failed` that arrived *after* the row was already committed\n * (framework#3149), or a result-count mismatch that voids the batch\n * (framework#3151). A caller that needs exactly-once must make its\n * `writeBatch`/`writeOne` idempotent; both receive an `attempt` counter for\n * exactly this — see the natural-key recheck the seed loader and import\n * runner perform on `attempt > 1`. `writeBatch` MUST also resolve exactly one\n * record per input row, in input order: a short / long / non-array return is\n * rejected as a failed batch (framework#3151), never silently backfilled.\n */\n\nexport interface BulkWriteRowResult<TRecord = any> {\n /** Index into the original `rows` array passed to {@link bulkWrite}. */\n index: number;\n ok: boolean;\n record?: TRecord;\n error?: unknown;\n}\n\nexport interface RetryOptions {\n /** Max attempts for one write (batch or single-row), including the first. Default 3. */\n maxRetries?: number;\n /** Base backoff in ms; doubled each retry, plus jitter. Default 200. */\n backoffBaseMs?: number;\n /** Classifies an error as transient (worth retrying) vs logical (the row/batch is just bad). */\n isTransientError?: (err: unknown) => boolean;\n /** Injectable sleep, for deterministic tests. */\n sleep?: (ms: number) => Promise<void>;\n}\n\nexport interface BulkWriteOptions<TRow, TRecord = any> extends RetryOptions {\n /** Rows per batch. Default 200 (framework#2678 suggests 100-500). */\n batchSize?: number;\n /**\n * Write one batch. MUST resolve to one record per input row, in the SAME\n * order as `batch` — {@link bulkWrite} correlates `records[i]` back to\n * `batch[i]` positionally (this is how every `bulkCreate` implementation in\n * this repo already behaves: sql's single `INSERT ... VALUES (...), (...)\n * RETURNING *`, memory's `Promise.all`, mongodb's ordered `insertMany`).\n *\n * `ctx.attempt` is the 1-based attempt number. `attempt > 1` means a prior\n * attempt's outcome is UNKNOWN (a transient blip that may have landed after\n * commit) — an exactly-once caller should recheck by natural key and skip\n * rows already present before re-writing (framework#3149).\n */\n writeBatch: (batch: TRow[], ctx: { attempt: number }) => Promise<TRecord[]>;\n /**\n * Write a single row — used only to degrade a failed batch. `ctx.attempt`\n * carries the same recheck signal as {@link writeBatch}.\n */\n writeOne: (row: TRow, ctx: { attempt: number }) => Promise<TRecord>;\n /**\n * Partial-success batch write (framework#3172). When provided it is used\n * INSTEAD of {@link writeBatch}: it must resolve one outcome per input row,\n * in input order — `{ ok: true, record }` for written rows, `{ ok: false,\n * error }` for rows that failed individually (e.g. validation). Per-row\n * failures are final verdicts: bulkWrite records them as-is and does NOT\n * degrade to `writeOne` for them — that is the whole point (a degradation\n * re-run would re-fire beforeInsert hooks on the good rows). Only a THROWN\n * error (a transient infra failure, a result-count mismatch) falls back to\n * the per-row `writeOne` degradation, exactly like `writeBatch`.\n */\n writeBatchPartial?: (\n batch: TRow[],\n ctx: { attempt: number },\n ) => Promise<Array<{ ok: boolean; record?: TRecord; error?: unknown }>>;\n}\n\nconst DEFAULT_BATCH_SIZE = 200;\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_BACKOFF_BASE_MS = 200;\n\n/**\n * Transient-error signatures shared by common HTTP/TCP-backed drivers\n * (turso/libsql's fetch-based transport included). Deliberately excludes\n * anything that looks like a validation/constraint error — those must NOT be\n * retried, only degraded to per-row.\n */\nconst TRANSIENT_PATTERNS: RegExp[] = [\n /fetch failed/i,\n /network/i,\n /timed?\\s*out/i,\n /timeout/i,\n /socket hang ?up/i,\n /connection.*(closed|reset|refused|terminated|aborted)/i,\n /\\b(502|503|504)\\b/,\n /server.*unavailable/i,\n /too many connections/i,\n];\n\nconst TRANSIENT_CODES = /^(ECONNRESET|ECONNREFUSED|ECONNABORTED|EPIPE|EAI_AGAIN|ETIMEDOUT|EHOSTUNREACH|ENETUNREACH|ENOTFOUND)$/i;\n\n/**\n * Validation / constraint / schema signatures that are DEFINITIVELY logical,\n * never worth retrying. Checked before {@link TRANSIENT_PATTERNS} so a message\n * that happens to mention both (e.g. `CHECK constraint failed: network_zone`,\n * `column network_id is not allowed`) is classified as logical rather than\n * burning retries on a row that will fail identically every time (framework\n * #3150).\n */\nconst NON_TRANSIENT_PATTERNS: RegExp[] = [\n /validation/i,\n /constraint/i,\n /\\brequired\\b/i,\n /\\bunique\\b/i,\n /duplicate/i,\n /not[\\s_-]*null/i,\n /invalid/i,\n /not allowed/i,\n /out of range/i,\n];\n\nexport function defaultIsTransientError(err: unknown): boolean {\n const message = (err as { message?: unknown } | null)?.message;\n const text = typeof message === 'string' ? message : String(err ?? '');\n // A definitive logical signature wins even if a transient word also appears.\n if (NON_TRANSIENT_PATTERNS.some((re) => re.test(text))) return false;\n const code = (err as { code?: unknown } | null)?.code;\n if (typeof code === 'string' && TRANSIENT_CODES.test(code)) return true;\n return TRANSIENT_PATTERNS.some((re) => re.test(text));\n}\n\nconst defaultSleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\ninterface ResolvedRetryOptions {\n maxRetries: number;\n backoffBaseMs: number;\n isTransientError: (err: unknown) => boolean;\n sleep: (ms: number) => Promise<void>;\n}\n\nasync function withRetry<T>(fn: (attempt: number) => Promise<T>, opts: ResolvedRetryOptions): Promise<T> {\n let lastError: unknown;\n for (let attempt = 1; attempt <= opts.maxRetries; attempt++) {\n try {\n return await fn(attempt);\n } catch (err) {\n lastError = err;\n if (attempt >= opts.maxRetries || !opts.isTransientError(err)) throw err;\n const jitter = Math.floor(Math.random() * 50);\n await opts.sleep(opts.backoffBaseMs * 2 ** (attempt - 1) + jitter);\n }\n }\n // Unreachable — the loop above always returns or throws — but keeps TS's\n // control-flow analysis happy about a guaranteed return type.\n throw lastError;\n}\n\n/**\n * Retry a single write (e.g. an `engine.update()` call the seed loader or\n * import runner makes outside the batched-insert path) with the same\n * transient-error backoff {@link bulkWrite} applies to batches — so a\n * network blip doesn't drop an update the way it used to drop an insert.\n */\nexport async function withTransientRetry<T>(fn: (attempt: number) => Promise<T>, opts: RetryOptions = {}): Promise<T> {\n return withRetry(fn, {\n maxRetries: Math.max(1, opts.maxRetries ?? DEFAULT_MAX_RETRIES),\n backoffBaseMs: opts.backoffBaseMs ?? DEFAULT_BACKOFF_BASE_MS,\n isTransientError: opts.isTransientError ?? defaultIsTransientError,\n sleep: opts.sleep ?? defaultSleep,\n });\n}\n\n/**\n * Write `rows` through `opts.writeBatch` in chunks of `opts.batchSize`,\n * retrying a whole-batch transient failure with backoff, and degrading to\n * per-row `opts.writeOne` calls (each itself retried) when a batch fails for\n * a non-transient reason — so one bad row can't drop the rest of the batch.\n *\n * Returns one {@link BulkWriteRowResult} per input row, indexed to match\n * `rows`' original order.\n */\nexport async function bulkWrite<TRow, TRecord = any>(\n rows: TRow[],\n opts: BulkWriteOptions<TRow, TRecord>,\n): Promise<BulkWriteRowResult<TRecord>[]> {\n const batchSize = Math.max(1, opts.batchSize ?? DEFAULT_BATCH_SIZE);\n const retryOpts: ResolvedRetryOptions = {\n maxRetries: Math.max(1, opts.maxRetries ?? DEFAULT_MAX_RETRIES),\n backoffBaseMs: opts.backoffBaseMs ?? DEFAULT_BACKOFF_BASE_MS,\n isTransientError: opts.isTransientError ?? defaultIsTransientError,\n sleep: opts.sleep ?? defaultSleep,\n };\n\n const results: BulkWriteRowResult<TRecord>[] = new Array(rows.length);\n\n for (let start = 0; start < rows.length; start += batchSize) {\n const batch = rows.slice(start, start + batchSize);\n try {\n // Partial-success path (framework#3172): one call yields a final per-row\n // verdict, so a row that fails validation never triggers the whole-batch\n // degradation that re-runs beforeInsert hooks on its siblings.\n if (opts.writeBatchPartial) {\n const outcomes = await withRetry((attempt) => opts.writeBatchPartial!(batch, { attempt }), retryOpts);\n if (!Array.isArray(outcomes) || outcomes.length !== batch.length) {\n throw Object.assign(\n new Error(\n `bulkWrite: writeBatchPartial returned ${\n Array.isArray(outcomes) ? `${outcomes.length} outcome(s)` : String(typeof outcomes)\n } for a ${batch.length}-row batch — treating batch as failed`,\n ),\n { code: 'ERR_BULK_RESULT_MISMATCH' },\n );\n }\n for (let i = 0; i < batch.length; i++) {\n const o = outcomes[i];\n results[start + i] = o.ok\n ? { index: start + i, ok: true, record: o.record }\n : { index: start + i, ok: false, error: o.error };\n }\n continue;\n }\n const records = await withRetry((attempt) => opts.writeBatch(batch, { attempt }), retryOpts);\n // Contract guard (framework#3151): `writeBatch` must resolve one record\n // per input row. A short / long / non-array return breaks the positional\n // correlation below, so backfilling it would report phantom successes\n // (`record: undefined`) or drop records. Treat the whole batch as failed\n // and fall through to per-row degradation (each row re-attempted via\n // `writeOne`, which under an idempotent caller rechecks before writing).\n // The message deliberately avoids any transient signature so this never\n // reads as a retryable blip — and it is thrown *outside* `withRetry`, so\n // the batch is not retried on it.\n if (!Array.isArray(records) || records.length !== batch.length) {\n throw Object.assign(\n new Error(\n `bulkWrite: writeBatch returned ${\n Array.isArray(records) ? `${records.length} record(s)` : String(typeof records)\n } for a ${batch.length}-row batch — treating batch as failed`,\n ),\n { code: 'ERR_BULK_RESULT_MISMATCH' },\n );\n }\n for (let i = 0; i < batch.length; i++) {\n results[start + i] = { index: start + i, ok: true, record: records[i] };\n }\n } catch (batchErr) {\n // A single-row \"batch\" already IS the per-row attempt — its failure\n // (after transient retry) is the row's final outcome; calling\n // `writeOne` again would just repeat the identical work.\n if (batch.length === 1) {\n results[start] = { index: start, ok: false, error: batchErr };\n continue;\n }\n // The batch failed even after transient retry, or failed for a logical\n // reason retry wouldn't fix. Degrade to per-row so one bad row can't\n // fail the other rows in this batch. Each row still gets its own\n // transient retry — the batch-level failure doesn't tell us which row\n // (if any) was actually the transient one.\n for (let i = 0; i < batch.length; i++) {\n const idx = start + i;\n try {\n const record = await withRetry((attempt) => opts.writeOne(batch[i], { attempt }), retryOpts);\n results[idx] = { index: idx, ok: true, record };\n } catch (err) {\n results[idx] = { index: idx, ok: false, error: err };\n }\n }\n }\n }\n\n return results;\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * [#7823 / #8497] The write-response half of the `internal: true` guarantee —\n * THE single helper every write mouth that returns a body to an external\n * caller passes its record(s) through.\n *\n * ## The contract\n *\n * A field declared `internal: true` is *never returned on the generic data\n * path* (#7728). The READ half lives in the engine (`omitInternalFields` runs\n * on every find/findOne result). The WRITE-RESPONSE half lives HERE.\n *\n * ## Why the ingress and not the engine (the measured history, #7823)\n *\n * The first shape stripped `internal` fields inside the engine's insert and\n * by-id-update paths. That conflated two different guarantees:\n *\n * - \"never returned on the generic data path\" — the flag's sentence, about\n * what an EXTERNAL caller receives; and\n * - \"never returned to the engine-level caller that performed the write\" —\n * which no ruling ever asked for, and which is FALSE for credential mint:\n * better-auth's `createWithHooks` reads the minted `sys_session` row back\n * off the insert result, so the engine-side strip broke `signIn`/`signUp`\n * outright (measured: `verify signIn: no token in response`).\n *\n * Plain removal of the engine limbs was ALSO measured wrong: the by-id-update\n * strip was the sole closure of #7728's fourth surface — with it neutralised,\n * `PATCH /data/sys_api_key/{id}` answered 200 with the stored 64-hex `key`\n * hash in the body. Both measurements are satisfiable at exactly one boundary:\n * the mouth that builds the external 201/200 body. Engine write results keep\n * the stored row whole (mint works); every external write response is stripped\n * through this helper (the hash never leaves); the read path is untouched.\n *\n * ## Why this module sits in `@objectstack/core` (#8497)\n *\n * It shipped inside `@objectstack/metadata-protocol`, next to the protocol\n * class that was then its only caller. That placement encoded an assumption\n * the surface does not honour: **the generic write mouths are not all on the\n * protocol class.** Two transports reach the engine directly —\n *\n * - `@objectstack/rest` (`rest-server.ts`, the cross-object `POST /batch`\n * update arm's direct `ql.update`), and\n * - `@objectstack/mcp` (`stdio-data-bridge.ts`, whose `create` handed the\n * engine's insert result straight back to the MCP client — a MEASURED leak\n * of the flagged column, found by widening this guard's scope in #8497),\n *\n * — and neither package depends on `@objectstack/metadata-protocol`. The old\n * home therefore forced each new mouth to choose between a duck-typed reach\n * through a protocol instance (what `rest` does) and a private restatement of\n * the rule (a third copy of a security-relevant predicate). `@objectstack/core`\n * is the floor all three already depend on, and it already hosts exactly this\n * class of shared write-path helper (`bulk-write.ts`, used by both\n * `metadata-protocol` and `rest` so neither reimplements batching). One helper,\n * reachable from every mouth, is the whole point of the flag being structural.\n *\n * `@objectstack/metadata-protocol` re-exports both functions unchanged, so its\n * public API is byte-identical across the move.\n *\n * ## The residual risk, and what gates it\n *\n * Response-body policy at the mouth means a FUTURE write mouth that forgets\n * this helper leaks silently. Three tripwires hold the property, each an\n * enumeration no author can dodge by adding code without touching it:\n *\n * - `protocol.write-response-internal-fields.tripwire.test.ts`\n * (`metadata-protocol`) walks the protocol class's prototype for `*Data`\n * faces;\n * - `rest-write-response-internal-fields.tripwire.test.ts` (`rest`) walks\n * `RestServer.getRoutes()` for HTTP write routes;\n * - `mcp-write-response-internal-fields.tripwire.test.ts` (`mcp`) walks the\n * `McpDataBridge` write faces.\n *\n * Together they assert the PROPERTY — \"no response body an external caller\n * receives from a write carries an `internal: true` value\" — rather than the\n * shape of any one class. Adding a write mouth? Route its response records\n * through this helper and register it with the tripwire that enumerates its\n * surface.\n *\n * ## Semantics\n *\n * Mirrors the engine's `collectInternalReadFields` rule exactly — a field\n * participates iff its declaration carries `internal === true` (strict\n * boolean; truthy strings and numbers do not count, same as the engine).\n * `@objectstack/core` cannot import that collector (`@objectstack/objectql`\n * sits above this package), so the rule is restated here in full;\n * `internal-fields.test.ts` in objectql and the tripwires above pin the same\n * spelling from both sides. OMIT, not mask, for the #7728 reasons: the flag's\n * columns are `required`, so a mask carries zero bits while still shipping a\n * value under a field whose description promises none.\n *\n * Deletion is IN PLACE and idempotent: records that already lack the field\n * (a re-stripped read result, a fake engine that never returned it) pass\n * through unchanged, and non-record values (`null`, an affected-row count, a\n * driver's boolean delete verdict) are skipped rather than judged.\n */\n\n/** Minimal view of an object schema this module reads — the field map only. */\ninterface SchemaWithFields {\n fields?: Record<string, { internal?: unknown } | undefined> | undefined;\n}\n\n/**\n * Collect the names of fields declared `internal: true` on `schema`.\n *\n * Same verdicts as objectql's `collectInternalReadFields` (see the module\n * header for why it is restated rather than imported): strict `=== true`,\n * empty result for a missing/field-less schema.\n */\nexport function collectInternalWriteResponseFields(schema: unknown): string[] {\n const fields = (schema as SchemaWithFields | null | undefined)?.fields;\n if (!fields || typeof fields !== 'object') return [];\n const out: string[] = [];\n for (const [name, def] of Object.entries(fields)) {\n if (def && def.internal === true) out.push(name);\n }\n return out;\n}\n\n/**\n * Drop every `internal: true` field from a write response's record(s), in\n * place. THE single helper every external write mouth goes through — see the\n * module header; the three tripwires enforce the \"every\".\n *\n * @param schema The registered object schema (`engine.registry.getObject(...)`\n * / the protocol's own registry view / `metadataService\n * .getObject(...)`). An unknown object (no schema) strips\n * nothing — the write itself would have been refused upstream\n * by the object-existence gate.\n * @param records A single record, an array of records, or anything a write\n * mouth hands back where a record could sit (`null`, a count, a\n * boolean): non-objects are skipped, arrays are walked.\n */\nexport function omitInternalFieldsFromWriteResponse(schema: unknown, records: unknown): void {\n if (!records) return;\n const internalFields = collectInternalWriteResponseFields(schema);\n if (internalFields.length === 0) return;\n const list = Array.isArray(records) ? records : [records];\n for (const row of list) {\n if (!row || typeof row !== 'object') continue;\n for (const field of internalFields) delete (row as Record<string, unknown>)[field];\n }\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * `runMigrationJournal` — the framework-owned runner for data migrations that\n * are too big, too long, or too multi-step to live in one transaction\n * (ADR-0119 D2, #4617).\n *\n * ## Why this is framework-owned rather than four hand-rolled copies\n *\n * Four migration-class consumers independently converged on the same four\n * moves — dry-run preflight, an undo journal, LIFO compensation, re-entrant\n * forward recovery: ADR-0105 D13 promotion, ADR-0117 D8's ownership backfill,\n * the org lifecycle transitions, and ADR-0119 D10's master-data distribution\n * (#4585). One copy is engineering. Four is platform debt, and the fourth\n * author would have had to rediscover the `chunk_done`-inside-the-transaction\n * subtlety below from scratch — or, far more likely, not rediscover it.\n *\n * ## Why a journal at all, given ADR-0034 gave us transactions\n *\n * ADR-0119 D1 made `engine.transaction()` reachable through the contract, but\n * a transaction cannot be the whole answer here:\n *\n * - a million-row backfill cannot hold one write-lock for its duration;\n * - `driver-memory`'s `beginTransaction` deep-clones the entire database, so\n * \"just wrap the whole thing\" is O(db) per begin;\n * - `ObjectQL.transaction()` binds the DEFAULT driver only, so a migration\n * spanning datasources silently commits part of its work outside it;\n * - a process KILLED — as distinct from a thrown error — defeats in-process\n * rollback entirely, and that is the case operators actually hit.\n *\n * So the unit of atomicity is the CHUNK, and durability across chunks is the\n * journal. Everything else in this file follows from that one sentence.\n *\n * ## The invariant that carries the whole design\n *\n * `chunk_done(i)` is written INSIDE the chunk's own transaction, so\n * `done ⇔ committed` holds by construction rather than by luck.\n * `chunk_started(i)` is written autonomously BEFORE it. A reader who \"tidies\"\n * that asymmetry destroys recovery: it is what gives `started ∧ ¬done` exactly\n * one meaning — **the outcome is unknown** — which is the only state a crash\n * can leave and the only state recovery has to reason about.\n *\n * ## Delivery semantics: at-least-once, idempotency is the caller's job\n *\n * Inherited verbatim from `./bulk-write.ts` rather than re-derived, because a\n * second delivery-semantics story in the same codebase is a second thing to\n * get subtly wrong. Forward and compensate callbacks receive an `attempt`\n * counter; `attempt > 1` means the previous outcome is UNKNOWN — the write may\n * or may not have committed — and the callback must recheck by natural key\n * before re-writing. That is the same contract the seed loader and import\n * runner already honour on `attempt > 1`.\n */\n\nimport { createHash, randomUUID } from 'node:crypto';\nimport type { IObjectQLEngine } from '@objectstack/spec/contracts';\nimport {\n MIGRATION_JOURNAL_OBJECT,\n type MigrationJournalEvent,\n type MigrationJournalKind,\n type MigrationOnCrashPolicy,\n} from '@objectstack/spec/system';\n\n/** Journal writes and recovery reads run as the platform, never as a user. */\nconst SYSTEM_CTX = { isSystem: true } as const;\n\n/** Rows per chunk when a plan does not choose. Matches `bulk-write.ts`. */\nconst DEFAULT_CHUNK_SIZE = 200;\n\n/**\n * Can this runtime actually roll back? — the ADR-0119 D4 gate, shared.\n *\n * Exported from `@objectstack/core` and consumed by\n * `@objectstack/metadata-protocol`'s `batchData` (which depends on core, so\n * the direction is legal) so the two cannot drift. They were the same two-line\n * condition written twice, which is precisely the shape that drifts by one\n * clause and leaves one caller believing it has atomicity it does not have.\n *\n * TWO levels, both necessary. `engine.transaction()` exists but runs the\n * callback with NO transaction and NO rollback when the default driver lacks\n * `beginTransaction` — a declared caveat of the contract member (ADR-0119 D1),\n * and one that turns \"atomic\" back into a lie precisely where it matters. So\n * where the driver registry is inspectable the driver is checked too; where it\n * is not (test doubles), the engine-level probe is all there is.\n *\n * A type predicate, not a bare boolean: every caller's next move is to CALL\n * `transaction`, and on the host surfaces that declare it optionally\n * (`MetadataHostEngine`) a boolean would leave each one re-narrowing by hand —\n * which is the same restatement this helper exists to remove.\n */\nexport function engineCanRollBack<T>(engine: T): engine is T & EngineWithTransaction {\n const e = engine as {\n transaction?: unknown;\n getDefaultDriverName?: () => string | undefined;\n getDriverByName?: (name: string) => unknown;\n } | null | undefined;\n if (typeof e?.transaction !== 'function') return false;\n const defaultDriverName = e.getDefaultDriverName?.();\n const defaultDriver = defaultDriverName ? e.getDriverByName?.(defaultDriverName) : undefined;\n return !defaultDriver || typeof (defaultDriver as { beginTransaction?: unknown }).beginTransaction === 'function';\n}\n\n/**\n * What {@link engineCanRollBack} proves is present.\n *\n * Typed FROM the contract rather than transcribed from it (#5696): a hand-copy\n * mirrors the signature only until the contract moves, and this one had already\n * started to — it predates `opts.require` and the callback's `owned` argument.\n * ADR-0119 D1 blessed exactly this shape for the narrow host surfaces\n * (`transaction?: IObjectQLEngine['transaction']`); a *narrow* surface may stay\n * narrow, but it may not drift from the real signature.\n */\nexport interface EngineWithTransaction {\n transaction: IObjectQLEngine['transaction'];\n}\n\n/** What a forward/compensate callback is told about the chunk it is running. */\nexport interface MigrationChunkContext {\n readonly runId: string;\n /** Run-global chunk index — the LIFO ordering key, stable across a resume. */\n readonly chunkIndex: number;\n /**\n * 1 on the first try. `> 1` means a previous attempt's outcome is UNKNOWN:\n * recheck by natural key before re-writing (see this file's header).\n */\n readonly attempt: number;\n /**\n * The transaction-bound execution context. Thread it to every engine call\n * this callback makes — `engine.insert(obj, row, { context })` — so the\n * write joins the chunk's transaction instead of committing beside it.\n */\n readonly context: unknown;\n}\n\n/** One step of a plan. Steps run in declaration order; each is chunked. */\nexport interface MigrationPlanStep<TRow = unknown> {\n readonly name: string;\n /**\n * Read-only preflight. Throw to refuse the run. Runs for EVERY step before\n * any step writes — a plan that would fail at step 3 must not have written\n * step 1 (ADR-0117 D8's fail-closed enable gate, generalized).\n */\n preflight?(engine: IObjectQLEngine): Promise<void>;\n /** The rows this step processes. Called once, before chunking. */\n load(engine: IObjectQLEngine): Promise<TRow[]>;\n /** Forward work for one chunk. Runs INSIDE the chunk's transaction. */\n forward(rows: TRow[], ctx: MigrationChunkContext, engine: IObjectQLEngine): Promise<void>;\n /**\n * Undo one previously-committed chunk. Runs in its OWN transaction.\n * A step without one makes the plan non-compensable — which the runner\n * refuses up front rather than discovering at the worst possible moment\n * (see {@link runMigrationJournal}'s preflight).\n */\n compensate?(rows: TRow[], ctx: MigrationChunkContext, engine: IObjectQLEngine): Promise<void>;\n}\n\nexport interface MigrationPlan {\n /** Stable plan id. Part of the plan hash; identifies the plan across runs. */\n readonly id: string;\n /** Optional join to `sys_migration.id` when this plan implements a named migration. */\n readonly migrationId?: string;\n readonly steps: ReadonlyArray<MigrationPlanStep<any>>;\n readonly chunkSize?: number;\n /**\n * What a REDISCOVERED (crashed) run should do. Note this governs restart\n * only — an in-run failure always compensates, because the runner is still\n * alive to do it and a half-applied plan is nobody's intent.\n */\n readonly onCrash?: MigrationOnCrashPolicy;\n}\n\n/** One chunk in the run-global chunk plan. */\nexport interface MigrationChunk {\n /** Run-global index, 0-based, stable for a given plan hash. */\n readonly index: number;\n readonly stepIndex: number;\n readonly stepName: string;\n readonly offset: number;\n readonly length: number;\n}\n\nexport interface MigrationRunResult {\n readonly runId: string;\n /**\n * `completed` — every chunk committed.\n * `compensated` — a chunk failed and every committed chunk was undone.\n * `failed` — a chunk failed AND compensation could not finish. The database\n * is in a partial state that needs a human; the journal says exactly where.\n */\n readonly status: 'completed' | 'compensated' | 'failed';\n readonly chunksTotal: number;\n readonly chunksCommitted: number;\n readonly chunksCompensated: number;\n readonly planHash: string;\n /** The failure that ended a non-`completed` run. */\n readonly error?: unknown;\n}\n\n/**\n * Where a resume finds the plan it has to re-run (#4617).\n *\n * A journal cannot hold a plan. `forward` and `compensate` are FUNCTIONS, and\n * the rows a chunk covers are produced by `load()` against the live database —\n * none of it survives a process boundary, which is why the journal records the\n * plan HASH rather than the plan. So recovery needs the plan handed back to it\n * by whoever owns the code, and that is what this registry is: the seam between\n * \"the journal knows a run stopped at chunk 7\" and \"something in this process\n * knows what chunk 7 was supposed to do\".\n *\n * Registered as the `migration-plans` kernel service. An interrupted run whose\n * plan no loaded plugin registers is REPORTED, never silently skipped — the\n * operator is told which plan id is missing, because \"nothing to resume\" and\n * \"the code that owns this run is not loaded\" are different facts and only one\n * of them is safe to ignore.\n */\nexport interface MigrationPlanProvider {\n register(plan: MigrationPlan): void;\n get(planId: string): MigrationPlan | undefined;\n list(): MigrationPlan[];\n}\n\n/** The default {@link MigrationPlanProvider}. Last registration for an id wins. */\nexport class MigrationPlanRegistry implements MigrationPlanProvider {\n private readonly plans = new Map<string, MigrationPlan>();\n\n register(plan: MigrationPlan): void {\n this.plans.set(plan.id, plan);\n }\n\n get(planId: string): MigrationPlan | undefined {\n return this.plans.get(planId);\n }\n\n list(): MigrationPlan[] {\n return [...this.plans.values()];\n }\n}\n\n/** A run found by {@link findInterruptedRuns} — started, never concluded. */\nexport interface InterruptedRun {\n readonly runId: string;\n readonly planId: string;\n readonly planHash: string;\n readonly migrationId?: string;\n readonly startedAt?: string;\n /** Chunks whose `chunk_done` is present — known committed. */\n readonly committedChunks: number[];\n /** Chunks with `chunk_started` and no `chunk_done` — outcome UNKNOWN. */\n readonly unknownChunks: number[];\n readonly compensatedChunks: number[];\n}\n\n/** Raised when the runner refuses to start or to resume. Never a partial run. */\nexport class MigrationJournalRefusal extends Error {\n readonly code: string;\n constructor(code: string, message: string) {\n super(message);\n this.name = 'MigrationJournalRefusal';\n this.code = code;\n }\n}\n\n// ── plan shape ────────────────────────────────────────────────────────────\n\n/** Flatten steps × rows into the run-global chunk list. */\nexport function planChunks(\n plan: MigrationPlan,\n rowCounts: readonly number[],\n chunkSize = plan.chunkSize ?? DEFAULT_CHUNK_SIZE,\n): MigrationChunk[] {\n const size = Math.max(1, chunkSize);\n const chunks: MigrationChunk[] = [];\n plan.steps.forEach((step, stepIndex) => {\n const total = rowCounts[stepIndex] ?? 0;\n for (let offset = 0; offset < total; offset += size) {\n chunks.push({\n index: chunks.length,\n stepIndex,\n stepName: step.name,\n offset,\n length: Math.min(size, total - offset),\n });\n }\n });\n return chunks;\n}\n\n/**\n * Hash the plan SHAPE — id, step names, and the chunk boundaries.\n *\n * Resuming a changed plan against an old journal would apply chunk boundaries\n * the journal never described: \"chunk 7 done\" would name a different range of\n * different rows, and the resume would skip work it never did. So the hash\n * covers exactly what a chunk index means, and a mismatch REFUSES.\n */\nexport function hashMigrationPlan(plan: MigrationPlan, chunks: readonly MigrationChunk[]): string {\n const shape = JSON.stringify({\n id: plan.id,\n steps: plan.steps.map((s) => s.name),\n chunks: chunks.map((c) => [c.stepIndex, c.offset, c.length]),\n });\n return createHash('sha256').update(shape, 'utf8').digest('hex').slice(0, 32);\n}\n\n// ── journal I/O ───────────────────────────────────────────────────────────\n\n/**\n * Append one event.\n *\n * `execContext` is the transaction-bound context when the event must share a\n * chunk's fate (`chunk_done`, `compensated`) and undefined when it must NOT\n * (`chunk_started`, and every run-level event). Passing the wrong one is the\n * single most consequential mistake available in this file — see the header.\n */\nasync function appendEvent(\n engine: IObjectQLEngine,\n event: MigrationJournalEvent,\n execContext?: unknown,\n): Promise<void> {\n await engine.insert(\n MIGRATION_JOURNAL_OBJECT,\n { ...event, created_at: event.created_at ?? new Date().toISOString() },\n { context: execContext ?? { ...SYSTEM_CTX } },\n );\n}\n\n/**\n * Every event for a run, ordered by `seq`.\n *\n * Sorted in memory, deliberately. `seq` is the ordering authority (wall-clock\n * stamps tie at coarse resolution and skew), and a run's journal is bounded by\n * its chunk count, so this costs nothing and removes recovery's dependence on\n * driver-side sort behaviour — which is not something a recovery path should\n * be discovering the edges of.\n */\nexport async function readRunJournal(\n engine: IObjectQLEngine,\n runId: string,\n): Promise<MigrationJournalEvent[]> {\n const rows = (await engine.find(\n MIGRATION_JOURNAL_OBJECT,\n { where: { run_id: runId } },\n { context: { ...SYSTEM_CTX } },\n )) as MigrationJournalEvent[];\n return [...(rows ?? [])].sort((a, b) => Number(a.seq) - Number(b.seq));\n}\n\n/** Chunk indices carrying `kind`, as a set. */\nfunction chunkSetOf(events: readonly MigrationJournalEvent[], kind: MigrationJournalKind): Set<number> {\n const out = new Set<number>();\n for (const e of events) {\n if (e.kind === kind && typeof e.chunk_index === 'number') out.add(e.chunk_index);\n }\n return out;\n}\n\n/**\n * Runs that started and never concluded — the boot scanner's input.\n *\n * \"Concluded\" means `run_done` (finished forward) or `run_failed` with every\n * committed chunk compensated (finished backward). Anything else is a run that\n * stopped mid-flight and still owes the operator an answer.\n */\nexport async function findInterruptedRuns(engine: IObjectQLEngine): Promise<InterruptedRun[]> {\n const started = (await engine.find(\n MIGRATION_JOURNAL_OBJECT,\n { where: { kind: 'run_started' } },\n { context: { ...SYSTEM_CTX } },\n )) as MigrationJournalEvent[];\n\n const out: InterruptedRun[] = [];\n for (const start of started ?? []) {\n const events = await readRunJournal(engine, start.run_id);\n if (events.some((e) => e.kind === 'run_done')) continue;\n\n const committed = chunkSetOf(events, 'chunk_done');\n const compensated = chunkSetOf(events, 'compensated');\n const outstanding = [...committed].filter((i) => !compensated.has(i));\n // A failed run whose committed chunks were all undone is settled: it ended\n // backward, on purpose, and its rows prove it.\n if (events.some((e) => e.kind === 'run_failed') && outstanding.length === 0) continue;\n\n const unknown = [...chunkSetOf(events, 'chunk_started')].filter((i) => !committed.has(i));\n let planId = start.run_id;\n try {\n planId = start.detail ? (JSON.parse(start.detail).planId ?? start.run_id) : start.run_id;\n } catch {\n // A malformed detail payload must not hide an interrupted run — the run\n // is still reported, just without its friendly plan id.\n }\n out.push({\n runId: start.run_id,\n planId,\n planHash: start.plan_hash ?? '',\n migrationId: start.migration_id,\n startedAt: start.created_at,\n committedChunks: [...committed].sort((a, b) => a - b),\n unknownChunks: unknown.sort((a, b) => a - b),\n compensatedChunks: [...compensated].sort((a, b) => a - b),\n });\n }\n return out;\n}\n\n// ── the runner ────────────────────────────────────────────────────────────\n\nexport interface RunMigrationJournalOptions {\n /** Supply to resume an existing run; omit to start a new one. */\n readonly runId?: string;\n readonly chunkSize?: number;\n /** Injectable for deterministic tests. */\n readonly now?: () => string;\n}\n\ninterface LoadedPlan {\n readonly chunks: MigrationChunk[];\n readonly planHash: string;\n readonly rowsByStep: unknown[][];\n}\n\n/** Load every step's rows, derive the chunk plan, hash it. */\nasync function loadPlan(\n engine: IObjectQLEngine,\n plan: MigrationPlan,\n chunkSize?: number,\n): Promise<LoadedPlan> {\n const rowsByStep: unknown[][] = [];\n for (const step of plan.steps) rowsByStep.push((await step.load(engine)) ?? []);\n const chunks = planChunks(plan, rowsByStep.map((r) => r.length), chunkSize ?? plan.chunkSize);\n return { chunks, planHash: hashMigrationPlan(plan, chunks), rowsByStep };\n}\n\n/**\n * Run `plan` under the journal, or resume a run left behind by a crash.\n *\n * Refuses (never partially runs) when: the runtime cannot roll back; any\n * step's preflight fails; the plan declares `onCrash: 'compensate'` but some\n * step cannot compensate; or a resume's plan hash disagrees with the journal.\n */\nexport async function runMigrationJournal(\n engine: IObjectQLEngine,\n plan: MigrationPlan,\n options: RunMigrationJournalOptions = {},\n): Promise<MigrationRunResult> {\n const now = options.now ?? (() => new Date().toISOString());\n\n // ── capability gate ───────────────────────────────────────────────────\n // Refuse rather than degrade. A runner whose chunks are not actually\n // atomic writes `chunk_done` rows that mean nothing, and a journal that\n // cannot be trusted is worse than no journal — it will be believed.\n if (!engineCanRollBack(engine)) {\n throw new MigrationJournalRefusal(\n 'NOT_IMPLEMENTED',\n `Migration plan '${plan.id}' requires engine transaction support; this runtime cannot roll back. ` +\n `The journal's chunk_done markers would not mean \"committed\", so the run is refused rather than started.`,\n );\n }\n\n const { chunks, planHash, rowsByStep } = await loadPlan(engine, plan, options.chunkSize);\n\n // ── resume bookkeeping ────────────────────────────────────────────────\n const resuming = Boolean(options.runId);\n const runId = options.runId ?? randomUUID();\n let events: MigrationJournalEvent[] = [];\n let seq = 0;\n let committed = new Set<number>();\n let compensated = new Set<number>();\n const attemptsByChunk = new Map<number, number>();\n\n if (resuming) {\n events = await readRunJournal(engine, runId);\n if (events.length === 0) {\n throw new MigrationJournalRefusal('NO_SUCH_RUN', `No journal rows for run '${runId}'.`);\n }\n const start = events.find((e) => e.kind === 'run_started');\n if (start?.plan_hash && start.plan_hash !== planHash) {\n // The plan changed under a journal that describes the old one. Chunk 7\n // in the journal and chunk 7 in this plan are different rows; resuming\n // would skip work that was never done.\n throw new MigrationJournalRefusal(\n 'PLAN_CHANGED',\n `Refusing to resume run '${runId}': plan hash ${planHash} does not match the journal's ${start.plan_hash}. ` +\n `The chunk boundaries recorded in the journal describe a different plan.`,\n );\n }\n if (events.some((e) => e.kind === 'run_done')) {\n return {\n runId, status: 'completed', chunksTotal: chunks.length,\n chunksCommitted: chunkSetOf(events, 'chunk_done').size,\n chunksCompensated: chunkSetOf(events, 'compensated').size, planHash,\n };\n }\n seq = events.reduce((m, e) => Math.max(m, Number(e.seq) + 1), 0);\n committed = chunkSetOf(events, 'chunk_done');\n compensated = chunkSetOf(events, 'compensated');\n for (const e of events) {\n if (e.kind === 'chunk_started' && typeof e.chunk_index === 'number') {\n attemptsByChunk.set(e.chunk_index, (attemptsByChunk.get(e.chunk_index) ?? 0) + 1);\n }\n }\n }\n\n // ── preflight ─────────────────────────────────────────────────────────\n // Every validator runs before any write, so a plan that would fail at step 3\n // has not written step 1. On a resume this re-runs too: the world moved\n // while the process was dead, and the reason to refuse may have appeared\n // since.\n for (const step of plan.steps) {\n if (!step.preflight) continue;\n try {\n await step.preflight(engine);\n } catch (err) {\n throw new MigrationJournalRefusal(\n 'PREFLIGHT_FAILED',\n `Migration plan '${plan.id}' refused: preflight for step '${step.name}' failed: ${errText(err)}`,\n );\n }\n }\n\n // A plan that says \"undo me on crash\" must be able to. Discovering that it\n // cannot at compensation time means discovering it with rows already\n // written and no way back.\n if (plan.onCrash === 'compensate') {\n const missing = plan.steps.filter((s) => !s.compensate).map((s) => s.name);\n if (missing.length > 0) {\n throw new MigrationJournalRefusal(\n 'NOT_COMPENSABLE',\n `Migration plan '${plan.id}' declares onCrash: 'compensate' but step(s) ${missing.join(', ')} declare no compensate().`,\n );\n }\n }\n\n const rowsOf = (c: MigrationChunk): unknown[] => rowsByStep[c.stepIndex].slice(c.offset, c.offset + c.length);\n const next = (): number => seq++;\n\n if (!resuming) {\n await appendEvent(engine, {\n run_id: runId, seq: next(), kind: 'run_started', plan_hash: planHash,\n migration_id: plan.migrationId, created_at: now(),\n detail: JSON.stringify({\n planId: plan.id,\n onCrash: plan.onCrash ?? 'resume',\n chunks: chunks.map((c) => ({ i: c.index, step: c.stepName, offset: c.offset, length: c.length })),\n }),\n });\n }\n\n // A rediscovered run whose policy is 'compensate' does not go forward at\n // all — it unwinds what it already did and stops.\n if (resuming && plan.onCrash === 'compensate') {\n return await unwind(engine, plan, {\n runId, planHash, chunks, rowsOf, next, now,\n committed, compensated, chunksTotal: chunks.length,\n cause: new Error(`run '${runId}' rediscovered after interruption; plan policy is compensate`),\n });\n }\n\n // ── forward ───────────────────────────────────────────────────────────\n for (const chunk of chunks) {\n if (committed.has(chunk.index)) continue; // already durable — skip, do not redo\n const attempt = (attemptsByChunk.get(chunk.index) ?? 0) + 1;\n attemptsByChunk.set(chunk.index, attempt);\n const step = plan.steps[chunk.stepIndex];\n const rows = rowsOf(chunk);\n\n // Autonomous, BEFORE the transaction: this is what makes an interrupted\n // chunk visible as \"started, outcome unknown\" rather than invisible.\n await appendEvent(engine, {\n run_id: runId, seq: next(), kind: 'chunk_started',\n chunk_index: chunk.index, attempt, migration_id: plan.migrationId, created_at: now(),\n });\n\n try {\n await engine.transaction(async (trxCtx: unknown) => {\n await step.forward(rows, { runId, chunkIndex: chunk.index, attempt, context: trxCtx }, engine);\n // INSIDE the transaction — `done ⇔ committed`, not a race.\n await appendEvent(\n engine,\n {\n run_id: runId, seq: next(), kind: 'chunk_done',\n chunk_index: chunk.index, attempt, migration_id: plan.migrationId, created_at: now(),\n },\n trxCtx,\n );\n }, { ...SYSTEM_CTX });\n committed.add(chunk.index);\n } catch (err) {\n // The chunk rolled back, so nothing of it is on disk — including its\n // `chunk_done`. Unwind what earlier chunks committed.\n return await unwind(engine, plan, {\n runId, planHash, chunks, rowsOf, next, now,\n committed, compensated, chunksTotal: chunks.length, cause: err,\n });\n }\n }\n\n await appendEvent(engine, {\n run_id: runId, seq: next(), kind: 'run_done', migration_id: plan.migrationId, created_at: now(),\n });\n return {\n runId, status: 'completed', chunksTotal: chunks.length,\n chunksCommitted: committed.size, chunksCompensated: compensated.size, planHash,\n };\n}\n\ninterface UnwindArgs {\n runId: string;\n planHash: string;\n chunks: readonly MigrationChunk[];\n rowsOf: (c: MigrationChunk) => unknown[];\n next: () => number;\n now: () => string;\n committed: Set<number>;\n compensated: Set<number>;\n chunksTotal: number;\n cause: unknown;\n}\n\n/**\n * LIFO compensation over committed chunks.\n *\n * Newest-first because later chunks may depend on earlier ones; undoing in\n * commit order can hit a state the compensator was never written for.\n *\n * A compensation failure HALTS and is journalled — never swallowed, never\n * \"best effort, carry on\". Continuing past it would produce a database whose\n * state no journal describes, which is the one outcome this whole file exists\n * to prevent. The run ends `failed`, and the rows say exactly which chunk\n * resisted.\n */\nasync function unwind(\n engine: IObjectQLEngine,\n plan: MigrationPlan,\n a: UnwindArgs,\n): Promise<MigrationRunResult> {\n const order = [...a.committed].sort((x, y) => y - x); // newest-first\n for (const index of order) {\n if (a.compensated.has(index)) continue;\n const chunk = a.chunks[index];\n const step = plan.steps[chunk.stepIndex];\n\n if (!step.compensate) {\n // Nothing to undo this with. Say so loudly and stop — a silent skip\n // would leave the row written and the journal claiming a clean unwind.\n await appendEvent(engine, {\n run_id: a.runId, seq: a.next(), kind: 'run_failed',\n chunk_index: index, migration_id: plan.migrationId, created_at: a.now(),\n detail: JSON.stringify({\n phase: 'compensate', reason: 'step declares no compensate()',\n step: step.name, cause: errText(a.cause),\n }),\n });\n return {\n runId: a.runId, status: 'failed', chunksTotal: a.chunksTotal,\n chunksCommitted: a.committed.size, chunksCompensated: a.compensated.size,\n planHash: a.planHash, error: a.cause,\n };\n }\n\n const attempt = 1;\n try {\n await engine.transaction(async (trxCtx: unknown) => {\n await step.compensate!(a.rowsOf(chunk), { runId: a.runId, chunkIndex: index, attempt, context: trxCtx }, engine);\n await appendEvent(\n engine,\n {\n run_id: a.runId, seq: a.next(), kind: 'compensated',\n chunk_index: index, attempt, migration_id: plan.migrationId, created_at: a.now(),\n },\n trxCtx,\n );\n }, { ...SYSTEM_CTX });\n a.compensated.add(index);\n } catch (err) {\n await appendEvent(engine, {\n run_id: a.runId, seq: a.next(), kind: 'run_failed',\n chunk_index: index, migration_id: plan.migrationId, created_at: a.now(),\n detail: JSON.stringify({\n phase: 'compensate', step: step.name,\n error: errText(err), cause: errText(a.cause),\n }),\n });\n return {\n runId: a.runId, status: 'failed', chunksTotal: a.chunksTotal,\n chunksCommitted: a.committed.size, chunksCompensated: a.compensated.size,\n planHash: a.planHash, error: err,\n };\n }\n }\n\n await appendEvent(engine, {\n run_id: a.runId, seq: a.next(), kind: 'run_failed',\n migration_id: plan.migrationId, created_at: a.now(),\n detail: JSON.stringify({ phase: 'forward', error: errText(a.cause), compensated: [...a.compensated].sort((x, y) => x - y) }),\n });\n return {\n runId: a.runId, status: 'compensated', chunksTotal: a.chunksTotal,\n chunksCommitted: a.committed.size, chunksCompensated: a.compensated.size,\n planHash: a.planHash, error: a.cause,\n };\n}\n\n/** Resume a run the journal says was interrupted. Thin alias for intent at call sites. */\nexport async function resumeMigrationJournal(\n engine: IObjectQLEngine,\n plan: MigrationPlan,\n runId: string,\n options: Omit<RunMigrationJournalOptions, 'runId'> = {},\n): Promise<MigrationRunResult> {\n return runMigrationJournal(engine, plan, { ...options, runId });\n}\n\nfunction errText(err: unknown): string {\n if (err instanceof Error) return err.message;\n try {\n return String(err);\n } catch {\n return '<unprintable error>';\n }\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Runtime resolution of filter placeholders — the server-side half of the\n * `{token}` contract that `@objectstack/spec` declares (framework#3582).\n *\n * `date-macros.zod.ts` and `context-tokens.zod.ts` freeze the *vocabulary*;\n * `@objectstack/lint`'s `validate-filter-tokens` rejects a token outside it at\n * authoring time. Neither one ever substituted a value: every server-side\n * consumer handed the literal `'{current_year_start}'` to the database, where\n * it compared as a string and matched nothing. The failure was invisible —\n * an empty widget, an unfiltered list — so apps worked around it by computing\n * dates at module load, freezing \"this year\" into the built artifact.\n *\n * This module is the missing evaluator. It walks a filter tree and replaces\n * every fully-wrapped placeholder with a concrete value:\n *\n * { close_date: { $gte: '{current_year_start}' } }\n * → { close_date: { $gte: '2026-01-01' } }\n * { owner: '{current_user_id}' }\n * → { owner: 'usr_7f3a…' }\n *\n * # Output form: ISO strings, not driver-native values\n *\n * Date tokens resolve to `YYYY-MM-DD` (or a full ISO timestamp for the\n * sub-day tokens `{now}` / `{N_hours_ago}` / `{N_minutes_ago}`), exactly the\n * form the spec's module doc promises the data engine sees. Translating that\n * to a column's on-disk form is the DRIVER's job and already exists —\n * `SqlDriver.coerceFilterValue` / `temporalFilterValue` turn an ISO comparand\n * into SQLite epoch-ms or leave it alone on native-timestamp dialects. Emitting\n * a driver-native value here would fork that convention into a second source of\n * truth and break the moment a query crosses datasources.\n *\n * # Period `_end` is the last calendar DAY, not the last instant\n *\n * `{current_year_end}` is `2026-12-31`, per the spec's own\n * `DATE_MACRO_DESCRIPTIONS` (\"Dec 31 of this year\"). On a `datetime` column\n * that means `<= {current_year_end}` excludes everything after midnight on the\n * 31st — the classic half-open-range trap. Authors filtering a timestamp want\n * `< {next_year_start}`. This is a documented property of the vocabulary, not\n * something the resolver may quietly \"fix\": silently widening a bound would\n * make the same token mean different things on different column types.\n *\n * # An unknown token throws\n *\n * A value that is entirely `{something}` is a placeholder by construction — no\n * author means the literal six characters `{foo}`. Passing an unrecognised one\n * through is precisely the silent-zero bug this module exists to end, so it is\n * a hard error carrying the near-miss suggestion (`{current_user}` →\n * `{current_user_id}`). Values that merely CONTAIN braces are left untouched.\n *\n * \"Entirely `{something}`\" means ANY character between the braces (#5586).\n * Until then the recognition grammar was the token-NAME grammar\n * (`[a-zA-Z0-9_]+`), so a placeholder carrying a non-word character —\n * `{TODAY()}`, `{current-user-id}`, `{30 days ago}`, `{user.id}` — was not\n * recognised as a token at all and fell straight through to the literal\n * comparison this module exists to abolish. The failure was inverted against\n * the author: `{TODAY}` threw (diagnostic working), `{TODAY()}` returned rows\n * (diagnostic bypassed) — and the parenthesised, kebab-case and\n * natural-language spellings are exactly what an author migrating from another\n * system's macro syntax reaches for first. See `FILTER_TOKEN_WRAPPED_RE` in\n * `@objectstack/spec`.\n */\n\nimport {\n classifyFilterToken,\n parseDateMacroParam,\n type DateMacroUnit,\n} from '@objectstack/spec/data';\nimport { calendarPartsInTzOrUtc } from './datetime.js';\n\n/**\n * The slice of an execution context the resolver reads. Structural on purpose —\n * see {@link filterTokenContextFrom}.\n */\nexport interface ExecutionContextLike {\n readonly userId?: string;\n readonly tenantId?: string;\n readonly timezone?: string;\n}\n\n/**\n * The request-scoped values a placeholder can resolve against.\n *\n * `now` is captured ONCE per resolve call so every token in one filter shares\n * an instant — otherwise a `$gte {current_month_start}` / `$lt\n * {next_month_start}` pair evaluated microseconds apart could straddle a\n * month boundary and silently drop a row.\n */\nexport interface FilterTokenResolutionContext {\n /** Reference instant. Defaults to `new Date()` at call time. */\n now?: Date;\n /** IANA reference timezone for calendar boundaries. Defaults to UTC. */\n timezone?: string;\n /** Resolves `{current_user_id}`. */\n userId?: string;\n /** Resolves `{current_org_id}`. */\n orgId?: string;\n}\n\n/**\n * Raised when a filter carries a placeholder outside the vocabulary.\n *\n * Carries `status`/`code` so the REST layer's generic 4xx passthrough maps it\n * to a **400 with a fixable message** rather than a 500: the caller's filter is\n * malformed, the server is fine. (Same convention plugin-sharing uses for its\n * record-scope denial — no runtime dependency in either direction.)\n */\nexport class UnknownFilterTokenError extends Error {\n readonly token: string;\n readonly suggestion?: string;\n readonly status = 400;\n readonly code = 'FILTER_TOKEN_UNKNOWN';\n\n constructor(token: string, suggestion?: string) {\n super(\n `Unresolvable filter placeholder \"{${token}}\". ` +\n (suggestion\n ? `Did you mean \"{${suggestion}}\"? `\n : 'Resolvable placeholders are the context tokens ({current_user_id}, ' +\n '{current_org_id}) and the date macros ({today}, {current_quarter_start}, ' +\n '{30_days_ago}, …). ') +\n 'Sending it to the data engine verbatim would compare it as a literal ' +\n 'string and match nothing, which is indistinguishable from an empty result.',\n );\n this.name = 'UnknownFilterTokenError';\n this.token = token;\n this.suggestion = suggestion;\n }\n}\n\n/**\n * Raised when a token IS in the vocabulary but the request carries no value\n * for it — an unauthenticated caller filtering on `{current_user_id}`.\n *\n * Distinct from {@link UnknownFilterTokenError} because the fix is different:\n * the metadata is correct, the context is not. Never silently resolves to\n * `null`/`undefined`, which on most drivers degrades to `IS NULL` and would\n * quietly hand back rows the filter was written to exclude.\n */\nexport class UnresolvedFilterTokenError extends Error {\n readonly token: string;\n /** 400, not 500 — see {@link UnknownFilterTokenError}. */\n readonly status = 400;\n readonly code = 'FILTER_TOKEN_UNRESOLVED';\n\n constructor(token: string, detail: string) {\n super(`Filter placeholder \"{${token}}\" cannot be resolved: ${detail}`);\n this.name = 'UnresolvedFilterTokenError';\n this.token = token;\n }\n}\n\n/** `YYYY-MM-DD` for a calendar day, zero-padded. */\nfunction ymd(year: number, month: number, day: number): string {\n const p = (n: number) => String(n).padStart(2, '0');\n return `${year}-${p(month)}-${p(day)}`;\n}\n\n/**\n * Calendar arithmetic is done on a UTC \"proxy\" date built from the reference\n * timezone's calendar parts. Working in UTC keeps the math free of DST jumps\n * (a local-midnight `Date` can shift by an hour when `setMonth` crosses a\n * transition); the zone only decides WHICH calendar day \"now\" is, which\n * {@link calendarPartsInTzOrUtc} answers from the platform tz database.\n */\nfunction proxyDay(now: Date, timezone?: string): Date {\n const { year, month, day } = calendarPartsInTzOrUtc(now, timezone);\n return new Date(Date.UTC(year, month - 1, day));\n}\n\nconst asYmd = (d: Date): string => ymd(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());\n\ntype PeriodKind = 'week' | 'month' | 'quarter' | 'year';\n\n/** Monday-based week start — matches the spec's \"Monday 00:00 of this week\". */\nfunction startOfPeriod(kind: PeriodKind, d: Date): Date {\n const r = new Date(d.getTime());\n switch (kind) {\n case 'week': {\n const dow = (r.getUTCDay() + 6) % 7; // 0 = Monday\n r.setUTCDate(r.getUTCDate() - dow);\n return r;\n }\n case 'month':\n return new Date(Date.UTC(r.getUTCFullYear(), r.getUTCMonth(), 1));\n case 'quarter':\n return new Date(Date.UTC(r.getUTCFullYear(), Math.floor(r.getUTCMonth() / 3) * 3, 1));\n case 'year':\n return new Date(Date.UTC(r.getUTCFullYear(), 0, 1));\n }\n}\n\n/** Days in the given (0-based) month of `year`. */\nfunction daysInMonth(year: number, month: number): number {\n return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();\n}\n\n/**\n * Shift `d` by `n` months, CLAMPING the day to the target month's length.\n *\n * Bare `setUTCMonth(m - 1)` on the 31st rolls FORWARD into the following month\n * (Mar 31 minus one month = \"Feb 31\" = Mar 3), which would make\n * `{1_month_ago}` land after `{today}` on five days of the year. Clamping to\n * Feb 28 is what every calendar library does and the only answer an author\n * would call correct.\n */\nfunction addMonthsClamped(d: Date, n: number): Date {\n const year = d.getUTCFullYear();\n const month = d.getUTCMonth() + n;\n const targetYear = year + Math.floor(month / 12);\n const targetMonth = ((month % 12) + 12) % 12;\n const day = Math.min(d.getUTCDate(), daysInMonth(targetYear, targetMonth));\n return new Date(Date.UTC(\n targetYear, targetMonth, day,\n d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds(),\n ));\n}\n\n/** Shift `d` by `n` whole periods of `kind` (negative shifts backwards). */\nfunction addPeriods(kind: PeriodKind, d: Date, n: number): Date {\n switch (kind) {\n case 'week': {\n const r = new Date(d.getTime());\n r.setUTCDate(r.getUTCDate() + n * 7);\n return r;\n }\n case 'month': return addMonthsClamped(d, n);\n case 'quarter': return addMonthsClamped(d, n * 3);\n case 'year': return addMonthsClamped(d, n * 12);\n }\n}\n\n/** Shift `d` by `n` units of the parameterised grammar. */\nfunction addUnits(unit: DateMacroUnit, d: Date, n: number): Date {\n const r = new Date(d.getTime());\n switch (unit) {\n case 'minute': r.setUTCMinutes(r.getUTCMinutes() + n); return r;\n case 'hour': r.setUTCHours(r.getUTCHours() + n); return r;\n case 'day': r.setUTCDate(r.getUTCDate() + n); return r;\n case 'week': r.setUTCDate(r.getUTCDate() + n * 7); return r;\n // Month/year steps clamp rather than overflow — see addMonthsClamped.\n case 'month': return addMonthsClamped(d, n);\n case 'year': return addMonthsClamped(d, n * 12);\n }\n}\n\n/**\n * `current|last|next` × `week|month|quarter|year` × `start|end`, plus the bare\n * `week_start`-style aliases (which mean `current_`). Returns `undefined` when\n * the token is not a period token.\n */\nconst PERIOD_RE = /^(?:(current|last|next)_)?(week|month|quarter|year)_(start|end)$/;\n\nfunction resolvePeriodToken(token: string, today: Date): string | undefined {\n const m = PERIOD_RE.exec(token);\n if (!m) return undefined;\n const rel = (m[1] ?? 'current') as 'current' | 'last' | 'next';\n const kind = m[2] as PeriodKind;\n const bound = m[3] as 'start' | 'end';\n const offset = rel === 'last' ? -1 : rel === 'next' ? 1 : 0;\n\n // Normalize to the period's own start BEFORE stepping. Day 1 of a\n // month/quarter/year (and a Monday) shifts exactly — no month-length clamping\n // is involved at all — so the answer never depends on the clamp policy, and\n // the arithmetic reads the same for every `kind`.\n const periodStart = startOfPeriod(kind, addPeriods(kind, startOfPeriod(kind, today), offset));\n if (bound === 'start') return asYmd(periodStart);\n // `_end` = the last calendar DAY of the period: the day before the next\n // period begins. See the module doc on half-open ranges.\n const next = addPeriods(kind, periodStart, 1);\n next.setUTCDate(next.getUTCDate() - 1);\n return asYmd(next);\n}\n\n/**\n * Resolve one token NAME (the bit inside the braces) to its concrete value.\n * Throws {@link UnresolvedFilterTokenError} for a vocabulary token the request\n * carries no value for. Returns `undefined` only when the token is outside the\n * vocabulary — callers turn that into {@link UnknownFilterTokenError}.\n */\nexport function resolveFilterToken(\n token: string,\n ctx: FilterTokenResolutionContext = {},\n): unknown {\n const now = ctx.now ?? new Date();\n\n // ── Context tokens ────────────────────────────────────────────────────\n if (token === 'current_user_id') {\n if (!ctx.userId) {\n throw new UnresolvedFilterTokenError(\n token,\n 'the request has no authenticated user. A filter scoped to the signed-in ' +\n 'user cannot run for an anonymous or system caller — gate the surface on ' +\n 'authentication, or drop the token from the filter.',\n );\n }\n return ctx.userId;\n }\n if (token === 'current_org_id') {\n if (!ctx.orgId) {\n throw new UnresolvedFilterTokenError(\n token,\n 'the request carries no active organization (ExecutionContext.tenantId is ' +\n 'unset). Set the active org on the request, or drop the token from the filter.',\n );\n }\n return ctx.orgId;\n }\n\n // ── Date macros ───────────────────────────────────────────────────────\n const today = proxyDay(now, ctx.timezone);\n\n switch (token) {\n case 'now': return now.toISOString();\n case 'today': return asYmd(today);\n case 'yesterday': return asYmd(addUnits('day', today, -1));\n case 'tomorrow': return asYmd(addUnits('day', today, 1));\n }\n\n const period = resolvePeriodToken(token, today);\n if (period !== undefined) return period;\n\n const param = parseDateMacroParam(token);\n if (param) {\n const sign = param.direction === 'ago' ? -1 : 1;\n // Sub-day units are instants — they must keep their time-of-day, so they\n // shift `now` and render as a full ISO timestamp. Day-and-coarser units are\n // calendar quantities and render as `YYYY-MM-DD` off the reference day.\n if (param.unit === 'minute' || param.unit === 'hour') {\n return addUnits(param.unit, now, sign * param.n).toISOString();\n }\n return asYmd(addUnits(param.unit, today, sign * param.n));\n }\n\n return undefined;\n}\n\n/**\n * Does this tree contain any fully-wrapped placeholder at all?\n *\n * A read-only pre-pass so the overwhelmingly common case — an internal query\n * whose filter is entirely literal — costs one allocation-free walk instead of\n * a full structural copy. This runs on every server-side read, so \"no\n * placeholders\" must be close to free.\n */\nfunction hasFilterToken(node: unknown): boolean {\n if (typeof node === 'string') return classifyFilterToken(node) !== null;\n if (Array.isArray(node)) return node.some(hasFilterToken);\n if (node && typeof node === 'object' && !(node instanceof Date)) {\n return Object.values(node as Record<string, unknown>).some(hasFilterToken);\n }\n return false;\n}\n\n/**\n * Deep-replace every fully-wrapped placeholder in `filter` with its resolved\n * value, returning a NEW tree (the caller's metadata is never mutated — a view\n * or dataset definition is shared across requests, so resolving in place would\n * bake one request's user id, and one day's dates, into every later render).\n *\n * Returns the input unchanged, by reference, when it holds no placeholders.\n */\nexport function resolveFilterTokens<T>(\n filter: T,\n ctx: FilterTokenResolutionContext = {},\n): T {\n if (filter == null) return filter;\n if (!hasFilterToken(filter)) return filter;\n\n // One instant for the whole tree (see FilterTokenResolutionContext.now).\n const pinned: FilterTokenResolutionContext = { ...ctx, now: ctx.now ?? new Date() };\n\n const walk = (node: unknown): unknown => {\n if (typeof node === 'string') {\n const cls = classifyFilterToken(node);\n if (!cls) return node;\n if (cls.kind === 'unknown') throw new UnknownFilterTokenError(cls.token, cls.suggestion);\n const resolved = resolveFilterToken(cls.token, pinned);\n // `classifyFilterToken` already vouched for the token, so `undefined`\n // here would mean the spec vocabulary and this resolver have drifted\n // apart — surface it loudly rather than silently emitting `undefined`.\n if (resolved === undefined) throw new UnknownFilterTokenError(cls.token);\n return resolved;\n }\n if (Array.isArray(node)) return node.map(walk);\n if (node && typeof node === 'object') {\n // Dates and other class instances are comparands, not filter structure.\n if (node instanceof Date) return node;\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(node as Record<string, unknown>)) out[k] = walk(v);\n return out;\n }\n return node;\n };\n\n return walk(filter) as T;\n}\n\n/**\n * Convenience bridge from an execution context to the resolver's inputs.\n * `{current_org_id}` reads `tenantId` — the active organization IS the tenant\n * on the read path (same value the RLS compiler binds to\n * `current_user.organization_id`).\n *\n * Typed structurally, not as `ExecutionContext`, so both the parsed context\n * (`ExecutionContextParsed`, defaults applied) and the pre-parse\n * `ExecutionContext` a caller holds mid-pipeline satisfy it. The three fields\n * read here are optional in both.\n */\nexport function filterTokenContextFrom(\n execCtx: ExecutionContextLike | undefined,\n now?: Date,\n): FilterTokenResolutionContext {\n return {\n now,\n timezone: execCtx?.timezone,\n userId: execCtx?.userId,\n orgId: execCtx?.tenantId,\n };\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * [#8690] Can a temporal field's storage rule READ this comparand at all?\n *\n * The one predicate behind the engine's temporal-comparand door\n * (`@objectstack/objectql`, `temporal-comparand-door.ts`) and the analytics\n * raw-SQL decline (`@objectstack/service-analytics`,\n * `NativeSQLStrategy.canHandle`). It lives in `core` because those two packages\n * do not depend on each other and a rule that exists twice is a rule that will\n * disagree with itself — the shape the 2026-08-12 ruling named by name.\n *\n * ## The defect it exists to close\n *\n * A `datetime` field filtered with a bare string the API cannot take literally\n * — `last_30_days`, `not-a-date-at-all` — was bound AS-IS all the way to the\n * driver, where the comparison is false for every row. Measured end to end on\n * `InMemoryDriver` with 51 rows seeded / 38 in-window:\n *\n * ```\n * $gte \"last_30_days\" HTTP 200 count=0 <- silent zero (the defect)\n * $gte \"not-a-date-at-all\" HTTP 200 count=0 <- silent zero\n * $gte \"{30_days_ago}\" HTTP 200 count=38 <- the positive control\n * $gte \"{TODAY}\" REFUSED FILTER_TOKEN_UNKNOWN / 400\n * ```\n *\n * A `{placeholder}` the resolver does not know is refused loudly; a bare string\n * that is not a date was not validated at all. An empty chart is the hardest\n * failure to debug — it is indistinguishable from \"there is genuinely no data\".\n *\n * ## Why the KIND is an argument, and not something this file works out\n *\n * \"Uninterpretable comparand\" is a field-TYPED judgement, and `packages/spec`\n * says so outright (`filter.zod.ts`): a filter schema \"is field-AGNOSTIC … it\n * never sees which column the operator is applied to\". This module therefore\n * answers only the VALUE half — given a kind, can that kind's storage rule read\n * this value — and the caller, which owns object metadata, supplies the kind.\n * That split is what lets the rule sit below both consumers without dragging\n * field metadata down with it.\n *\n * ## Interpretability is defined by the DRIVERS' own totals, deliberately\n *\n * Each predicate below mirrors the total function that would receive the value\n * if the door let it through — `storageDatetimeValue` / `storageDateValue` /\n * `storageTimeValue` in `driver-memory`, and their `SqlDriver` twins. Those\n * functions are total on purpose: an input they cannot interpret is returned\n * UNCHANGED rather than becoming an invented instant. So \"the driver would\n * return it unchanged\" IS the definition of uninterpretable, and defining it\n * any other way would refuse comparands that work today.\n *\n * That is why this is not `utcInstantMs` (`@objectstack/spec/data`), which is\n * the stricter canonical reader: it rejects a bare epoch-millisecond string and\n * every non-ISO spelling `Date.parse` accepts, both of which the drivers read\n * correctly today. Refusing those would be a narrowing this card did not ask\n * for and no measurement supports.\n *\n * ## Two things it deliberately does NOT judge\n *\n * - **Non-string comparands.** A number is epoch milliseconds, a `Date` is an\n * instant, `null` is a null test. The refusal scopes to strings by ruling.\n * - **The EMPTY string.** Measured, `$gte \"\"` binds as `''` and every canonical\n * UTC text sorts at or above it, so it returns every non-null row — a third\n * behaviour again, and one the maintainer ruled stays its own card: \"B and C\n * scope to non-empty strings and must not decide it in passing\". A\n * whitespace-only string is the same cell (the drivers `trim()` before they\n * test), so it is left alone too.\n */\n\nimport { classifyFilterToken } from '@objectstack/spec/data';\n\n/** Which temporal storage rule a declared field takes. */\nexport type TemporalComparandKind = 'datetime' | 'date' | 'time';\n\n/**\n * The kind a declared field's `type` takes, or `null` for every non-temporal\n * field.\n *\n * The same three-way split `driver-memory`'s `indexTemporalFields` and\n * `SqlDriver.temporalFieldKind` make, so the door and the drivers cannot\n * disagree about which fields are temporal at all.\n */\nexport function temporalComparandKind(fieldType: unknown): TemporalComparandKind | null {\n if (fieldType === 'datetime') return 'datetime';\n if (fieldType === 'date') return 'date';\n if (fieldType === 'time') return 'time';\n return null;\n}\n\n/**\n * `storageDatetimeValue`'s reading, as a yes/no.\n *\n * Mirrors it step for step: a bare integer in either sign is epoch\n * milliseconds; a bare `YYYY-MM-DD` is midnight UTC; a zone-naive\n * `YYYY-MM-DD[ T]HH:MM[:SS[.fff]]` has its wall clock read AS UTC; anything\n * else is handed to `Date.parse` exactly as the drivers hand it over.\n */\nfunction readsAsInstant(s: string): boolean {\n if (/^-?\\d+$/.test(s)) return Number.isFinite(new Date(Number(s)).getTime());\n const iso = /^\\d{4}-\\d{2}-\\d{2}$/.test(s)\n ? `${s}T00:00:00.000Z`\n : /^\\d{4}-\\d{2}-\\d{2}[ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?$/.test(s)\n ? `${s.replace(' ', 'T')}Z`\n : s;\n return Number.isFinite(Date.parse(iso));\n}\n\n/**\n * `storageDateValue`'s reading of a STRING: its leading `YYYY-MM-DD`, and\n * nothing else.\n *\n * Narrower than {@link readsAsInstant} on purpose, because the rule it mirrors\n * is narrower: `storageDateValue` collapses a leading calendar day and returns\n * every other string untouched. `2026/07/15` is therefore uninterpretable for a\n * `date` column even though `Date.parse` reads it — today it survives to the\n * driver and compares as text against `YYYY-MM-DD` values, which is the silent\n * wrong answer this door exists to stop.\n */\nfunction readsAsCalendarDay(s: string): boolean {\n return /^\\d{4}-\\d{2}-\\d{2}/.test(s);\n}\n\n/**\n * `storageTimeValue`'s reading: a bare wall clock whose components are in\n * range. Out-of-range (`25:00`) is uninterpretable — the rule it mirrors\n * returns such a value untouched rather than wrapping it.\n */\nfunction readsAsWallClock(s: string): boolean {\n const m = /^(\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.(\\d+))?)?$/.exec(s);\n if (!m) return false;\n return Number(m[1]) <= 23 && Number(m[2]) <= 59 && Number(m[3] ?? '0') <= 59;\n}\n\n/**\n * Is `value` a comparand that a `kind` column's storage rule cannot read?\n *\n * `true` ONLY for a non-empty, non-placeholder STRING that the kind's rule\n * would hand back unchanged. Everything else — a number, a `Date`, `null`, a\n * `{ $field }` reference, filter structure, the empty string, a `{token}` —\n * answers `false`, each for a reason recorded in the module note or below.\n *\n * A `{placeholder}` is stepped around rather than judged because it is another\n * layer's vocabulary and that layer already refuses the unknown ones loudly\n * (`FILTER_TOKEN_UNKNOWN` / 400, with the resolvable tokens listed). Both doors\n * that call this run BEFORE token resolution, so judging a placeholder here\n * would refuse `{30_days_ago}` — the platform's own correct spelling, and the\n * positive control this fix is pinned against.\n */\nexport function isUninterpretableTemporalComparand(\n kind: TemporalComparandKind,\n value: unknown,\n): boolean {\n if (typeof value !== 'string') return false;\n const s = value.trim();\n // The empty-string cell is its own card — see the module note.\n if (s === '') return false;\n // Another layer's vocabulary, and it has its own loud refusal.\n if (classifyFilterToken(value) !== null) return false;\n if (kind === 'datetime') return !readsAsInstant(s);\n if (kind === 'date') return !readsAsCalendarDay(s);\n return !(readsAsWallClock(s) || readsAsInstant(s));\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * [ADR-0126 §4] THE activation-ledger row contract — one implementation,\n * parameterized by `metadata_type`.\n *\n * ## Why this file exists (#12350)\n *\n * ADR-0126 §4 declares ONE activation ledger for the whole disable+clone\n * family. It briefly had two implementations of that one row contract:\n *\n * | Implementation | Package | Landed |\n * | :-------------------------------- | :--------------------------------- | :----- |\n * | `ObjectStoreFlowActivationStore` | `@objectstack/service-automation` | #12296 |\n * | `ObjectStoreActionActivationStore`| `@objectstack/objectql` | #12348 |\n *\n * They agreed on every load-bearing detail because the second was written from\n * the first — and nothing structurally held them together. ADR-0126 §8\n * pre-charts `tool`, `skill` and `position` as later consumers, and a third\n * and fourth copy is where the row semantics start drifting: the org-row skip\n * and the `0`-is-false read are exactly the kind of detail a copy loses\n * quietly, in a direction (an artifact silently re-arming) nothing else\n * measures.\n *\n * ## Why the code lives HERE and the object does not\n *\n * Neither consumer could import the other: `@objectstack/service-automation`\n * does not depend on `@objectstack/objectql` (devDependency only), and the\n * engine must not depend on a service — the dependency arrow points the other\n * way. `@objectstack/core` is the package BOTH already depend on, so this is\n * the one home that needs no new edge. ⛔ NOT `@objectstack/platform-objects`,\n * which declares the OBJECT: `objectql` does not depend on it and adding that\n * edge would invert the tiering, since platform-objects is a catalog the\n * engine serves. That is a MODULE-IMPORT question, and it is independent of\n * where the object's REGISTRATION lives (a composition question, ruled\n * separately on #12359 — `PlatformObjectsPlugin`).\n *\n * The table is reached by NAME, never by importing the declaration, exactly as\n * the engine already reaches `sys_metadata` / `sys_secret`.\n *\n * ## The row shape — ⛔ this module writes COLUMNS, never schema\n *\n * `metadata_type` · `name` · `package_id` · `active`, exactly the four\n * ADR-0126 §4 declares. Three properties are load-bearing and each is pinned on\n * both consumers' sides (`flow-activation-ledger.test.ts`,\n * `action-activation.test.ts` — unchanged by the consolidation, which is what\n * makes them the proof it lost nothing):\n *\n * - **The ledger is DEPLOYMENT-level, and carries no tenant column at all.**\n * A row says \"this environment switched this managed item off\" — a fact no\n * organization owns. The table briefly declared a nullable tenant column\n * marked RESERVED and never written, and this module correspondingly\n * filtered reads to the NULL ones and skipped any row carrying an\n * organization. Both are gone: a reserved nullable tenant\n * column is the shape the total-organization-ownership record proposed in\n * PR #14976 rules out, so the column was dropped before it ever shipped\n * (17.2.0 predates the table). There is no filter here any more because\n * there is no column to filter on — `list()` is simply every activation\n * row of this type. Should a per-organization dimension ever be wanted, it\n * returns as a separate org-owned object, never as a column here.\n * - **Absence of a row means ACTIVE.** Nothing here ever writes a row to say\n * \"active by default\", and `list()` returning nothing is the normal\n * stock-boot state, not an error. Re-enabling UPDATES the row to\n * `active: true` rather than deleting it, so the ledger records the\n * administrator's CHOICE instead of erasing it (§6 wall 3) — which is why\n * {@link MetadataActivationStoreEngine} deliberately has no `delete`.\n * - **A driver `0` reads as false.** SQLite/libsql round-trip booleans as\n * 0/1; a `!== false` test alone would read a disabled artifact as armed.\n *\n * ## The discriminator is never optional\n *\n * The ledger is generic and shared — flow rows and action rows live in the\n * same table today, and §8 charts more. Every read and write below is scoped\n * by `metadata_type`, so no consumer can touch a neighbour's state through a\n * table all of them are told to treat as generic. It is a constructor\n * argument, not a per-call one, so a caller cannot forget it at a single site.\n */\n\n/**\n * The ledger table. A NAME, not an import: the object is declared in\n * `@objectstack/platform-objects` and this package must not depend on it.\n */\nexport const METADATA_ACTIVATION_TABLE = 'sys_metadata_activation';\n\n/** Infrastructure rows, not tenant data — the `sys_metadata_activation` posture. */\nconst SYSTEM_CTX = { isSystem: true, positions: [], permissions: [] } as const;\n\n/**\n * [ADR-0126 §4] One packaged artifact's install-level activation row.\n *\n * The ledger's own columns are `metadata_type` / `name` / `package_id` /\n * `active`; `metadata_type` is fixed by the store, so it never reaches a\n * consumer's projection.\n */\nexport interface MetadataActivationRow {\n /** The packaged artifact's declarative machine name (ADR-0126 §4). */\n name: string;\n /** The package that ships the base artifact. */\n packageId: string;\n /** Is the packaged artifact armed for this installation. */\n active: boolean;\n}\n\n/**\n * [ADR-0126 §4] The durable off-switch for one class of packaged artifact.\n *\n * Absence of a row means the packaged default — ACTIVE — so a runtime with no\n * store attached, or a store with no rows, behaves exactly as a stock boot\n * always has.\n */\nexport interface MetadataActivationStore {\n /** Every activation row for this type — the ledger is deployment-wide. */\n list(): Promise<MetadataActivationRow[]>;\n /** Insert or update the row for one packaged artifact. */\n setActive(row: MetadataActivationRow): Promise<void>;\n}\n\n/**\n * The exact engine slice this store needs: a keyed read, an insert and an\n * update. Deliberately WITHOUT `delete` — re-enabling updates the `active`\n * bit, it never removes the row (see the module header), and demanding only\n * what is used keeps every test double honest about that.\n */\nexport interface MetadataActivationStoreEngine {\n find(object: string, options?: any): Promise<any[]>;\n insert(object: string, data: any, options?: any): Promise<any>;\n update(object: string, data: any, options?: any): Promise<any>;\n}\n\n/**\n * In-memory {@link MetadataActivationStore} — process-lifetime only, for tests\n * and for hosts with no durable plane. What it lacks versus the ObjectStore\n * implementation is DURABILITY, which is exactly the property ADR-0126 §6\n * wall 3 asks for; it is not a sanctioned production off-switch.\n *\n * No discriminator: an in-memory map is per-instance, so there is no shared\n * table for a neighbouring type's rows to be in.\n */\nexport class InMemoryMetadataActivationStore implements MetadataActivationStore {\n private readonly rows = new Map<string, MetadataActivationRow>();\n\n async list(): Promise<MetadataActivationRow[]> {\n return [...this.rows.values()];\n }\n\n async setActive(row: MetadataActivationRow): Promise<void> {\n this.rows.set(row.name, { ...row });\n }\n}\n\n/**\n * Durable {@link MetadataActivationStore} backed by the\n * `sys_metadata_activation` object (ADR-0126 §4), scoped to one\n * `metadata_type`.\n *\n * All access uses a system context: the object is `managedBy: 'engine-owned'`\n * and declares `apiMethods: ['get', 'list']`, i.e. the generic data API cannot\n * write it at all — these rows are written by the ADR-0126 enable/disable\n * doors and by nothing else.\n */\nexport class ObjectStoreMetadataActivationStore implements MetadataActivationStore {\n constructor(\n private readonly engine: MetadataActivationStoreEngine,\n /**\n * The ledger's `metadata_type` discriminator for this consumer —\n * `'flow'`, `'action'`, … Required, and fixed for the store's\n * lifetime: see the module header on why it is never a per-call\n * argument.\n */\n private readonly metadataType: string,\n ) {}\n\n /**\n * Every row of this type. Read once at boot to hydrate the consumer's\n * projection.\n *\n * The only scoping is the `metadata_type` discriminator: the ledger is\n * deployment-wide and has no tenant column, so there is no second axis to\n * filter on (see the module header).\n */\n async list(): Promise<MetadataActivationRow[]> {\n const rows = await this.engine.find(METADATA_ACTIVATION_TABLE, {\n where: { metadata_type: this.metadataType },\n context: SYSTEM_CTX,\n });\n if (!Array.isArray(rows)) return [];\n const out: MetadataActivationRow[] = [];\n for (const row of rows) {\n const r = row as { name?: unknown; package_id?: unknown; active?: unknown };\n if (typeof r.name !== 'string' || !r.name) continue;\n out.push({\n name: r.name,\n packageId: typeof r.package_id === 'string' ? r.package_id : '',\n // The column defaults to `true`; only an explicit `false`\n // disarms. A driver that round-trips booleans as 0/1\n // (SQLite/libsql) is read through the same `=== false || === 0`\n // test, so a `0` is not mistaken for `true`.\n active: !(r.active === false || r.active === 0),\n });\n }\n return out;\n }\n\n /**\n * Insert or update the row for one packaged artifact.\n *\n * Read-then-write rather than a blind upsert because the object's\n * uniqueness is a DECLARED index (`unique: 'global'` over\n * `(metadata_type, name)`), not a primary key this store controls: there is\n * no id to collide on, so an insert-and-catch could not tell \"already\n * there\" from a real store failure.\n *\n * That index is also why taking the FIRST match is taking the only one: the\n * read below is keyed on exactly the index's two columns, so it can match\n * at most one row. It used to pick the first row with a NULL organization\n * out of the result, back when the table carried a reserved tenant column;\n * with no such column the set it was choosing from can no longer hold more\n * than one member.\n */\n async setActive(row: MetadataActivationRow): Promise<void> {\n const existing = await this.engine.find(METADATA_ACTIVATION_TABLE, {\n where: { metadata_type: this.metadataType, name: row.name },\n context: SYSTEM_CTX,\n });\n const current = Array.isArray(existing) ? existing[0] : undefined;\n\n if (current && (current as { id?: unknown }).id != null) {\n await this.engine.update(\n METADATA_ACTIVATION_TABLE,\n { id: (current as { id: unknown }).id, active: row.active, package_id: row.packageId },\n { context: SYSTEM_CTX },\n );\n return;\n }\n\n await this.engine.insert(\n METADATA_ACTIVATION_TABLE,\n {\n metadata_type: this.metadataType,\n name: row.name,\n package_id: row.packageId,\n active: row.active,\n },\n { context: SYSTEM_CTX },\n );\n }\n\n /**\n * Read the backing table once so a misconfiguration surfaces at BOOT\n * rather than as a failed toggle later. Throws the driver error verbatim —\n * `no such table: sys_metadata_activation` means the object was never\n * registered (or its schema never synced) in this composition.\n *\n * ⚠️ Unscoped by design: the question is \"does the TABLE read at all\",\n * which is a property of the composition, not of one `metadata_type`.\n */\n async probe(): Promise<void> {\n await this.engine.find(METADATA_ACTIVATION_TABLE, { where: {}, limit: 1, context: SYSTEM_CTX });\n }\n}\n","// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * [#4435] The 404 a single-record operation answers when the id names no row.\n *\n * Extracted so the READ and the two WRITE paths cannot disagree about it. They\n * did: `getData` answered `404 RECORD_NOT_FOUND` while `updateData` returned\n * `200 { record: null }` and `deleteData` returned `200 { success: true }` for\n * any string in the path — so a typo'd id, an already-deleted row and a real\n * deletion were indistinguishable, and a client PATCHing a concurrently deleted\n * record was told its write had landed.\n *\n * That is the same silent-no-op shape the v17 train removed everywhere else\n * this window (#4240/#4303/#4315 refuse missing fields, #4169 refuses unknown\n * params, #4190 stopped dropping filters) — a write that touched zero rows\n * reporting 200 is that shape one level up, on the verb where it costs the\n * most.\n *\n * [#5138] EXPORTED, for the same \"cannot disagree about it\" reason one layer\n * out. `@objectstack/runtime`'s `callData` is protocol-first with an ObjectQL\n * FALLBACK, and the fallback had reinvented this fact three incompatible ways\n * (`get` → `null`, `update` → a bare `Error` with no status ⇒ 500, `delete` →\n * no check at all ⇒ `200 { deleted: true }` for a row that never existed). It\n * now calls THIS function, so the two paths behind one `callData` answer a\n * missing id identically — which is the only reason a caller may stop caring\n * which of them served it. Re-spelling the envelope there would have been a\n * second not-found envelope; `RECORD_NOT_FOUND` (#5088) is the one this repo\n * has.\n *\n * ── [#7867] Why it lives in `@objectstack/core` and not where it was written ──\n *\n * Because the THIRD path that needed it could not reach the second one. An\n * action body's `ctx.api.object(name).update({ id, … })` traverses neither\n * `protocol.updateData` nor `callData`: it reaches `ObjectQL.update()`'s by-id\n * branch directly, which had no existence gate at all, so a ghost id was a\n * silent no-op that then died on whatever the pipeline complained about first\n * (a `HookConditionError` 400 on a hooked object, a required-field\n * `VALIDATION_FAILED` 400 on an unhooked one — the 400 class varied with the\n * object's declarations; the missing 404 was the constant).\n *\n * The gate for that path belongs in the engine, and `packages/objectql` cannot\n * import `@objectstack/metadata-protocol` where this function was written:\n * ADR-0076 D2's boundary ratchet (`core-boundary.ratchet.test.ts`) forbids the\n * whole `@objectstack/objectql/core` closure — `engine.ts` included — from\n * pulling that package in. So the choice was a FOURTH spelling of the envelope\n * or one home both layers already depend on. #5138's own sentence rules the\n * first out, so this is the second: the factory moved down to the lowest\n * package the three producers share, and `@objectstack/metadata-protocol`\n * re-exports it unchanged for every existing importer.\n *\n * This is the same move `engineCanRollBack` made for the same reason — a fact\n * two layers must agree on lives in the layer beneath both, not in a copy each.\n */\nexport function recordNotFoundError(object: string, id: string | number): Error {\n const err = new Error(`Record ${id} not found in ${object}`) as Error & {\n code?: string;\n status?: number;\n object?: string;\n };\n err.code = 'RECORD_NOT_FOUND';\n err.status = 404;\n err.object = object;\n return err;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { \n PluginHealthStatus, \n PluginHealthCheckParsed, \n PluginHealthReport \n} from '@objectstack/spec/kernel';\nimport type { ObjectLogger } from './logger.js';\nimport type { Plugin } from './types.js';\n\n/**\n * Is a success from `status` still subject to `successThreshold`?\n *\n * `successThreshold` is declared as \"Consecutive successes needed to mark\n * healthy\" (`PluginHealthCheckSchema`, `@objectstack/spec/kernel`), so the\n * counter stays in force on the way out of every status that records an\n * OBSERVED failure — `recovering` included. `recovering` is declared \"Plugin\n * is in recovery process\": recovery under way, not finished, and the count is\n * exactly its completion criterion. Consulting the counter only from\n * `unhealthy`/`degraded` capped the declared value at 2, because the first\n * success moved the plugin to `recovering` — a status the gate did not name —\n * so the second success bypassed the counter entirely and went straight to\n * `healthy`. `failed` was skipped for the same reason and recovered on its\n * first success (#11955).\n *\n * `healthy` and `unknown` promote on the first success: neither records a\n * failure to recover from. `unknown` is declared \"Health status cannot be\n * determined\" — the status a plugin is registered in before any check has run,\n * so there is nothing for the recovery count to count back from.\n *\n * Exhaustive over `PluginHealthStatus` deliberately: a status added to\n * `PluginHealthStatusSchema` fails to compile here until this map says which\n * side it falls on, so the gate cannot quietly acquire a second bypass the way\n * `recovering` did.\n */\nconst RECOVERY_IS_THRESHOLD_GATED: Record<PluginHealthStatus, boolean> = {\n degraded: true,\n unhealthy: true,\n failed: true,\n recovering: true,\n healthy: false,\n unknown: false,\n};\n\n/**\n * An ADR-0112-enveloped refusal (`code` + `status` on the error), so a caller —\n * and a rejection-class test — can assert the refusal rather than merely \"it\n * threw\". `VALIDATION_ERROR` is the standard catalog's generic\n * argument-validation code, the same envelope `hot-reload.ts` uses for the\n * sibling retirements (#12340, #12428).\n */\nfunction healthMonitorRefusal(message: string): Error & { code: string; status: number } {\n const err = new Error(message) as Error & { code: string; status: number };\n err.code = 'VALIDATION_ERROR';\n err.status = 400;\n return err;\n}\n\n/**\n * Keys removed from `PluginHealthCheck` in 18 (#12032) that a host may still\n * be passing.\n *\n * `PluginHealthCheckSchema` is not `.strict()`, so before the tombstones zod\n * would have silently STRIPPED each of these — a clean parse and a setting\n * that never takes effect. The tombstones answer the parse; this table is the\n * door for the audience that does NOT parse, which is every host there is:\n * nothing in the tree parses `PluginHealthCheckSchema` outside its own unit\n * test, and `registerPlugin` takes the PARSED shape straight from the caller's\n * hand.\n *\n * Each entry is the guidance clause; the `[HealthMonitor] Plugin '<name>': `\n * prefix is added at throw time. The facts these must carry are pinned in\n * `health-monitor.test.ts` by CONTENT, never by byte-equality against the\n * spec-side prescriptions — `@objectstack/core`'s every import of\n * `@objectstack/spec/kernel` is type-only, and a value import would be the\n * first, linking that module's zod closure into every consumer of this package\n * for three strings (the reasoning `hot-reload.ts` records for the same\n * duplication).\n */\nconst RETIRED_HEALTH_CHECK_KEYS: ReadonlyArray<readonly [string, string]> = [\n [\n 'autoRestart',\n \"'autoRestart' was removed from PluginHealthCheck in @objectstack/spec 18 \"\n + '(#12032, ADR-0049 enforce-or-remove) — it never restarted a plugin. '\n + '`attemptRestart` called `plugin.destroy()` and stopped there, then '\n + \"logged 'Plugin restarted' and set status `recovering`, and the periodic \"\n + 'checks carried on against the destroyed instance — which the default '\n + \"check (`{ name: 'plugin-loaded', status: 'passed' }`) passes forever, so \"\n + 'a destroyed, never-re-initialised plugin ended up reported `healthy`. '\n + 'Delete the key. This monitor no longer destroys anything: a failing '\n + 'plugin is reported `unhealthy` or `failed` and left alone.',\n ],\n [\n 'maxRestartAttempts',\n \"'maxRestartAttempts' was removed from PluginHealthCheck in \"\n + '@objectstack/spec 18 (#12032, ADR-0049 enforce-or-remove) — it capped a '\n + 'restart that never happened, so it only counted `destroy()` calls. '\n + 'Delete the key.',\n ],\n [\n 'restartBackoff',\n \"'restartBackoff' was removed from PluginHealthCheck in @objectstack/spec \"\n + '18 (#12032, ADR-0049 enforce-or-remove) — it delayed a restart that '\n + 'never happened, so it only moved when the `destroy()` landed. Delete '\n + 'the key.',\n ],\n];\n\n/**\n * What a host does instead. Names only affordances that exist: the monitor\n * could not restart a plugin even in principle, because `Plugin.init(ctx)`\n * needs a `PluginContext` that only the kernel constructs and exposes to\n * nobody (`ObjectKernel.context` is private, `KernelBase.createContext` is\n * protected).\n */\nconst RESTART_IS_THE_HOSTS_JOB =\n ' Restarting a plugin is the HOST\\'s job in this host-driven library: poll '\n + '`getHealthStatus(pluginName)` / `getHealthReport(pluginName)` and act on '\n + '`unhealthy` / `failed` at the level that owns the plugin\\'s lifetime — '\n + 'recreate the kernel, or let your supervisor restart the process.';\n\n/**\n * Refuse a key this library removed, at the moment the host hands the config\n * over. First match wins; the order is the order they appear in the schema.\n */\nfunction assertNoRetiredKeys(pluginName: string, config: object): void {\n for (const [key, guidance] of RETIRED_HEALTH_CHECK_KEYS) {\n if (!Object.prototype.hasOwnProperty.call(config, key)) {\n continue;\n }\n throw healthMonitorRefusal(\n `[HealthMonitor] Plugin '${pluginName}': ${guidance}${RESTART_IS_THE_HOSTS_JOB}`\n );\n }\n}\n\n/**\n * Plugin Health Monitor\n *\n * Monitors plugin health status. It REPORTS; it does not act on what it finds.\n *\n * ## The monitor no longer \"restarts\" anything (#12032)\n *\n * It used to claim it did. `attemptRestart` called `plugin.destroy()` and\n * stopped there — the comment above the call read \"Call destroy and init to\n * restart\", and `init` appeared in this file ONLY inside that comment. What a\n * plugin got was: destroy, a log line reading 'Plugin restarted', status\n * `recovering`, and periodic checks continuing against the destroyed instance.\n * The default check when no `checkMethod` resolves is\n * `{ name: 'plugin-loaded', status: 'passed' }`, which a destroyed plugin\n * passes indefinitely, so the TERMINAL report on a destroyed, never\n * re-initialised plugin was `healthy` — and #11955 made that MORE convincing\n * rather than less, because reaching `healthy` now costs `successThreshold`\n * consecutive passing rounds.\n *\n * The restart could not be repaired in place. `Plugin.init(ctx)` needs a\n * `PluginContext`, and the only two `plugin.init(...)` call sites in the tree\n * are the kernel's own boot loops, over the full plugin list, with a context\n * that is `private` on `ObjectKernel` and `protected` on `KernelBase`. No host\n * can obtain one, so there was nothing for a re-init hook to call. ADR-0049\n * enforce-or-remove, with no roadmap to point EXPERIMENTAL at, therefore\n * removed the declaration: `autoRestart`, `maxRestartAttempts` and\n * `restartBackoff` are tombstoned in `@objectstack/spec` 18, and this class\n * refuses a config that still carries one instead of accepting it and doing\n * something else.\n *\n * What a failing plugin gets now is the truth: `degraded`, `unhealthy` or\n * `failed`, and no destroy. Acting on that is the HOST's job — this is a\n * host-driven library (#11825 route 2), and the host is the only party that\n * owns the plugin's lifetime.\n */\nexport class PluginHealthMonitor {\n private logger: ObjectLogger;\n private healthChecks = new Map<string, PluginHealthCheckParsed>();\n private healthStatus = new Map<string, PluginHealthStatus>();\n private healthReports = new Map<string, PluginHealthReport>();\n private checkIntervals = new Map<string, NodeJS.Timeout>();\n private failureCounters = new Map<string, number>();\n private successCounters = new Map<string, number>();\n\n constructor(logger: ObjectLogger) {\n this.logger = logger.child({ component: 'HealthMonitor' });\n }\n\n /**\n * Register a plugin for health monitoring\n */\n registerPlugin(pluginName: string, config: PluginHealthCheckParsed): void {\n // Before anything is stored: a config still carrying a retired restart key\n // is refused with its prescription, never accepted-and-ignored. Deliberately\n // FIRST, so a config that would otherwise register cleanly cannot smuggle\n // the false declaration past the door.\n assertNoRetiredKeys(pluginName, config as object);\n\n this.healthChecks.set(pluginName, config);\n this.healthStatus.set(pluginName, 'unknown');\n this.failureCounters.set(pluginName, 0);\n this.successCounters.set(pluginName, 0);\n\n this.logger.info('Plugin registered for health monitoring', { \n plugin: pluginName,\n interval: config.interval \n });\n }\n\n /**\n * Start monitoring a plugin\n */\n startMonitoring(pluginName: string, plugin: Plugin): void {\n const config = this.healthChecks.get(pluginName);\n if (!config) {\n this.logger.warn('Cannot start monitoring - plugin not registered', { plugin: pluginName });\n return;\n }\n\n // Clear any existing interval\n this.stopMonitoring(pluginName);\n\n // Set up periodic health checks\n const interval = setInterval(() => {\n this.performHealthCheck(pluginName, plugin, config).catch(error => {\n this.logger.error('Health check failed with error', { \n plugin: pluginName, \n error \n });\n });\n }, config.interval);\n\n this.checkIntervals.set(pluginName, interval);\n this.logger.info('Health monitoring started', { plugin: pluginName });\n\n // Perform initial health check\n this.performHealthCheck(pluginName, plugin, config).catch(error => {\n this.logger.error('Initial health check failed', { \n plugin: pluginName, \n error \n });\n });\n }\n\n /**\n * Stop monitoring a plugin\n */\n stopMonitoring(pluginName: string): void {\n const interval = this.checkIntervals.get(pluginName);\n if (interval) {\n clearInterval(interval);\n this.checkIntervals.delete(pluginName);\n this.logger.info('Health monitoring stopped', { plugin: pluginName });\n }\n }\n\n /**\n * Perform a health check on a plugin\n */\n private async performHealthCheck(\n pluginName: string,\n plugin: Plugin,\n config: PluginHealthCheckParsed\n ): Promise<void> {\n const startTime = Date.now();\n let status: PluginHealthStatus = 'healthy';\n let message: string | undefined;\n const checks: Array<{ name: string; status: 'passed' | 'failed' | 'warning'; message?: string }> = [];\n // Which failure route this round took, if any. A round can fail two\n // disjoint ways and both settle here, so that the counters and the\n // threshold are consulted in exactly one place below.\n let failureRoute: 'returned' | 'thrown' | undefined;\n\n try {\n // Check if plugin has a custom health check method\n if (config.checkMethod && typeof (plugin as any)[config.checkMethod] === 'function') {\n const checkResult = await this.raceCheckTimeout(\n (plugin as any)[config.checkMethod](),\n config.timeout,\n `Health check timeout after ${config.timeout}ms`\n );\n\n if (checkResult === false || (checkResult && checkResult.status === 'unhealthy')) {\n status = 'unhealthy';\n message = checkResult?.message || 'Custom health check failed';\n checks.push({ name: config.checkMethod, status: 'failed', message });\n } else {\n checks.push({ name: config.checkMethod, status: 'passed' });\n }\n } else {\n // Default health check - just verify plugin is loaded\n checks.push({ name: 'plugin-loaded', status: 'passed' });\n }\n\n // Update counters based on result\n if (status === 'healthy') {\n this.successCounters.set(pluginName, (this.successCounters.get(pluginName) || 0) + 1);\n this.failureCounters.set(pluginName, 0);\n\n // Recover only once `successThreshold` consecutive successes have\n // accumulated — from every status that records an observed failure,\n // not just the two the gate used to name (#11955).\n const currentStatus = this.healthStatus.get(pluginName) ?? 'unknown';\n if (RECOVERY_IS_THRESHOLD_GATED[currentStatus]) {\n const successCount = this.successCounters.get(pluginName) || 0;\n if (successCount >= config.successThreshold) {\n this.healthStatus.set(pluginName, 'healthy');\n this.logger.info('Plugin recovered to healthy state', { plugin: pluginName });\n } else {\n this.healthStatus.set(pluginName, 'recovering');\n }\n } else {\n this.healthStatus.set(pluginName, 'healthy');\n }\n } else {\n failureRoute = 'returned';\n }\n } catch (error) {\n status = 'failed';\n message = error instanceof Error ? error.message : 'Unknown error';\n\n checks.push({ \n name: 'health-check', \n status: 'failed', \n message: message \n });\n\n this.logger.error('Health check exception', { \n plugin: pluginName, \n error \n });\n\n failureRoute = 'thrown';\n }\n\n // Both failure routes land here, and only here. Kept outside the `try`:\n // a fault raised while RECORDING a round is not a health-check exception,\n // and catching it above would relabel it as one and push a second\n // `health-check` entry for a check that ran. (#11852 needed this because\n // `recordFailedRound` awaited a restart; the restart is gone as of #12032\n // but the reason the boundary sits here is unchanged.)\n if (failureRoute) {\n this.recordFailedRound(pluginName, config, failureRoute);\n }\n\n // Create health report\n const report: PluginHealthReport = {\n status: this.healthStatus.get(pluginName) || 'unknown',\n timestamp: new Date().toISOString(),\n message,\n metrics: {\n uptime: Date.now() - startTime,\n },\n checks: checks.length > 0 ? checks : undefined,\n };\n\n this.healthReports.set(pluginName, report);\n }\n\n /**\n * Handle one failed round — the single path BOTH failure routes take.\n *\n * `performHealthCheck` can fail two disjoint ways: the check *returns* a\n * failure (`false` or `{ status: 'unhealthy' }`), or it *throws* — which by\n * `raceCheckTimeout` includes every `timeout` overrun, the severest case of\n * the two. The routes used to be handled in separate blocks, and only the\n * returned one cleared `successCounters`, so the counters a declared\n * `failureThreshold` / `successThreshold` are counted with depended on which\n * way the round happened to fail (#11852).\n *\n * What stays route-specific is the *status label*, deliberately. A throw is\n * the separate `failed` status applied immediately with no threshold — that\n * is the documented contract (`content/docs/protocol/kernel/lifecycle.mdx`,\n * \"Custom Health Checks\") and is pinned by the timeout test. Only the\n * counters are shared, because that is what `failureThreshold` declares, and\n * it does not name a route.\n *\n * This round ENDS here. Nothing is done TO the plugin — see the #12032 note\n * on the class: a monitor that cannot re-initialise a plugin has no business\n * destroying one.\n */\n private recordFailedRound(\n pluginName: string,\n config: PluginHealthCheckParsed,\n route: 'returned' | 'thrown'\n ): void {\n const failureCount = (this.failureCounters.get(pluginName) || 0) + 1;\n this.failureCounters.set(pluginName, failureCount);\n this.successCounters.set(pluginName, 0);\n\n const thresholdReached = failureCount >= config.failureThreshold;\n\n if (route === 'thrown') {\n this.healthStatus.set(pluginName, 'failed');\n } else if (thresholdReached) {\n this.healthStatus.set(pluginName, 'unhealthy');\n this.logger.warn('Plugin marked as unhealthy', { \n plugin: pluginName, \n failures: failureCount \n });\n } else {\n this.healthStatus.set(pluginName, 'degraded');\n }\n }\n\n /**\n * Get current health status of a plugin\n */\n getHealthStatus(pluginName: string): PluginHealthStatus | undefined {\n return this.healthStatus.get(pluginName);\n }\n\n /**\n * Get latest health report for a plugin\n */\n getHealthReport(pluginName: string): PluginHealthReport | undefined {\n return this.healthReports.get(pluginName);\n }\n\n /**\n * Get all health statuses\n */\n getAllHealthStatuses(): Map<string, PluginHealthStatus> {\n return new Map(this.healthStatus);\n }\n\n /**\n * Shutdown health monitor\n */\n shutdown(): void {\n // Stop all monitoring intervals\n for (const pluginName of this.checkIntervals.keys()) {\n this.stopMonitoring(pluginName);\n }\n \n this.healthChecks.clear();\n this.healthStatus.clear();\n this.healthReports.clear();\n this.failureCounters.clear();\n this.successCounters.clear();\n \n this.logger.info('Health monitor shutdown complete');\n }\n\n /**\n * Race a plugin's custom health check against its timeout guard, and\n * reclaim the guard the moment the race settles (#4875).\n *\n * Same shape, same reasoning as `ObjectKernel.raceStartupTimeout()` (#4813,\n * PR #4874): the guard used to be armed and then abandoned — when the check\n * won the race, its `setTimeout` stayed ref'd in the event loop for the full\n * `config.timeout`. Health checks are *periodic*, so unlike the kernel's\n * one-shot startup guards the orphans here accumulate: one per plugin per\n * round, each pinning the loop for `config.timeout`.\n *\n * Clearing on settle rather than `unref()`-ing at arm time is deliberate.\n * An unref'd guard also stops pinning the loop, but it stops being a guard\n * as well: if the check never settles and nothing else keeps the loop alive,\n * Node exits before the timer can fire and the timeout is never reported.\n * The guard has to stay ref'd exactly as long as the race is undecided,\n * which is what `clearTimeout` in a `finally` expresses.\n *\n * `check` is widened to `T | PromiseLike<T>` because `checkMethod` is called\n * dynamically off the plugin and may be synchronous; such a check wins the\n * race immediately and the guard is reclaimed on the same turn.\n */\n private async raceCheckTimeout<T>(\n check: T | PromiseLike<T>,\n ms: number,\n message: string\n ): Promise<T> {\n let guard: ReturnType<typeof setTimeout> | undefined;\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n guard = setTimeout(() => {\n reject(new Error(message));\n }, ms);\n });\n\n try {\n return await Promise.race([check, timeoutPromise]);\n } finally {\n clearTimeout(guard);\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { createHash } from 'node:crypto';\n\nimport type { \n HotReloadConfigParsed, \n PluginStateSnapshot \n} from '@objectstack/spec/kernel';\nimport type { ObjectLogger } from './logger.js';\nimport type { Plugin } from './types.js';\n\n// Polyfill for UUID generation to support both Node.js and Browser\nconst generateUUID = () => {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Basic UUID v4 fallback\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n};\n\n/**\n * The `stateStrategy` values `PluginStateManager` actually implements.\n *\n * This is the ENFORCED set — it exists so the runtime door and the switch in\n * `saveState` cannot drift apart silently. `@objectstack/spec`'s\n * `HotReloadConfigSchema` declares exactly these two (#12340).\n */\nconst HONOURED_STATE_STRATEGIES = ['memory', 'none'] as const;\n\n/**\n * Prescription for the two strategies retired in 18 (#12340).\n *\n * Deliberately PARALLEL to `HOT_RELOAD_STATE_STRATEGY_RETIRED` in\n * `packages/spec/src/kernel/plugin-lifecycle-advanced.zod.ts` rather than\n * imported from it: every `@objectstack/core` import of\n * `@objectstack/spec/kernel` is type-only, and a value import would be the\n * first — linking that module's zod closure into every consumer of this\n * package for one string. The two prescriptions answer different doors (parse\n * vs registration); the facts they must both carry are pinned in\n * `hot-reload.test.ts`, by content and never by byte-equality.\n */\nconst RETIRED_STATE_STRATEGY_GUIDANCE =\n \"'disk' and 'distributed' were removed from HotReloadConfig.stateStrategy in \"\n + '@objectstack/spec 18 (ADR-0049 enforce-or-remove) — neither was ever '\n + \"implemented. Both wrote to the same in-memory Map as 'memory' and reported it \"\n + 'only at debug level, so a host that asked for durable or cluster-replicated '\n + 'state got process-local memory and no error. '\n + \"Use 'memory' for in-process state preservation across a reload, or 'none' to \"\n + 'disable it. There is no in-tree replacement for durable or distributed plugin '\n + 'state — persist it in the host, which owns the process lifetime these '\n + 'strategies pretended to outlive.';\n\n/**\n * An ADR-0112-enveloped refusal (`code` + `status` on the error), so a caller —\n * and a rejection-class test — can assert the refusal rather than merely \"it\n * threw\". `VALIDATION_ERROR` is the standard catalog's generic\n * argument-validation code, following `metadata-service-contract.ts`.\n */\nfunction hotReloadRefusal(message: string): Error & { code: string; status: number } {\n const err = new Error(message) as Error & { code: string; status: number };\n err.code = 'VALIDATION_ERROR';\n err.status = 400;\n return err;\n}\n\n/**\n * Refuse a `stateStrategy` this library does not implement, at the moment the\n * host hands the config over.\n *\n * TypeScript hosts cannot reach this: `HotReloadConfigParsed['stateStrategy']`\n * is `'memory' | 'none'`, so `'disk'` is a compile error. This is the door for\n * the callers types do not reach — JavaScript hosts, and config that arrived\n * as JSON — which is exactly where a silent fallback used to live.\n */\nfunction assertHonouredStateStrategy(pluginName: string, strategy: unknown): void {\n if ((HONOURED_STATE_STRATEGIES as readonly unknown[]).includes(strategy)) {\n return;\n }\n const shown = typeof strategy === 'string' ? `'${strategy}'` : String(strategy);\n const retired = strategy === 'disk' || strategy === 'distributed';\n throw hotReloadRefusal(\n `[HotReload] Plugin '${pluginName}': unsupported stateStrategy ${shown}. `\n + `Honoured values are ${HONOURED_STATE_STRATEGIES.map((v) => `'${v}'`).join(' and ')}. `\n + (retired\n ? RETIRED_STATE_STRATEGY_GUIDANCE\n : 'This value has never been implemented by PluginStateManager.')\n );\n}\n\n/**\n * Keys removed from `HotReloadConfig` that a host may still be passing.\n *\n * `HotReloadConfigSchema` is not `.strict()`, so zod would silently STRIP each\n * of these on the parse paths that exist — a clean parse and a setting that\n * never takes effect, which is the exact failure the authorable-surface gate\n * names. Both keys were removed rather than tombstoned (route 3: nothing in\n * the tree parses this schema, so a parse-time prescription reaches nobody),\n * and this table is the door that makes that route honest for the audience\n * that DOES exist — a host handing the object straight to the class.\n *\n * Each entry is the guidance clause; the `[HotReload] Plugin '<name>': `\n * prefix is added at throw time. #12340's `distributedConfig` message is\n * carried across byte-for-byte — its pins assert content, and this\n * generalisation must not move them.\n */\nconst RETIRED_HOT_RELOAD_KEYS: ReadonlyArray<readonly [string, string]> = [\n [\n 'distributedConfig',\n \"'distributedConfig' was removed from \"\n + 'HotReloadConfig in @objectstack/spec 18 (ADR-0049 '\n + 'enforce-or-remove) — nothing ever read it. A provider, endpoints, a key '\n + 'prefix, a TTL and a replication factor could all be declared and no '\n + \"connection was ever opened. It left with the stateStrategy: 'distributed' \"\n + 'value it was documented as being required for. Delete the key; there is no '\n + 'in-tree replacement for distributed plugin state — persist it in the host.',\n ],\n [\n 'watchPatterns',\n \"'watchPatterns' was removed from HotReloadConfig in @objectstack/spec 18 \"\n + '(ADR-0049 enforce-or-remove) — nothing ever read it. Its only two '\n + 'uses were log lines: no watcher was ever constructed from it, so an author '\n + 'could declare a glob and no file change ever triggered a reload. File '\n + 'watching is the HOST\\'s job in this host-driven library. Delete the key, '\n + 'declare your globs wherever your own watcher reads them, and call '\n + '`HotReloadManager.scheduleReload(pluginName, reloadFn)` when one matches — '\n + 'that is the debounced integration point this class does implement.',\n ],\n];\n\n/**\n * Refuse a key this library removed, at the moment the host hands the config\n * over. First match wins; the order is the order they were retired.\n */\nfunction assertNoRetiredKeys(pluginName: string, config: object): void {\n for (const [key, guidance] of RETIRED_HOT_RELOAD_KEYS) {\n if (!Object.prototype.hasOwnProperty.call(config, key)) {\n continue;\n }\n throw hotReloadRefusal(`[HotReload] Plugin '${pluginName}': ${guidance}`);\n }\n}\n\n/**\n * Plugin State Manager\n * \n * Handles state persistence and restoration during hot reloads\n */\nclass PluginStateManager {\n private logger: ObjectLogger;\n private stateSnapshots = new Map<string, PluginStateSnapshot>();\n private memoryStore = new Map<string, any>();\n\n constructor(logger: ObjectLogger) {\n this.logger = logger.child({ component: 'StateManager' });\n }\n\n /**\n * Save plugin state before reload\n */\n async saveState(\n pluginId: string,\n version: string,\n state: Record<string, any>,\n config: HotReloadConfigParsed\n ): Promise<string> {\n const snapshot: PluginStateSnapshot = {\n pluginId,\n version,\n timestamp: new Date().toISOString(),\n state,\n metadata: {\n checksum: this.calculateChecksum(state),\n compressed: false,\n },\n };\n\n const snapshotId = generateUUID();\n\n switch (config.stateStrategy) {\n case 'memory':\n this.memoryStore.set(snapshotId, snapshot);\n this.logger.debug('State saved to memory', { pluginId, snapshotId });\n break;\n\n case 'none':\n this.logger.debug('State persistence disabled', { pluginId });\n break;\n }\n\n this.stateSnapshots.set(pluginId, snapshot);\n return snapshotId;\n }\n\n /**\n * Restore plugin state after reload\n */\n async restoreState(\n pluginId: string,\n snapshotId?: string\n ): Promise<Record<string, any> | undefined> {\n // Try to get from snapshot ID first, otherwise use latest for plugin\n let snapshot: PluginStateSnapshot | undefined;\n\n if (snapshotId) {\n snapshot = this.memoryStore.get(snapshotId);\n } else {\n snapshot = this.stateSnapshots.get(pluginId);\n }\n\n if (!snapshot) {\n this.logger.warn('No state snapshot found', { pluginId, snapshotId });\n return undefined;\n }\n\n // Verify checksum if available\n if (snapshot.metadata?.checksum) {\n const currentChecksum = this.calculateChecksum(snapshot.state);\n if (currentChecksum !== snapshot.metadata.checksum) {\n this.logger.error('State checksum mismatch - data may be corrupted', { \n pluginId,\n expected: snapshot.metadata.checksum,\n actual: currentChecksum\n });\n return undefined;\n }\n }\n\n this.logger.debug('State restored', { pluginId, version: snapshot.version });\n return snapshot.state;\n }\n\n /**\n * Clear state for a plugin\n */\n clearState(pluginId: string): void {\n this.stateSnapshots.delete(pluginId);\n // Note: We don't clear memory store as it might have multiple snapshots\n this.logger.debug('State cleared', { pluginId });\n }\n\n /**\n * Calculate checksum for state verification using SHA-256.\n */\n private calculateChecksum(state: Record<string, any>): string {\n const stateStr = JSON.stringify(state);\n return createHash('sha256').update(stateStr).digest('hex');\n }\n\n /**\n * Shutdown state manager\n */\n shutdown(): void {\n this.stateSnapshots.clear();\n this.memoryStore.clear();\n this.logger.info('State manager shutdown complete');\n }\n}\n\n/**\n * Hot Reload Manager\n * \n * Manages hot reloading of plugins with state preservation\n */\nexport class HotReloadManager {\n private logger: ObjectLogger;\n private stateManager: PluginStateManager;\n private reloadConfigs = new Map<string, HotReloadConfigParsed>();\n private reloadTimers = new Map<string, NodeJS.Timeout>();\n\n constructor(logger: ObjectLogger) {\n this.logger = logger.child({ component: 'HotReload' });\n this.stateManager = new PluginStateManager(logger);\n }\n\n /**\n * Register a plugin for hot reload\n */\n registerPlugin(pluginName: string, config: HotReloadConfigParsed): void {\n // Refused BEFORE the `enabled` check on purpose: a config naming a\n // strategy this library cannot honour is malformed whether or not hot\n // reload is switched on, and a door that opens only for `enabled: true`\n // would let the false declaration through on exactly the configs nobody\n // is watching.\n assertHonouredStateStrategy(pluginName, config.stateStrategy);\n assertNoRetiredKeys(pluginName, config);\n\n if (!config.enabled) {\n this.logger.debug('Hot reload disabled for plugin', { plugin: pluginName });\n return;\n }\n\n this.reloadConfigs.set(pluginName, config);\n this.logger.info('Plugin registered for hot reload', { \n plugin: pluginName,\n stateStrategy: config.stateStrategy\n });\n }\n\n /**\n * Refuse the file-watching call this class never implemented (#12428).\n *\n * The body used to be a guard plus `logger.info('File watching started')`\n * over an in-source note saying real watching \"would require chokidar or\n * similar\". Nothing was ever watched, so an operator who set\n * `enabled: true` and read that line at INFO had been told the opposite of\n * the truth — positive confirmation of a capability that did not exist.\n * ADR-0049 leaves three states and this surface qualified for none of the\n * other two: no runtime composes this class, so ENFORCE would build for a\n * caller that does not exist, and no roadmap entry anywhere claims the\n * feature, so EXPERIMENTAL would be a promise nobody made.\n *\n * Kept as a throwing door rather than deleted: removing the method leaves a\n * JavaScript host a bare `TypeError: not a function` with no prescription,\n * and this is the one place a caller of the old placeholder is guaranteed\n * to arrive. The refusal carries an ADR-0112 envelope so it can be asserted\n * rather than merely caught.\n */\n startWatching(pluginName: string): never {\n throw hotReloadRefusal(\n `[HotReload] Plugin '${pluginName}': startWatching() never watched `\n + 'anything and was removed in @objectstack/core 18 (ADR-0049 '\n + \"enforce-or-remove). It logged 'File watching started' at info level \"\n + 'while no watcher was ever constructed, so no file change could ever '\n + 'trigger a reload. File watching is the HOST\\'s job in this '\n + 'host-driven library: run your own watcher and call '\n + '`HotReloadManager.scheduleReload(pluginName, reloadFn)` when a file '\n + 'changes — that is the debounced integration point this class does '\n + 'implement. `HotReloadConfig.watchPatterns` was removed in '\n + '@objectstack/spec 18 for the same reason; declare your globs where '\n + 'your watcher reads them.'\n );\n }\n\n /**\n * Cancel a pending debounced reload for a plugin.\n *\n * The name is historical (#12428). This never stopped a watcher, because\n * nothing in this class ever started one: its `watchHandles` cleanup branch\n * read a Map that had no writer anywhere in the tree, so the branch was\n * structurally unreachable rather than merely untaken, and it left with\n * `startWatching`'s placeholder. What survives is the half that always did\n * something — the debounce timer armed by `scheduleReload` is cleared, so a\n * reload that was scheduled but has not fired yet is cancelled.\n */\n stopWatching(pluginName: string): void {\n const timer = this.reloadTimers.get(pluginName);\n if (timer) {\n clearTimeout(timer);\n this.reloadTimers.delete(pluginName);\n }\n }\n\n /**\n * Trigger hot reload for a plugin\n */\n async reloadPlugin(\n pluginName: string,\n plugin: Plugin,\n version: string,\n getPluginState: () => Record<string, any>,\n restorePluginState: (state: Record<string, any>) => void\n ): Promise<boolean> {\n const config = this.reloadConfigs.get(pluginName);\n if (!config) {\n this.logger.warn('Cannot reload - plugin not registered', { plugin: pluginName });\n return false;\n }\n\n this.logger.info('Starting hot reload', { plugin: pluginName });\n\n try {\n // Call before reload hooks\n if (config.beforeReload) {\n this.logger.debug('Executing before reload hooks', { \n plugin: pluginName,\n hooks: config.beforeReload \n });\n // Hook execution would be done through kernel's hook system\n }\n\n // Save state if configured\n let snapshotId: string | undefined;\n if (config.preserveState && config.stateStrategy !== 'none') {\n const state = getPluginState();\n snapshotId = await this.stateManager.saveState(\n pluginName,\n version,\n state,\n config\n );\n this.logger.debug('Plugin state saved', { plugin: pluginName, snapshotId });\n }\n\n // Gracefully shutdown the plugin\n if (plugin.destroy) {\n this.logger.debug('Destroying plugin', { plugin: pluginName });\n \n await this.raceShutdownTimeout(\n plugin.destroy(),\n config.shutdownTimeout,\n 'Shutdown timeout'\n );\n this.logger.debug('Plugin destroyed successfully', { plugin: pluginName });\n }\n\n // At this point, the kernel would reload the plugin module\n // This would be handled by the plugin loader\n this.logger.debug('Plugin module would be reloaded here', { plugin: pluginName });\n\n // Restore state if we saved it\n if (snapshotId && config.preserveState) {\n const restoredState = await this.stateManager.restoreState(pluginName, snapshotId);\n if (restoredState) {\n restorePluginState(restoredState);\n this.logger.debug('Plugin state restored', { plugin: pluginName });\n }\n }\n\n // Call after reload hooks\n if (config.afterReload) {\n this.logger.debug('Executing after reload hooks', { \n plugin: pluginName,\n hooks: config.afterReload \n });\n // Hook execution would be done through kernel's hook system\n }\n\n this.logger.info('Hot reload completed successfully', { plugin: pluginName });\n return true;\n } catch (error) {\n this.logger.error('Hot reload failed', { \n plugin: pluginName, \n error \n });\n return false;\n }\n }\n\n /**\n * Race a plugin's `destroy()` against its shutdown-timeout guard, and\n * reclaim the guard the moment the race settles (#4952).\n *\n * The guard used to be armed and then abandoned — byte-for-byte the leak\n * #4813 fixed in the kernel's startup guards (PR #4874) and #4875 fixed in\n * the periodic health checks (PR #4950): when `destroy()` won the race, its\n * `setTimeout` stayed ref'd in the event loop for the full\n * `shutdownTimeout`, so a hot reload that finished in milliseconds still\n * pinned the loop for the whole budget — once per reload, per plugin.\n *\n * Clearing on settle rather than `unref()`-ing at arm time is deliberate.\n * An unref'd guard also stops pinning the loop, but it stops being a guard\n * as well: if `destroy()` never settles and nothing else keeps the loop\n * alive, Node exits before the timer can fire and the timeout is never\n * reported. The guard has to stay ref'd exactly as long as the race is\n * undecided, which is what `clearTimeout` in a `finally` expresses.\n *\n * `shutdown` is widened to `T | PromiseLike<T>` because the Plugin contract\n * permits a synchronous `destroy()` (`Promise<void> | void`); such a hook\n * wins the race immediately and the guard is reclaimed on the same turn.\n */\n private async raceShutdownTimeout<T>(\n shutdown: T | PromiseLike<T>,\n timeout: number,\n message: string\n ): Promise<T> {\n let guard: ReturnType<typeof setTimeout> | undefined;\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n guard = setTimeout(() => {\n reject(new Error(message));\n }, timeout);\n });\n\n try {\n return await Promise.race([shutdown, timeoutPromise]);\n } finally {\n clearTimeout(guard);\n }\n }\n\n /**\n * Schedule a reload with debouncing\n */\n scheduleReload(\n pluginName: string,\n reloadFn: () => Promise<void>\n ): void {\n const config = this.reloadConfigs.get(pluginName);\n if (!config) {\n return;\n }\n\n // Clear existing timer\n const existingTimer = this.reloadTimers.get(pluginName);\n if (existingTimer) {\n clearTimeout(existingTimer);\n }\n\n // Schedule new reload with debounce\n const timer = setTimeout(() => {\n this.logger.debug('Debounce period elapsed, executing reload', { \n plugin: pluginName \n });\n reloadFn().catch(error => {\n this.logger.error('Scheduled reload failed', { \n plugin: pluginName, \n error \n });\n });\n this.reloadTimers.delete(pluginName);\n }, config.debounceDelay);\n\n this.reloadTimers.set(pluginName, timer);\n this.logger.debug('Reload scheduled with debounce', { \n plugin: pluginName,\n delay: config.debounceDelay \n });\n }\n\n /**\n * Get state manager for direct access\n */\n getStateManager(): PluginStateManager {\n return this.stateManager;\n }\n\n /**\n * Shutdown hot reload manager\n */\n shutdown(): void {\n // Clear all timers\n for (const timer of this.reloadTimers.values()) {\n clearTimeout(timer);\n }\n\n this.reloadConfigs.clear();\n this.reloadTimers.clear();\n this.stateManager.shutdown();\n \n this.logger.info('Hot reload manager shutdown complete');\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { \n SemanticVersion,\n VersionConstraint,\n CompatibilityLevel,\n DependencyConflict\n} from '@objectstack/spec/kernel';\nimport type { ObjectLogger } from './logger.js';\n\n/**\n * Semantic Version Parser and Comparator\n * \n * Implements semantic versioning comparison and constraint matching\n */\nexport class SemanticVersionManager {\n /**\n * Parse a version string into semantic version components\n */\n static parse(versionStr: string): SemanticVersion {\n // Remove 'v' prefix if present\n const cleanVersion = versionStr.replace(/^v/, '');\n \n // Match semver pattern: major.minor.patch[-prerelease][+build]\n const match = cleanVersion.match(\n /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([a-zA-Z0-9.-]+))?(?:\\+([a-zA-Z0-9.-]+))?$/\n );\n\n if (!match) {\n throw new Error(`Invalid semantic version: ${versionStr}`);\n }\n\n return {\n major: parseInt(match[1], 10),\n minor: parseInt(match[2], 10),\n patch: parseInt(match[3], 10),\n preRelease: match[4],\n build: match[5],\n };\n }\n\n /**\n * Convert semantic version back to string\n */\n static toString(version: SemanticVersion): string {\n let str = `${version.major}.${version.minor}.${version.patch}`;\n if (version.preRelease) {\n str += `-${version.preRelease}`;\n }\n if (version.build) {\n str += `+${version.build}`;\n }\n return str;\n }\n\n /**\n * Compare two semantic versions\n * Returns: -1 if a < b, 0 if a === b, 1 if a > b\n */\n static compare(a: SemanticVersion, b: SemanticVersion): number {\n // Compare major, minor, patch\n if (a.major !== b.major) return a.major - b.major;\n if (a.minor !== b.minor) return a.minor - b.minor;\n if (a.patch !== b.patch) return a.patch - b.patch;\n\n // Pre-release versions have lower precedence\n if (a.preRelease && !b.preRelease) return -1;\n if (!a.preRelease && b.preRelease) return 1;\n \n // Compare pre-release versions\n if (a.preRelease && b.preRelease) {\n return a.preRelease.localeCompare(b.preRelease);\n }\n\n return 0;\n }\n\n /**\n * Check if version satisfies constraint\n */\n static satisfies(version: SemanticVersion, constraint: VersionConstraint): boolean {\n const constraintStr = constraint as string;\n\n // Any version\n if (constraintStr === '*' || constraintStr === 'latest') {\n return true;\n }\n\n // Exact version\n if (/^[\\d.]+$/.test(constraintStr)) {\n const exact = this.parse(constraintStr);\n return this.compare(version, exact) === 0;\n }\n\n // Caret range (^): Compatible with version\n if (constraintStr.startsWith('^')) {\n const base = this.parse(constraintStr.slice(1));\n return (\n version.major === base.major &&\n this.compare(version, base) >= 0\n );\n }\n\n // Tilde range (~): Approximately equivalent\n if (constraintStr.startsWith('~')) {\n const base = this.parse(constraintStr.slice(1));\n return (\n version.major === base.major &&\n version.minor === base.minor &&\n this.compare(version, base) >= 0\n );\n }\n\n // Greater than or equal\n if (constraintStr.startsWith('>=')) {\n const base = this.parse(constraintStr.slice(2));\n return this.compare(version, base) >= 0;\n }\n\n // Greater than\n if (constraintStr.startsWith('>')) {\n const base = this.parse(constraintStr.slice(1));\n return this.compare(version, base) > 0;\n }\n\n // Less than or equal\n if (constraintStr.startsWith('<=')) {\n const base = this.parse(constraintStr.slice(2));\n return this.compare(version, base) <= 0;\n }\n\n // Less than\n if (constraintStr.startsWith('<')) {\n const base = this.parse(constraintStr.slice(1));\n return this.compare(version, base) < 0;\n }\n\n // Range (1.2.3 - 2.3.4)\n const rangeMatch = constraintStr.match(/^([\\d.]+)\\s*-\\s*([\\d.]+)$/);\n if (rangeMatch) {\n const min = this.parse(rangeMatch[1]);\n const max = this.parse(rangeMatch[2]);\n return this.compare(version, min) >= 0 && this.compare(version, max) <= 0;\n }\n\n return false;\n }\n\n /**\n * Determine compatibility level between two versions\n */\n static getCompatibilityLevel(from: SemanticVersion, to: SemanticVersion): CompatibilityLevel {\n const cmp = this.compare(from, to);\n\n // Same version\n if (cmp === 0) {\n return 'fully-compatible';\n }\n\n // Major version changed - breaking changes\n if (from.major !== to.major) {\n return 'breaking-changes';\n }\n\n // Minor version increased - backward compatible\n if (from.minor < to.minor) {\n return 'backward-compatible';\n }\n\n // Patch version increased - fully compatible\n if (from.patch < to.patch) {\n return 'fully-compatible';\n }\n\n // Downgrade - incompatible\n return 'incompatible';\n }\n}\n\n/**\n * Plugin Dependency Resolver\n * \n * Resolves plugin dependencies using topological sorting and conflict detection\n */\nexport class DependencyResolver {\n private logger: ObjectLogger;\n\n constructor(logger: ObjectLogger) {\n this.logger = logger.child({ component: 'DependencyResolver' });\n }\n\n /**\n * Resolve dependencies using topological sort\n */\n resolve(\n plugins: Map<string, { version?: string; dependencies?: string[] }>\n ): string[] {\n const graph = new Map<string, string[]>();\n const inDegree = new Map<string, number>();\n\n // Build dependency graph\n for (const [pluginName, pluginInfo] of plugins) {\n if (!graph.has(pluginName)) {\n graph.set(pluginName, []);\n inDegree.set(pluginName, 0);\n }\n\n const deps = pluginInfo.dependencies || [];\n for (const dep of deps) {\n // Check if dependency exists\n if (!plugins.has(dep)) {\n throw new Error(`Missing dependency: ${pluginName} requires ${dep}`);\n }\n\n // Add edge\n if (!graph.has(dep)) {\n graph.set(dep, []);\n inDegree.set(dep, 0);\n }\n graph.get(dep)!.push(pluginName);\n inDegree.set(pluginName, (inDegree.get(pluginName) || 0) + 1);\n }\n }\n\n // Topological sort using Kahn's algorithm\n const queue: string[] = [];\n const result: string[] = [];\n\n // Add all nodes with no incoming edges\n for (const [node, degree] of inDegree) {\n if (degree === 0) {\n queue.push(node);\n }\n }\n\n while (queue.length > 0) {\n const node = queue.shift()!;\n result.push(node);\n\n // Reduce in-degree for dependent nodes\n const dependents = graph.get(node) || [];\n for (const dependent of dependents) {\n const newDegree = (inDegree.get(dependent) || 0) - 1;\n inDegree.set(dependent, newDegree);\n \n if (newDegree === 0) {\n queue.push(dependent);\n }\n }\n }\n\n // Check for circular dependencies\n if (result.length !== plugins.size) {\n const remaining = Array.from(plugins.keys()).filter(p => !result.includes(p));\n this.logger.error('Circular dependency detected', { remaining });\n throw new Error(`Circular dependency detected among: ${remaining.join(', ')}`);\n }\n\n this.logger.debug('Dependencies resolved', { order: result });\n return result;\n }\n\n /**\n * Detect dependency conflicts\n */\n detectConflicts(\n plugins: Map<string, { version: string; dependencies?: Record<string, VersionConstraint> }>\n ): DependencyConflict[] {\n const conflicts: DependencyConflict[] = [];\n const versionRequirements = new Map<string, Map<string, VersionConstraint>>();\n\n // Collect all version requirements\n for (const [pluginName, pluginInfo] of plugins) {\n if (!pluginInfo.dependencies) continue;\n\n for (const [depName, constraint] of Object.entries(pluginInfo.dependencies)) {\n if (!versionRequirements.has(depName)) {\n versionRequirements.set(depName, new Map());\n }\n versionRequirements.get(depName)!.set(pluginName, constraint);\n }\n }\n\n // Check for version mismatches\n for (const [depName, requirements] of versionRequirements) {\n const depInfo = plugins.get(depName);\n if (!depInfo) continue;\n\n const depVersion = SemanticVersionManager.parse(depInfo.version);\n const unsatisfied: Array<{ pluginId: string; version: string }> = [];\n\n for (const [requiringPlugin, constraint] of requirements) {\n if (!SemanticVersionManager.satisfies(depVersion, constraint)) {\n unsatisfied.push({\n pluginId: requiringPlugin,\n version: constraint as string,\n });\n }\n }\n\n if (unsatisfied.length > 0) {\n conflicts.push({\n type: 'version-mismatch',\n severity: 'error',\n description: `Version mismatch for ${depName}: detected ${unsatisfied.length} unsatisfied requirements`,\n plugins: [\n { pluginId: depName, version: depInfo.version },\n ...unsatisfied,\n ],\n resolutions: [{\n strategy: 'upgrade',\n description: `Upgrade ${depName} to satisfy all constraints`,\n targetPlugins: [depName],\n automatic: false,\n } as any],\n });\n }\n }\n\n // Check for circular dependencies (will be caught by resolve())\n try {\n this.resolve(new Map(\n Array.from(plugins.entries()).map(([name, info]) => [\n name,\n { version: info.version, dependencies: info.dependencies ? Object.keys(info.dependencies) : [] }\n ])\n ));\n } catch (error) {\n if (error instanceof Error && error.message.includes('Circular dependency')) {\n conflicts.push({\n type: 'circular-dependency',\n severity: 'critical',\n description: error.message,\n plugins: [], // Would need to extract from error\n resolutions: [{\n strategy: 'manual',\n description: 'Remove circular dependency by restructuring plugins',\n automatic: false,\n } as any],\n });\n }\n }\n\n return conflicts;\n }\n\n /**\n * Find best version that satisfies all constraints\n */\n findBestVersion(\n availableVersions: string[],\n constraints: VersionConstraint[]\n ): string | undefined {\n // Parse and sort versions (highest first)\n const versions = availableVersions\n .map(v => ({ str: v, parsed: SemanticVersionManager.parse(v) }))\n .sort((a, b) => -SemanticVersionManager.compare(a.parsed, b.parsed));\n\n // Find highest version that satisfies all constraints\n for (const version of versions) {\n const satisfiesAll = constraints.every(constraint =>\n SemanticVersionManager.satisfies(version.parsed, constraint)\n );\n\n if (satisfiesAll) {\n return version.str;\n }\n }\n\n return undefined;\n }\n\n /**\n * Check if dependencies form a valid DAG (no cycles)\n */\n isAcyclic(dependencies: Map<string, string[]>): boolean {\n try {\n const plugins = new Map(\n Array.from(dependencies.entries()).map(([name, deps]) => [\n name,\n { dependencies: deps }\n ])\n );\n this.resolve(plugins);\n return true;\n } catch {\n return false;\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { ObjectLogger } from './logger.js';\n\n/**\n * Namespace entry representing an object/view/flow etc. registered by a package.\n */\nexport interface NamespaceEntry {\n /** The namespace path (e.g. \"objects.project_task\", \"views.task_list\") */\n namespace: string;\n /** The package that owns this namespace */\n packageId: string;\n /** When this entry was registered */\n registeredAt: string;\n}\n\n/**\n * Result of a namespace conflict check.\n */\nexport interface NamespaceConflict {\n /** The conflicting namespace path */\n namespace: string;\n /** The package that currently owns this namespace */\n existingPackageId: string;\n /** The package attempting to register the same namespace */\n incomingPackageId: string;\n /** A suggested alternative name to avoid the conflict */\n suggestion?: string;\n}\n\n/**\n * Result of namespace availability check.\n */\nexport interface NamespaceCheckResult {\n /** Whether all requested namespaces are available */\n available: boolean;\n /** List of conflicts detected */\n conflicts: NamespaceConflict[];\n /** Suggested alternatives for each conflict */\n suggestions: Record<string, string>;\n}\n\n/**\n * Namespace Resolver\n *\n * Manages namespace registration for installed packages and detects collisions\n * during install-time. Each metadata item (object, view, flow, page, etc.)\n * produces a namespace like `objects.<name>` or `views.<name>`.\n *\n * When a new package declares objects, views, or other metadata that would\n * collide with an existing package's metadata, this resolver reports the\n * conflicts and suggests prefixed alternatives.\n */\nexport class NamespaceResolver {\n private logger: ObjectLogger;\n private registry: Map<string, NamespaceEntry> = new Map();\n\n constructor(logger: ObjectLogger) {\n this.logger = logger.child({ component: 'NamespaceResolver' });\n }\n\n /**\n * Register namespaces owned by a package.\n */\n register(packageId: string, namespaces: string[]): void {\n const now = new Date().toISOString();\n for (const ns of namespaces) {\n if (this.registry.has(ns)) {\n const existing = this.registry.get(ns)!;\n if (existing.packageId !== packageId) {\n this.logger.warn('Overwriting namespace entry', { namespace: ns, existing: existing.packageId, incoming: packageId });\n }\n }\n this.registry.set(ns, { namespace: ns, packageId, registeredAt: now });\n this.logger.debug('Namespace registered', { namespace: ns, packageId });\n }\n }\n\n /**\n * Unregister all namespaces belonging to a package.\n */\n unregister(packageId: string): string[] {\n const removed: string[] = [];\n for (const [ns, entry] of this.registry) {\n if (entry.packageId === packageId) {\n this.registry.delete(ns);\n removed.push(ns);\n }\n }\n this.logger.debug('Namespaces unregistered', { packageId, count: removed.length });\n return removed;\n }\n\n /**\n * Check whether a set of namespaces is available for a given package.\n */\n checkAvailability(packageId: string, namespaces: string[]): NamespaceCheckResult {\n const conflicts: NamespaceConflict[] = [];\n const suggestions: Record<string, string> = {};\n\n for (const ns of namespaces) {\n const existing = this.registry.get(ns);\n if (existing && existing.packageId !== packageId) {\n const suggestion = this.suggestAlternative(ns, packageId);\n conflicts.push({\n namespace: ns,\n existingPackageId: existing.packageId,\n incomingPackageId: packageId,\n suggestion,\n });\n suggestions[ns] = suggestion;\n }\n }\n\n return {\n available: conflicts.length === 0,\n conflicts,\n suggestions,\n };\n }\n\n /**\n * Extract namespace strings from a package's metadata definition.\n */\n extractNamespaces(config: Record<string, unknown>): string[] {\n const namespaces: string[] = [];\n const categories = [\n 'objects', 'views', 'pages', 'flows', 'workflows',\n 'apps', 'dashboards', 'reports', 'actions', 'agents',\n ];\n\n for (const category of categories) {\n const items = config[category];\n if (Array.isArray(items)) {\n for (const item of items) {\n const name = (item as Record<string, unknown>)?.name;\n if (typeof name === 'string') {\n namespaces.push(`${category}.${name}`);\n }\n }\n } else if (items && typeof items === 'object') {\n for (const key of Object.keys(items as object)) {\n namespaces.push(`${category}.${key}`);\n }\n }\n }\n\n return namespaces;\n }\n\n /**\n * Get all registered entries.\n */\n getRegistry(): ReadonlyMap<string, NamespaceEntry> {\n return this.registry;\n }\n\n /**\n * Get all namespaces belonging to a specific package.\n */\n getPackageNamespaces(packageId: string): string[] {\n const namespaces: string[] = [];\n for (const [ns, entry] of this.registry) {\n if (entry.packageId === packageId) {\n namespaces.push(ns);\n }\n }\n return namespaces;\n }\n\n /**\n * Generate a prefixed alternative namespace to avoid conflicts.\n */\n private suggestAlternative(ns: string, packageId: string): string {\n // Extract the short package name for prefixing\n const shortName = packageId\n .replace(/^@[^/]+\\//, '')\n .replace(/^plugin-/, '')\n .replace(/-/g, '_');\n\n const parts = ns.split('.');\n if (parts.length >= 2) {\n // e.g. \"objects.task\" → \"objects.crm_task\"\n return `${parts[0]}.${shortName}_${parts.slice(1).join('.')}`;\n }\n return `${shortName}_${ns}`;\n }\n}\n"],"mappings":"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","names":["nodePath","import_system","cryptoSign","cryptoVerify","ServiceLifecycle","import_system","import_node_crypto","nodePath","import_node_crypto","import_types","import_spec","import_security","sink","safeJsonParse","ymd","import_node_crypto","import_system","import_data","import_data","SYSTEM_CTX","import_node_crypto","assertNoRetiredKeys"]}
|