@objectstack/core 17.1.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/dist/index.d.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  import { Logger, LifecycleEventName, IServiceRegistry, AudienceBindingSuggestionFilter, IObjectQLEngine } from '@objectstack/spec/contracts';
2
2
  export { EngineSchemaRegistryView, EngineTransactionInfo, EngineTransactionOptions, HttpResponseObservation, HttpResponseObserver, IDataDriver, IDataEngine, IHttpRequest, IHttpResponse, IHttpServer, IObjectQLEngine, Logger, Middleware, RouteHandler, UNMATCHED_ROUTE_PATTERN } from '@objectstack/spec/contracts';
3
- import { z } from 'zod';
4
3
  import { LoggerConfig, MigrationOnCrashPolicy, MigrationJournalEvent } from '@objectstack/spec/system';
4
+ import { CORE_PLUGIN_TYPES, PluginCapability, PluginPermissions as PluginPermissions$1, PluginPermissionSet, ResourceType, PermissionAction, PluginPermission, SandboxConfig, KernelSecurityScanResult, KernelSecurityVulnerability, ExecutionContext, PluginHealthCheckParsed, PluginHealthStatus as PluginHealthStatus$1, PluginHealthReport, HotReloadConfigParsed, VersionConstraint, DependencyConflict, SemanticVersion, CompatibilityLevel } from '@objectstack/spec/kernel';
5
5
  import { ObjectLogger } from './logger.js';
6
6
  export { createLogger } from './logger.js';
7
7
  import * as QA from '@objectstack/spec/qa';
8
8
  import { KeyObject } from 'node:crypto';
9
- import { PluginCapability, PluginPermissions as PluginPermissions$1, PluginPermissionSet, ResourceType, PermissionAction, PluginPermission, SandboxConfig, KernelSecurityScanResult, KernelSecurityVulnerability, ExecutionContext, PluginHealthCheckParsed, PluginHealthStatus as PluginHealthStatus$1, PluginHealthReport, HotReloadConfigParsed, VersionConstraint, DependencyConflict, SemanticVersion, CompatibilityLevel } from '@objectstack/spec/kernel';
10
9
  import { TenancyPosture, AuthzPosture } from '@objectstack/spec/security';
11
10
  export { nextUtcCalendarDay, utcInstantMs } from '@objectstack/spec/data';
12
11
 
@@ -42,16 +41,12 @@ interface ServiceRegistration {
42
41
  interface PluginMetadata extends Plugin {
43
42
  /** Semantic version (e.g., "1.0.0") */
44
43
  version: string;
45
- /** Configuration schema for validation */
46
- configSchema?: z.ZodSchema;
47
44
  /** Plugin signature for security verification */
48
45
  signature?: string;
49
46
  /** Plugin health check function */
50
47
  healthCheck?(): Promise<PluginHealthStatus>;
51
48
  /** Startup timeout in milliseconds (default: 30000) */
52
49
  startupTimeout?: number;
53
- /** Whether plugin supports hot reload */
54
- hotReloadable?: boolean;
55
50
  }
56
51
  /**
57
52
  * Plugin Health Status
@@ -97,7 +92,6 @@ interface VersionCompatibility {
97
92
  declare class PluginLoader {
98
93
  private logger;
99
94
  private context?;
100
- private configValidator;
101
95
  private loadedPlugins;
102
96
  private serviceFactories;
103
97
  private serviceInstances;
@@ -160,7 +154,6 @@ declare class PluginLoader {
160
154
  private validatePluginStructure;
161
155
  private checkVersionCompatibility;
162
156
  private isValidSemanticVersion;
163
- private validatePluginConfig;
164
157
  private verifyPluginSignature;
165
158
  private getSingletonService;
166
159
  private createTransientService;
@@ -320,12 +313,14 @@ declare class ObjectKernel {
320
313
  * as well: if the hook never settles and nothing else keeps the loop alive,
321
314
  * Node exits before the timer can fire and the timeout is never reported.
322
315
  * The guard has to stay ref'd exactly as long as the race is undecided,
323
- * which is what `clearTimeout` in a `finally` expresses.
316
+ * which is what clearing on settle expresses.
324
317
  *
325
- * `operation` is widened to `T | PromiseLike<T>` because the Plugin
326
- * contract permits a synchronous hook (`init`/`start` return
327
- * `void | Promise<void>`); such a hook wins the race immediately and the
328
- * guard is reclaimed on the same turn.
318
+ * Clearing the timer was only half of it, though (#10604): the promise the
319
+ * race still holds a reaction on has to SETTLE, or it and that reaction are
320
+ * retained past the end of the run two leaking promises per boot, which
321
+ * is what `vitest --detectAsyncLeaks` names here. Both halves now live in
322
+ * `TimeoutGuard.reclaim()`, shared with `shutdown()`, so the two sites
323
+ * cannot drift into doing one half each again.
329
324
  */
330
325
  private raceStartupTimeout;
331
326
  /**
@@ -464,6 +459,17 @@ interface PluginContext {
464
459
  */
465
460
  getKernel(): ObjectKernel;
466
461
  }
462
+ /**
463
+ * The closed set of plugin types (#13925): `'standard'` plus the seven
464
+ * `CORE_PLUGIN_TYPES` members, in exactly the shape `PluginSchema.type`
465
+ * declares in `@objectstack/spec` (`kernel/plugin.zod.ts`:
466
+ * `z.enum(['standard', ...CORE_PLUGIN_TYPES])`). Derived from the spec's own
467
+ * constant rather than re-spelled here, so the compiler's accept set and the
468
+ * Zod gate's cannot drift apart: `plugin-type-closed-set.test.ts` pins the
469
+ * parity at runtime, and `packages/rest`'s `plugin-type-closed-set.pin.test.ts`
470
+ * pins the published `.d.ts` at compile time.
471
+ */
472
+ type PluginType = 'standard' | (typeof CORE_PLUGIN_TYPES)[number];
467
473
  /**
468
474
  * Plugin Interface
469
475
  *
@@ -479,10 +485,13 @@ interface Plugin {
479
485
  */
480
486
  version?: string;
481
487
  /**
482
- * Plugin type (standard, ui, driver, server, app, theme, agent)
488
+ * Plugin type categorisation for runtime behaviour a {@link PluginType},
489
+ * the closed set the spec declares. The enumeration lives on that type
490
+ * (derived from `CORE_PLUGIN_TYPES`), not in this comment: a value outside
491
+ * it no longer type-checks, and `PluginSchema.type` refuses it at parse.
483
492
  * @default 'standard'
484
493
  */
485
- type?: string;
494
+ type?: PluginType;
486
495
  /**
487
496
  * List of other plugin names that this plugin depends on.
488
497
  * The kernel ensures these plugins are initialized before this one.
@@ -797,6 +806,41 @@ declare function describeInitOrderFault(currentlyInitializing: string | undefine
797
806
  */
798
807
  declare function assertInitServiceRequirements(plugin: OrderablePlugin, isServiceRegistered: (name: string) => boolean): void;
799
808
 
809
+ /**
810
+ * Refusals raised by {@link resolveArtifactPackageOrder}, as ADR-0112 envelopes
811
+ * (`code` + `status`) — the shape this repository's rejection tests assert
812
+ * against, never a bare throw.
813
+ */
814
+ type ArtifactPackageError = Error & {
815
+ code: string;
816
+ status: number;
817
+ };
818
+ /**
819
+ * The id one artifact package is keyed by.
820
+ *
821
+ * `||`, not `??`, on purpose: `ObjectQL.registerApp` keys the installed package
822
+ * on `manifest.id || manifest.name`, so an empty-string `id` falls back to
823
+ * `name` there. Every seam that has to name a package — this module's ordering
824
+ * map, and the install gate's co-ownership set (ADR-0130 D1) — reads the id
825
+ * through THIS function, so none of them can order or admit a package under a
826
+ * key the registry never stores it by.
827
+ *
828
+ * @returns The package id, or `undefined` when the manifest carries neither a
829
+ * usable `id` nor a usable `name`.
830
+ */
831
+ declare function artifactPackageId(manifest: unknown): string | undefined;
832
+ /**
833
+ * Resolve an artifact into the manifests to register, in dependency-topological
834
+ * order (ADR-0130 D4 + D5).
835
+ *
836
+ * @param artifact - A release artifact (`{ packages: [...] }`), or a bare
837
+ * manifest / single-`manifest` artifact — both shapes are read.
838
+ * @returns The manifest bodies to register, in the order to register them.
839
+ * @throws An ADR-0112 envelope (`code` + `status: 422`) for a malformed entry or
840
+ * a duplicate package id, and `resolvePluginOrder`'s own error for a cycle.
841
+ */
842
+ declare function resolveArtifactPackageOrder(artifact: unknown): unknown[];
843
+
800
844
  /**
801
845
  * ObjectKernel - MiniKernel Architecture
802
846
  *
@@ -860,6 +904,100 @@ declare class LiteKernel extends ObjectKernelBase {
860
904
  isRunning(): boolean;
861
905
  }
862
906
 
907
+ /**
908
+ * [#13905] The discriminator that tells **"nothing ever registered this
909
+ * service"** apart from **"the service IS registered and could not be built"**
910
+ * on the ASYNCHRONOUS resolution path.
911
+ *
912
+ * ## The fault
913
+ *
914
+ * `PluginLoader.getService` (reached through `Kernel.getServiceAsync`) answered
915
+ * both facts with the same bare `Error`. A caller that holds only the rejection
916
+ * therefore could not tell an UNWIRED embedder from a BROKEN one, and the only
917
+ * thing separating them was message text.
918
+ *
919
+ * That mattered one layer out. `RestServer.computeExecCtx`'s kernel branch
920
+ * absorbs a failed `getServiceAsync('objectql')` and degrades to "no engine is
921
+ * wired". It must keep doing so — a kernel with no data plane is a SUPPORTED
922
+ * configuration (`rest-api-plugin.ts` declares
923
+ * `optionalDependencies: ['com.objectstack.engine.objectql']`) — but a
924
+ * multi-tenant host whose engine FAILED TO CONSTRUCT reached that same resolver
925
+ * as "no engine is wired", degrading silently where it should have refused
926
+ * loudly. The branch could not be repaired from the outside, because the fact
927
+ * it needed had been collapsed before it arrived.
928
+ *
929
+ * ## Why a brand, and ⛔ not message text
930
+ *
931
+ * The SYNCHRONOUS accessor in `kernel.ts` already draws exactly this line, and
932
+ * the comment there records what happened the last time someone read the fact
933
+ * off the wrong surface: reading "not found" off the async path "reported every
934
+ * missing service as `is async - use await` — the wrong fix, pointing at the
935
+ * wrong layer". A second text classifier on a resolution path is the failure
936
+ * mode this module removes, ⛔ not a repair of it.
937
+ *
938
+ * The sync side decides from the REGISTRY — synchronous and authoritative — and
939
+ * raises two different messages. The async side now carries that same
940
+ * distinction as a branded, `code`-bearing rejection: one fact, spelled for a
941
+ * caller that only ever sees the rejection.
942
+ *
943
+ * ## The test is CLOSED, and its default is LOUD
944
+ *
945
+ * Exactly one throw in `PluginLoader.getService` means "never registered", and
946
+ * it is the one branded here. Every other way that method can reject — a
947
+ * factory that threw, a missing scope id, an unset loader context, a circular
948
+ * service dependency — is a service that IS registered and could not be
949
+ * produced, and stays unbranded. So `false` is the safe answer: a consumer that
950
+ * absorbs only the branded rejection stays loud about everything else,
951
+ * including rejections added to that method later.
952
+ *
953
+ * ## Two deliberate omissions
954
+ *
955
+ * - **No `status`.** An ADR-0112 envelope pairs `code` with a `status`, but
956
+ * the whole point of this discriminator is that the CONSUMER decides what an
957
+ * unwired service means — absorb and degrade (the supported no-data-plane
958
+ * kernel) or refuse. Carrying an HTTP status here would presuppose that
959
+ * decision at the layer that must not make it.
960
+ * - **No `name` override.** The rejection stays `name: 'Error'` with a
961
+ * byte-identical message, so `String(err)`, logs and existing assertions
962
+ * render exactly as before. The only observable change is two added
963
+ * own-properties.
964
+ *
965
+ * Brand shape follows `AuthzStoreUnavailableError` (2026-08-30): a string-keyed
966
+ * own property rather than `instanceof`, so the predicate still answers
967
+ * correctly when two copies of `@objectstack/core` are installed (a duplicated
968
+ * module makes `instanceof` say "no" to an error it built itself).
969
+ *
970
+ * ⚠️ The brand does NOT survive `structuredClone`, and no claim here depends on
971
+ * it doing so — measured on Node 22: cloning an `Error` keeps `name`, `message`,
972
+ * `stack` and `cause` and DROPS every other own property, brand and `code`
973
+ * alike. This discriminator is for an in-process rejection travelling from
974
+ * `PluginLoader.getService` to a seam that catches it, which is the only path
975
+ * it is used on.
976
+ */
977
+ /**
978
+ * The code carried by the "never registered" rejection.
979
+ *
980
+ * ⚠️ Spelled the ADR-0112 way, but deliberately NOT wire vocabulary: this value
981
+ * is read in-process by the seam that catches the rejection and is never
982
+ * serialized into an `error.code` envelope. `dispatcher-error-vocabulary.ts`
983
+ * classifies it `door: 'none'` / `boot-refusal` for exactly that reason — the
984
+ * same class as the migration-journal runner refusals. If a transport ever
985
+ * needs to ANSWER with this fact, that is a registration question for #8846's
986
+ * ledger, ⛔ not something to start doing at a door.
987
+ */
988
+ declare const SERVICE_NOT_REGISTERED_CODE = "SERVICE_NOT_REGISTERED";
989
+ /**
990
+ * True when `err` is the rejection meaning **nothing was ever registered under
991
+ * that service name** — never when a registered service failed to construct.
992
+ *
993
+ * The predicate a seam uses to keep absorbing the supported "no data plane"
994
+ * composition while staying loud about a service that IS wired and broke.
995
+ */
996
+ declare function isServiceNotRegisteredError(err: unknown): err is Error & {
997
+ readonly code: typeof SERVICE_NOT_REGISTERED_CODE;
998
+ readonly serviceName: string;
999
+ };
1000
+
863
1001
  /**
864
1002
  * Interface for executing test actions against a target system.
865
1003
  * The target could be a local Kernel instance or a remote API.
@@ -1191,82 +1329,45 @@ declare function verifyPluginArtifact(input: {
1191
1329
  requirePlatform?: boolean;
1192
1330
  }): Promise<PluginArtifactVerifyResult>;
1193
1331
 
1194
- /**
1195
- * Plugin Configuration Validator
1196
- *
1197
- * Validates plugin configurations against Zod schemas to ensure:
1198
- * 1. Type safety - all config values have correct types
1199
- * 2. Business rules - values meet constraints (min/max, regex, etc.)
1200
- * 3. Required fields - all mandatory configuration is provided
1201
- * 4. Default values - missing optional fields get defaults
1202
- *
1203
- * Architecture:
1204
- * - Uses Zod for runtime validation
1205
- * - Provides detailed error messages with field paths
1206
- * - Supports nested configuration objects
1207
- * - Allows partial validation for incremental updates
1208
- *
1209
- * Usage:
1210
- * ```typescript
1211
- * const validator = new PluginConfigValidator(logger);
1212
- * const validConfig = validator.validatePluginConfig(plugin, userConfig);
1213
- * ```
1214
- */
1215
- declare class PluginConfigValidator {
1216
- private logger;
1217
- constructor(logger: Logger);
1218
- /**
1219
- * Validate plugin configuration against its Zod schema
1220
- *
1221
- * @param plugin - Plugin metadata with configSchema
1222
- * @param config - User-provided configuration
1223
- * @returns Validated and typed configuration
1224
- * @throws Error with detailed validation errors
1225
- */
1226
- validatePluginConfig<T = any>(plugin: PluginMetadata, config: any): T;
1227
- /**
1228
- * Validate partial configuration (for incremental updates)
1229
- *
1230
- * @param plugin - Plugin metadata
1231
- * @param partialConfig - Partial configuration to validate
1232
- * @returns Validated partial configuration
1233
- */
1234
- validatePartialConfig<T = any>(plugin: PluginMetadata, partialConfig: any): Partial<T>;
1235
- /**
1236
- * Get default configuration from schema
1237
- *
1238
- * @param plugin - Plugin metadata
1239
- * @returns Default configuration object
1240
- */
1241
- getDefaultConfig<T = any>(plugin: PluginMetadata): T | undefined;
1242
- /**
1243
- * Check if configuration is valid without throwing
1244
- *
1245
- * @param plugin - Plugin metadata
1246
- * @param config - Configuration to check
1247
- * @returns True if valid, false otherwise
1248
- */
1249
- isConfigValid(plugin: PluginMetadata, config: any): boolean;
1250
- /**
1251
- * Get configuration errors without throwing
1252
- *
1253
- * @param plugin - Plugin metadata
1254
- * @param config - Configuration to check
1255
- * @returns Array of validation errors, or empty array if valid
1256
- */
1257
- getConfigErrors(plugin: PluginMetadata, config: any): Array<{
1258
- path: string;
1259
- message: string;
1260
- }>;
1261
- private formatZodErrors;
1332
+ /** A single unpacked artifact file. `path` is POSIX, archive-relative. */
1333
+ interface IntegrityFile {
1334
+ path: string;
1335
+ data: Uint8Array;
1336
+ }
1337
+ type IntegrityViolationKind = 'digest_mismatch' | 'missing_file' | 'extra_file';
1338
+ /** One structured integrity finding (the rejection envelope's unit). */
1339
+ interface IntegrityViolation {
1340
+ kind: IntegrityViolationKind;
1341
+ /** Artifact-relative POSIX path the finding is about. */
1342
+ path: string;
1343
+ /** The digest the manifest declares (absent for `extra_file`). */
1344
+ declared?: string;
1345
+ /** The digest computed from the supplied bytes (absent unless comparable). */
1346
+ actual?: string;
1347
+ }
1348
+ interface VerifyIntegrityResult {
1349
+ /** Overall verdict: every declared digest matched and no file was unaccounted for. */
1350
+ ok: boolean;
1351
+ /** True when no integrity map was supplied, so nothing was checked (still `ok`). */
1352
+ skipped: boolean;
1353
+ /** Number of declared entries whose digests were computed and compared. */
1354
+ checked: number;
1355
+ violations: IntegrityViolation[];
1262
1356
  }
1263
1357
  /**
1264
- * Create a plugin config validator
1358
+ * Verify `files` against the manifest's declared `integrity` map.
1265
1359
  *
1266
- * @param logger - Logger instance
1267
- * @returns Plugin config validator
1360
+ * `options.exempt` names paths outside the map's coverage — the compiled
1361
+ * manifest itself and the signature placeholder, which `computeIntegrity`
1362
+ * excludes at build time (the manifest cannot hash itself, and the
1363
+ * signature signs the manifest) — so their presence is never an
1364
+ * `extra_file` finding.
1268
1365
  */
1269
- declare function createPluginConfigValidator(logger: Logger): PluginConfigValidator;
1366
+ declare function verifyIntegrity(files: readonly IntegrityFile[], integrity: Readonly<Record<string, string>> | null | undefined, options?: {
1367
+ exempt?: readonly string[];
1368
+ }): VerifyIntegrityResult;
1369
+ /** Render one violation as a single human-actionable line. */
1370
+ declare function formatIntegrityViolation(v: IntegrityViolation): string;
1270
1371
 
1271
1372
  /**
1272
1373
  * Plugin Permissions
@@ -1882,6 +1983,188 @@ declare function resolveApiKeyPrincipal(ql: any, headers: any, nowMs?: number, t
1882
1983
  */
1883
1984
  declare function resolveApiKeyAdmission(ql: any, headers: any, nowMs?: number, tenancyPosture?: TenancyPosture): Promise<ApiKeyAdmission>;
1884
1985
 
1986
+ /**
1987
+ * [#13279] The LOUD failure an unreachable permission store raises.
1988
+ *
1989
+ * ## The defect this exists to end
1990
+ *
1991
+ * `resolveAuthzContext`'s per-read helper `tryFind` used to answer a THROWN
1992
+ * read the same way it answers an EMPTY one: `[]`. So a permission-store
1993
+ * outage resolved as a well-formed context for an AUTHENTICATED principal
1994
+ * holding ZERO capabilities, and the package-management door answered
1995
+ * `403 FORBIDDEN` — "Reading packages requires the `studio.access` or
1996
+ * `setup.access` capability." That answer was measured BYTE-IDENTICAL
1997
+ * (`JSON.stringify` equal, against a positive control that separates two
1998
+ * answers which differ) to the answer a caller who genuinely holds nothing
1999
+ * gets. An administrator was told they lack a capability, during an outage of
2000
+ * the store that holds the capability.
2001
+ *
2002
+ * The resolver was asserting a fact it did not have. "No rows came back"
2003
+ * and "the read failed" are different facts, and only one of them licenses
2004
+ * the sentence "this user holds nothing".
2005
+ *
2006
+ * ## …and "the read failed" turned out to be TWO facts (ruled 2026-08-30, A)
2007
+ *
2008
+ * A read ALSO throws when the table was never PROVISIONED — a real engine,
2009
+ * wired and reachable, whose `sys_*` tables were never created. There "this
2010
+ * user holds nothing" is TRUE, not invented, so failing loud would refuse
2011
+ * service to a correctly-configured deployment. The first implementation of
2012
+ * this card did exactly that and four CI suites measured it.
2013
+ *
2014
+ * So `tryFind` raises this error only for a read failure that is NOT
2015
+ * positively identified as an unprovisioned table, asking the one relocated
2016
+ * `isMissingTableError` predicate (`@objectstack/types`) rather than a second
2017
+ * copy. The boundary, the ruling's verbatim text and the false-positive risk
2018
+ * signed off with it are written beside that call in `resolve-authz-context.ts`;
2019
+ * both directions are pinned in `authz-store-unavailable.test.ts` §4.
2020
+ *
2021
+ * ## Maintainer ruling, 2026-08-30, verbatim 「第一批其余同意」
2022
+ *
2023
+ * > `tryFind` 区分「无行」与「读失败」,读失败 fail-loud —— 权限库不可达时
2024
+ * > 不再解析为「已认证零能力」,而是响亮拒绝(与真实能力拒绝的 403 可区分),
2025
+ * > 让宕机不再伪装成一次逐字节相同的能力否决。
2026
+ *
2027
+ * The ruling fixes the DIRECTION and leaves the spelling to the implementation.
2028
+ *
2029
+ * ## Why a THROW, and not a field on the envelope
2030
+ *
2031
+ * The alternative was a discriminator field on `ResolvedAuthzContext` — the
2032
+ * shape `authRefusal` already has. That was rejected on a MEASUREMENT, not a
2033
+ * preference: `authRefusal` has existed since #8287 and, outside this module
2034
+ * and its own unit test, has **zero** consumers anywhere in the repo. A
2035
+ * diagnostic field on this envelope is demonstrably not read by any door. Every
2036
+ * transport reads `userId` and `systemPermissions`; a new sibling field would
2037
+ * have to be taught to eight separate call sites before it made a single door
2038
+ * louder, and would answer the old quiet 403 at every site that was missed.
2039
+ *
2040
+ * A field is quiet by default and must be deliberately made loud. A throw is
2041
+ * loud by default and must be deliberately silenced. On a security surface
2042
+ * whose whole defect is a silence, the default is the entire decision.
2043
+ *
2044
+ * It is also the idiom this platform already uses for unresolvable authority:
2045
+ * `packages/mcp`'s stdio entry throws and refuses to start rather than run with
2046
+ * an authority it could not resolve.
2047
+ *
2048
+ * ## Why `SERVICE_UNAVAILABLE` / 503, and why that is not a new wire shape
2049
+ *
2050
+ * `SERVICE_UNAVAILABLE` is an EXISTING member of the closed ADR-0112 wire
2051
+ * vocabulary (`StandardErrorCode`, `packages/spec/src/api/errors.zod.ts`), and
2052
+ * `HttpStatusErrorCodeMap` already maps it to 503 — "service exists but is
2053
+ * temporarily down". Nothing is added to the vocabulary and no envelope gains
2054
+ * or loses a key: a door that already renders `{ code, message }` renders this
2055
+ * one the same way. What changes is WHICH declared code an outage selects —
2056
+ * from the caller's `FORBIDDEN` to the operator's `SERVICE_UNAVAILABLE`.
2057
+ *
2058
+ * That is the ruling's own test, stated on the wire: 503 is not 403, so an
2059
+ * outage is no longer answerable as a capability denial.
2060
+ *
2061
+ * ## Recognise by BRAND, never by `instanceof`
2062
+ *
2063
+ * {@link isAuthzStoreUnavailableError} tests a own-property brand rather than
2064
+ * `instanceof`. This error crosses package boundaries (`@objectstack/core` →
2065
+ * rest / runtime / mcp / services / plugins) and a monorepo resolves the same
2066
+ * module through more than one path (`src` under vitest aliases, `dist` under
2067
+ * the published `exports`). Two copies of this class make `instanceof` answer
2068
+ * FALSE for a genuine instance — which, here, silently restores the exact
2069
+ * quiet 403 this module exists to remove. The brand survives duplication.
2070
+ */
2071
+ /** HTTP status an unreachable authorization store answers with. */
2072
+ declare const AUTHZ_STORE_UNAVAILABLE_STATUS: 503;
2073
+ /**
2074
+ * Machine code — an EXISTING `StandardErrorCode` member (ADR-0112: SCREAMING).
2075
+ * Deliberately NOT a new code: the wire vocabulary is closed.
2076
+ */
2077
+ declare const AUTHZ_STORE_UNAVAILABLE_CODE: "SERVICE_UNAVAILABLE";
2078
+ /**
2079
+ * Human-facing message. States the OUTAGE, and says explicitly that no
2080
+ * capability judgement was reached — so neither the caller nor the operator
2081
+ * reads it as a permission verdict.
2082
+ */
2083
+ declare const AUTHZ_STORE_UNAVAILABLE_MESSAGE: string;
2084
+ /**
2085
+ * The own-property brand {@link isAuthzStoreUnavailableError} tests for.
2086
+ * A string-keyed own property (not a `Symbol.for` registry key), so a
2087
+ * duplicated copy of this module still brands identically — which is exactly
2088
+ * what `instanceof` cannot do (module doc above).
2089
+ *
2090
+ * ⚠️ The brand does NOT survive `structuredClone`, and no claim here depends
2091
+ * on it doing so — the same measured behaviour `service-not-registered.ts`
2092
+ * records for its own brand. Reproduce on Node 22.22.2:
2093
+ *
2094
+ * ```js
2095
+ * const e = new Error('x'); e.__brand = true; e.code = 'C';
2096
+ * const c = structuredClone(e);
2097
+ * // c.__brand === undefined c.code === undefined c.message === 'x'
2098
+ * // control: structuredClone({ __brand: true, code: 'C' }) keeps BOTH keys
2099
+ * ```
2100
+ *
2101
+ * `Error` has a dedicated serialization carrying `message`, `stack` and
2102
+ * `cause` only, so it DROPS every other own property — this brand, the
2103
+ * ADR-0112 `code`, `status` and `object` alike (and a subclass's own `name`
2104
+ * returns as `'Error'`). The plain-object control is the half that proves the
2105
+ * loss is specific to `Error`, not general to `structuredClone`.
2106
+ *
2107
+ * ⛔ So never branch on this brand across a worker or `postMessage` boundary:
2108
+ * it would answer `false` and fail OPEN. Every call site today is in-process —
2109
+ * `rethrowAuthzStoreUnavailable` on the rest rethrow paths and
2110
+ * `isAuthzStoreUnavailableError` inside service `catch` blocks.
2111
+ */
2112
+ declare const AUTHZ_STORE_UNAVAILABLE_BRAND: "__objectstackAuthzStoreUnavailable";
2113
+ /**
2114
+ * Raised when a permission-store read FAILED — never when it legitimately
2115
+ * returned no rows.
2116
+ *
2117
+ * Carries the `object` whose read failed so an operator sees WHICH table was
2118
+ * unreachable, and the originating error as `cause` so the driver's own
2119
+ * diagnostic is not lost behind this one.
2120
+ */
2121
+ declare class AuthzStoreUnavailableError extends Error {
2122
+ /** Brand — see the module doc on why this is not `instanceof`. */
2123
+ readonly [AUTHZ_STORE_UNAVAILABLE_BRAND]: true;
2124
+ /** ADR-0112 wire code. */
2125
+ readonly code: "SERVICE_UNAVAILABLE";
2126
+ /** HTTP status a transport should answer. */
2127
+ readonly status: 503;
2128
+ /** The object/table whose read failed (e.g. `sys_user_permission_set`). */
2129
+ readonly object: string;
2130
+ /** The driver's originating failure, kept so its diagnostic is not lost. */
2131
+ readonly cause?: unknown;
2132
+ constructor(object: string, cause?: unknown);
2133
+ }
2134
+ /**
2135
+ * True when `err` is the loud authorization-store failure above.
2136
+ *
2137
+ * The predicate every transport uses to tell "the store was unreachable" apart
2138
+ * from every other throw, so a fail-closed `catch` can re-raise THIS one
2139
+ * without loosening its handling of anything else.
2140
+ */
2141
+ declare function isAuthzStoreUnavailableError(err: unknown): err is AuthzStoreUnavailableError;
2142
+ /**
2143
+ * The `.catch` argument every fail-closed seam between `resolveAuthzContext`
2144
+ * and a door should use in place of `() => undefined`.
2145
+ *
2146
+ * Re-raises {@link AuthzStoreUnavailableError} and swallows everything else to
2147
+ * `undefined`, so a seam keeps its existing fail-closed behaviour for every
2148
+ * fault EXCEPT the one the 2026-08-30 ruling requires to stay loud.
2149
+ *
2150
+ * ## Why the seams need this at all
2151
+ *
2152
+ * Making `tryFind` throw is necessary but NOT sufficient, and that was
2153
+ * MEASURED rather than assumed. Between the resolver and the package door sit
2154
+ * three independent nets — `computeExecCtx`'s `try { … } catch { return
2155
+ * undefined; }`, `resolvePackageRouteExecutionContext`'s `.catch(() =>
2156
+ * undefined)`, and `refusePackageRequest`'s own — and with the throw in place
2157
+ * but the nets untouched, the door answered **401**: the outage had simply
2158
+ * changed disguises, from "you hold no capability" (403) into "you are not
2159
+ * authenticated" (401), which is byte-identical to a genuine anonymous caller.
2160
+ * Distinguishable from a capability denial, yes — but still not LOUD, and now
2161
+ * wearing the costume of a different card's defect.
2162
+ *
2163
+ * A blanket `catch` cannot tell a fault from a refusal, so each net has to be
2164
+ * told once, in one shape. This is that shape.
2165
+ */
2166
+ declare function rethrowAuthzStoreUnavailable(err: unknown): undefined;
2167
+
1885
2168
  /** The transport-agnostic authorization envelope produced from a request. */
1886
2169
  interface ResolvedAuthzContext {
1887
2170
  userId?: string;
@@ -1905,7 +2188,9 @@ interface ResolvedAuthzContext {
1905
2188
  /**
1906
2189
  * [ADR-0095 D2/D3] The monotonic posture rung this principal resolves to,
1907
2190
  * DERIVED once here from held capability grants (never a better-auth role):
1908
- * `PLATFORM_ADMIN` (unscoped `admin_full_access`) > `TENANT_ADMIN`
2191
+ * `PLATFORM_ADMIN` (an unscoped `admin_full_access` grant, or — since #11663
2192
+ * L2 — a VERIFIED `sys_user.email` on the deployment's declared
2193
+ * administrator list) > `TENANT_ADMIN`
1909
2194
  * (`organization_admin`) > `MEMBER` (the authenticated floor). `EXTERNAL` is
1910
2195
  * defined/test-locked but never resolved yet (no external principal type —
1911
2196
  * see `posture-ladder.ts`). Present only for an authenticated principal;
@@ -1958,8 +2243,19 @@ interface ResolveAuthzInput {
1958
2243
  tenancyPosture?: TenancyPosture;
1959
2244
  }
1960
2245
  /**
1961
- * Resolve the authorization context for an inbound request. Always resolves —
1962
- * never throws. Anonymous requests yield `{ positions: [], permissions: [], ... }`.
2246
+ * Resolve the authorization context for an inbound request. Anonymous requests
2247
+ * yield `{ positions: [], permissions: [], ... }`.
2248
+ *
2249
+ * ⚠️ [#13279] This function used to document itself as "Always resolves — never
2250
+ * throws", and that total guarantee WAS the defect: the only way to always
2251
+ * resolve across a permission-store outage is to report a capability set the
2252
+ * resolver never actually read. It now throws exactly one error —
2253
+ * {@link AuthzStoreUnavailableError}, when a permission-store read was issued
2254
+ * and failed FOR A REASON THAT IS NOT AN UNPROVISIONED TABLE. Every other path
2255
+ * still resolves, including every MISSING-service path and every
2256
+ * never-provisioned one. A transport that fails closed on unexpected throws should re-raise this
2257
+ * one ({@link isAuthzStoreUnavailableError}) rather than degrade it to a
2258
+ * refusal — degrading it restores the disguise the ruling removed.
1963
2259
  */
1964
2260
  declare function resolveAuthzContext(input: ResolveAuthzInput): Promise<ResolvedAuthzContext>;
1965
2261
  /** The authorization grants a KNOWN user holds — a subset of {@link ResolvedAuthzContext}. */
@@ -1990,6 +2286,25 @@ interface ResolveUserAuthzGrantsOptions {
1990
2286
  seedPermissions?: string[];
1991
2287
  /** A caller-supplied email (e.g. from the session) that wins over the `sys_user` read. */
1992
2288
  seedEmail?: string;
2289
+ /**
2290
+ * ⭐ Force a FRESH resolution even when the #11971 grants cache is enabled
2291
+ * — the ruled bypass list of #11633 (leg B, maintainer acceptance
2292
+ * 2026-08-25). Two call sites carry it, for two ruled reasons:
2293
+ *
2294
+ * - `plugin-security/src/explain-engine.ts` (`buildContextForUser`): the
2295
+ * permission explainer is the tool an administrator uses to VERIFY that
2296
+ * a revocation took effect. An explainer answering from cache would
2297
+ * explain a state that no longer exists, and would do it at exactly the
2298
+ * moment someone is checking.
2299
+ * - `service-automation/src/plugin.ts` (`runAs:'user'` runs): automation
2300
+ * runs are not request-shaped and can be long-lived; they must not pin
2301
+ * an envelope.
2302
+ *
2303
+ * Bypassing reads NOTHING from the cache and writes NOTHING into it — a
2304
+ * bypassed resolution must not repopulate an entry the next cached caller
2305
+ * would then trust.
2306
+ */
2307
+ bypassGrantsCache?: boolean;
1993
2308
  }
1994
2309
  /**
1995
2310
  * resolveUserAuthzGrants — the userId-driven core of {@link resolveAuthzContext}.
@@ -2010,28 +2325,117 @@ interface ResolveUserAuthzGrantsOptions {
2010
2325
  * automation engine calls this to run the flow's data ops exactly as that user
2011
2326
  * — not the bare member/everyone fallback the missing grants used to leave it.
2012
2327
  *
2013
- * Fail-closed like its parent: every read is defensive, a missing engine/table
2014
- * yields an empty-but-valid envelope, and it never throws.
2328
+ * Fail-closed like its parent: a missing engine yields an empty-but-valid
2329
+ * envelope.
2330
+ *
2331
+ * ⚠️ [#13279] "and it never throws" was removed from this sentence deliberately.
2332
+ * A permission-store read that is issued and FAILS now raises
2333
+ * {@link AuthzStoreUnavailableError} rather than contributing an empty grant
2334
+ * set, so a `runAs:'user'` automation cannot silently run with the authority of
2335
+ * a user whose grants were never read.
2015
2336
  */
2016
2337
  declare function resolveUserAuthzGrants(ql: any, userId: string, opts?: ResolveUserAuthzGrantsOptions): Promise<UserAuthzGrants>;
2338
+ /**
2339
+ * hasPlatformAdminStanding — the ID-SHAPED platform-admin question, asked in
2340
+ * exactly one place.
2341
+ *
2342
+ * ADR-0068 D2 defined PLATFORM standing as one thing: an UNSCOPED
2343
+ * (`organization_id = null`) `sys_user_permission_set` grant on the
2344
+ * `admin_full_access` set, held **now**. Since #11663 L2 there is a SECOND
2345
+ * anchor beside it — a `sys_user` row whose VERIFIED email is on the
2346
+ * deployment's declared administrator list (`OS_PLATFORM_OWNER_EMAIL`) — and
2347
+ * this predicate answers for both, for free, because it is a projection rather
2348
+ * than a copy (see below). A surface that only knows a user id —
2349
+ * a session-payload derivation, a platform-operator route gate, an
2350
+ * impersonation oracle — asks here, so it never has to re-read the grant tables
2351
+ * itself, which is the prohibition this module's header states.
2352
+ *
2353
+ * It is a PROJECTION of {@link resolveUserAuthzGrants}, never a second
2354
+ * derivation: the answer is the `PLATFORM_ADMIN` rung of the posture ladder,
2355
+ * and that rung is derived from the unscoped-grant evidence and nothing else.
2356
+ * Everything that governs those grants therefore applies here by construction
2357
+ * and cannot drift from it — the ADR-0091 validity window (§6), the ADR-0049
2358
+ * `active` flag on the catalogue row (§6b), the system-identity read, and the
2359
+ * resolution of `admin_full_access` BY ID rather than by scanning a page of the
2360
+ * catalogue. Each of those was missing from a hand-written copy of this
2361
+ * predicate; none of them can be missing from a projection.
2362
+ *
2363
+ * ⛔ Read the RUNG — never `positions.includes(BUILTIN_IDENTITY_PLATFORM_ADMIN)`.
2364
+ * The positions list is wider on purpose: an ADR-0057 D4 `sys_user_position`
2365
+ * row may spell that very name, and a platform-RBAC assignment is not the D2
2366
+ * capability grant. The two readings genuinely differ, so the narrow one is the
2367
+ * one that gets a name here.
2368
+ *
2369
+ * ⛔ The options are deliberately NOT {@link ResolveUserAuthzGrantsOptions}.
2370
+ * That type carries caller-supplied seeds (`seedEmail`, `seedPermissions`) for
2371
+ * transports that already resolved part of a principal; an authorization
2372
+ * predicate that accepted them would let a caller supply part of its own
2373
+ * verdict. Clock injection is the only thing a caller may pass, so this
2374
+ * function's answer is a function of `(ql, userId)` and the stored rows alone.
2375
+ *
2376
+ * ⚠️ This is the PER-USER predicate. The POPULATION question ("which user is
2377
+ * the platform admin?" — `ensure-default-organization.ts`) is a different kind
2378
+ * and is deliberately not expressible through it; do not widen this to serve
2379
+ * it.
2380
+ *
2381
+ * Fail-CLOSED: an empty id, a missing engine, or any unreadable lookup answers
2382
+ * `false`. This backs security gates, and an unverifiable actor never passes.
2383
+ */
2384
+ declare function hasPlatformAdminStanding(ql: any, userId: string, opts?: {
2385
+ nowMs?: number;
2386
+ }): Promise<boolean>;
2017
2387
  interface ResolveLocalizationInput {
2018
2388
  ql: any;
2019
- /** Settings service exposing `get(namespace, key, { tenantId, userId })`. */
2389
+ /**
2390
+ * Settings service occupant. Two methods are consumed, in this order:
2391
+ *
2392
+ * - `getMany(namespace, keys, { tenantId, userId })` — PREFERRED since
2393
+ * #10826, and what this resolver calls for all three localization keys in
2394
+ * ONE grouped read.
2395
+ * - `get(namespace, key, { tenantId, userId })` — the per-key fallback,
2396
+ * taken only when the occupant does not expose `getMany` (three parallel
2397
+ * reads; see the feature-detect below).
2398
+ *
2399
+ * `getMany` is OPTIONAL for an occupant: the branch is feature-detected, so
2400
+ * a service that predates it still resolves — at three reads instead of one.
2401
+ * Typed `any` deliberately (the occupant's shape varies by host); the
2402
+ * declaration above is the contract this resolver actually relies on, and it
2403
+ * is prose precisely because nothing type-checks it — `getService` is a cast
2404
+ * and `rest-server.ts` widens the provider's return to a bare promise.
2405
+ */
2020
2406
  settings?: any;
2021
2407
  tenantId?: string;
2022
2408
  userId?: string;
2023
2409
  }
2410
+ type LocalizationResult = {
2411
+ timezone: string;
2412
+ locale: string;
2413
+ currency?: string;
2414
+ };
2024
2415
  /**
2025
2416
  * Resolve workspace localization defaults (reference `timezone` / `locale` /
2026
2417
  * `currency`). Canonical path is the `localization` SettingsManifest (cascade:
2027
2418
  * platform default → global → tenant); falls back to direct tenant-scoped
2028
2419
  * `sys_setting` rows, then the built-ins `UTC` / `en-US`. Never throws.
2029
- */
2030
- declare function resolveLocalizationContext(input: ResolveLocalizationInput): Promise<{
2031
- timezone: string;
2032
- locale: string;
2033
- currency?: string;
2034
- }>;
2420
+ *
2421
+ * The DIRECT `sys_setting` read failing outright (backend fault — table
2422
+ * missing, connection refused, etc.) is memoized for
2423
+ * {@link LOCALIZATION_FAILURE_CACHE_TTL_MS} per `(ql, tenantId, userId)` so
2424
+ * the failing query — and the driver's log line for it — does not repeat
2425
+ * every request (#10221). A settings-service refusal is not a backend fault
2426
+ * and never populates that memo (#11877).
2427
+ *
2428
+ * A SUCCESSFUL read is cached too, since #11966 (leg C of #11633) — but only
2429
+ * when the engine carries the write-epoch seam, and only until the first of:
2430
+ * a `localization` settings change, an engine write, or
2431
+ * `OS_LOCALIZATION_CACHE_TTL_MS`. Both invalidations are synchronous and
2432
+ * in-process, which is what lets the success cache exist at all: the
2433
+ * dogfood analytics-bucketing test writes a new org timezone and reads it back
2434
+ * on the very next request, and it is kept unweakened as this leg's acceptance
2435
+ * criterion. See the leg-C docblock above for the full contract, including why
2436
+ * a `ql` with no seam declines to cache rather than falling back to the TTL.
2437
+ */
2438
+ declare function resolveLocalizationContext(input: ResolveLocalizationInput): Promise<LocalizationResult>;
2035
2439
 
2036
2440
  /**
2037
2441
  * ADR-0069 — authentication-policy session gate.
@@ -2580,6 +2984,20 @@ declare function isRowActive(row: ActivatableRow | null | undefined): boolean;
2580
2984
  * the table-level half of the same guarantee: a resolver that starts deriving
2581
2985
  * administrator standing from a new table would otherwise be invisible to a
2582
2986
  * column-set comparison, because the new table appears in neither side's list.
2987
+ *
2988
+ * ## ⚠️ Tables are no longer the whole surface (#11663 L2)
2989
+ *
2990
+ * Since the platform-admin re-anchor's core leg, one input to the administrator
2991
+ * derivation is NOT a table at all: the deployment's declared administrator
2992
+ * list, read from the environment on every resolution
2993
+ * (`security/platform-admin.ts`). A file that listed only tables would go on
2994
+ * being perfectly accurate about the tables while silently claiming the
2995
+ * derivation reads nothing else — the same shape as the stale comment this file
2996
+ * replaced, one level up. {@link ADMIN_STANDING_NON_TABLE_INPUTS} is the place
2997
+ * that says so, and it is deliberately a SEPARATE export rather than a
2998
+ * pseudo-row in the table map: the map is compared for equality against
2999
+ * observed table reads, and a pseudo-row would have to be excluded from that
3000
+ * comparison by name, which is exactly the kind of special case that rots.
2583
3001
  */
2584
3002
  /** How a table this resolver reads relates to "who is an administrator". */
2585
3003
  interface AdminStandingTable {
@@ -2606,11 +3024,37 @@ interface AdminStandingTable {
2606
3024
  * principal, and therefore all of `resolveUserAuthzGrants`. The API-key
2607
3025
  * ADMISSION path (`resolveApiKeyAdmission`) is outside it on purpose: it
2608
3026
  * authenticates a principal and seeds `permissions` with the key's scopes, and
2609
- * confers no administrator standing of its own — `hasPlatformAdminGrant` (§6b)
2610
- * is set only from a `sys_permission_set` row reached through an UNSCOPED
2611
- * `sys_user_permission_set` grant, never from a scope string.
3027
+ * confers no administrator standing of its own — `hasPlatformAdminGrant` is set
3028
+ * from a `sys_permission_set` row reached through an UNSCOPED
3029
+ * `sys_user_permission_set` grant (§6b) or from the deployment config matched
3030
+ * against the caller's own STORED `sys_user` row (§6b-config), never from a
3031
+ * scope string and never from the caller-seedable `grants.email`.
2612
3032
  */
2613
3033
  declare const ADMIN_STANDING_SURFACE: Readonly<Record<string, AdminStandingTable>>;
3034
+ /** A derivation input that is not a table — see {@link ADMIN_STANDING_NON_TABLE_INPUTS}. */
3035
+ interface AdminStandingNonTableInput {
3036
+ /** How the value reaches the resolver, e.g. `env` for a process environment variable. */
3037
+ readonly kind: 'env';
3038
+ /** The exact spelling an operator sets — quotable verbatim in a refusal message. */
3039
+ readonly name: string;
3040
+ /** What it decides, and what a break-glass guard can and cannot do about it. */
3041
+ readonly reason: string;
3042
+ }
3043
+ /**
3044
+ * [#11663 L2] Inputs to the administrator derivation that no table write can
3045
+ * reach — declared here so this file's silence about them cannot be read as
3046
+ * "the derivation reads only tables".
3047
+ *
3048
+ * The practical consequence is the one worth writing down: a break-glass guard
3049
+ * simulates a pending WRITE, and there is no write to simulate for any of
3050
+ * these. Standing that rests on one of them is taken away by changing the
3051
+ * deployment's configuration and rolling the process, which is deliberately
3052
+ * outside every in-product path — including every path an agent could be talked
3053
+ * into calling. That is the whole point of the config anchor, and it is also
3054
+ * the reason a guard cannot promise to prevent this class of lockout: it can
3055
+ * only refuse the writes it can see.
3056
+ */
3057
+ declare const ADMIN_STANDING_NON_TABLE_INPUTS: readonly AdminStandingNonTableInput[];
2614
3058
  /** The tables a write to which can change who is an administrator. */
2615
3059
  declare function adminStandingTables(): string[];
2616
3060
  /**
@@ -2619,6 +3063,147 @@ declare function adminStandingTables(): string[];
2619
3063
  */
2620
3064
  declare function adminStandingColumns(table: string): readonly string[] | undefined;
2621
3065
 
3066
+ /**
3067
+ * The one separator {@link parsePlatformAdminEmails} splits on (Choice 2B).
3068
+ * Same shape as `OS_CORS_ORIGIN`, the existing comma-separated precedent.
3069
+ */
3070
+ declare const PLATFORM_ADMIN_EMAIL_SEPARATOR = ",";
3071
+ /**
3072
+ * The ONE normalization, applied to both sides of every comparison: trim, then
3073
+ * lowercase. Email domains are case-insensitive and every mailbox this platform
3074
+ * issues is too, so an operator who types `Ada@Example.com` and a row storing
3075
+ * `ada@example.com` must be one administrator, not two half-matches.
3076
+ */
3077
+ declare function normalizePlatformAdminEmail(value: unknown): string;
3078
+ /** The parsed state of `OS_PLATFORM_OWNER_EMAIL` for one raw value. */
3079
+ interface PlatformAdminEmailConfig {
3080
+ /**
3081
+ * Normalized, de-duplicated administrator addresses in the order the operator
3082
+ * declared them. EMPTY when the variable is unset, blank, or refused — those
3083
+ * three are one outcome by design (zero config-derived administrators), and
3084
+ * they are told apart by {@link refusal} rather than by a second empty value.
3085
+ */
3086
+ readonly emails: readonly string[];
3087
+ /**
3088
+ * The SAME administrators as {@link emails} and index-aligned with it, each
3089
+ * spelled as the operator typed it — trimmed only, never lowercased (exactly
3090
+ * what `resolvePlatformOwnerEmail()` used to hand a single-value reader).
3091
+ *
3092
+ * It exists so that no consumer ever has a reason to split {@link raw} a
3093
+ * second time. Two readers need the as-typed form and neither may re-parse
3094
+ * to get it: the platform-admin STANDING surface's by-email `sys_user`
3095
+ * lookup queries the verbatim spelling alongside the normalized one (an
3096
+ * imported/legacy row may not be stored lowercased, and a driver `where` is
3097
+ * an exact match) — that lookup is `resolvePlatformAdminStanding` in
3098
+ * plugin-security's `platform-admin-service.ts`, which inherited the
3099
+ * two-spelling discipline from the elevation gate the #11663 re-anchor
3100
+ * (leg L4) retired — and the walled boot diagnostic quotes the addresses
3101
+ * back to the operator, who should see what they wrote.
3102
+ */
3103
+ readonly declaredSpellings: readonly string[];
3104
+ /** What the operator actually typed, when the variable was set to anything. */
3105
+ readonly raw?: string;
3106
+ /**
3107
+ * Set when the variable was DECLARED but refused, naming the offending entry.
3108
+ * `emails` is empty in that case: the whole variable fails closed, never the
3109
+ * one entry (Choice 2B).
3110
+ */
3111
+ readonly refusal?: string;
3112
+ }
3113
+ /**
3114
+ * Parse one raw `OS_PLATFORM_OWNER_EMAIL` value into the administrator list.
3115
+ *
3116
+ * Pure — no env read, no logging — so the whole parse is testable as a
3117
+ * function of its input. {@link resolvePlatformAdminEmails} is the env-reading,
3118
+ * memoizing, once-per-value-loud wrapper around it.
3119
+ */
3120
+ declare function parsePlatformAdminEmails(raw: string | undefined): PlatformAdminEmailConfig;
3121
+ /**
3122
+ * Sink for the refusal notice. `console` by default so the loudness does not
3123
+ * depend on any host wiring it up — a deployment that declared administrators
3124
+ * and got none must never find that out silently. Swappable for tests.
3125
+ */
3126
+ interface PlatformAdminConfigSink {
3127
+ error(message: string): void;
3128
+ warn(message: string): void;
3129
+ }
3130
+ /** Redirect this module's notices (tests). Returns the previous sink. */
3131
+ declare function setPlatformAdminConfigSink(next: PlatformAdminConfigSink | undefined): PlatformAdminConfigSink;
3132
+ /**
3133
+ * Resolve the deployment's declared platform administrators — live from the
3134
+ * environment, memoized on the raw string, and LOUD exactly once per distinct
3135
+ * refused value.
3136
+ *
3137
+ * Silence for an UNSET variable is deliberate and is not the same decision:
3138
+ * every `single`-posture deployment runs that way by design (Choice 4A leaves
3139
+ * first-user promotion in place there), and warning on the shipped default is
3140
+ * how a log people read becomes a log people skim. A walled posture with the
3141
+ * variable unset already REFUSES BOOT one layer up, in plugin-auth.
3142
+ */
3143
+ declare function resolvePlatformAdminEmails(): PlatformAdminEmailConfig;
3144
+ /** Drop the memo — for tests that drive several values through one process. */
3145
+ declare function resetPlatformAdminEmailMemo(): void;
3146
+ /**
3147
+ * Does this stored `sys_user` row belong to a declared platform administrator?
3148
+ *
3149
+ * Fail-closed on every axis: an empty/refused config answers `false` without
3150
+ * looking at the row at all, an address that is not on the list answers
3151
+ * `false`, and an address that IS on the list but whose `email_verified`
3152
+ * column does not read verified answers `false` too. The last one is the point
3153
+ * of the whole leg — an unverified account holding a configured address confers
3154
+ * nothing, so an attacker who registers the operator's address before the
3155
+ * operator does gains no standing by it.
3156
+ *
3157
+ * ⚠️ `row` MUST be the caller's own stored `sys_user` row. See this module's
3158
+ * header: `grants.email` is caller-seedable and reading it here would be an
3159
+ * escalation channel.
3160
+ */
3161
+ declare function matchesConfiguredPlatformAdmin(row: unknown, config: PlatformAdminEmailConfig): boolean;
3162
+ /**
3163
+ * [#13147] Is this bare ADDRESS one of the declared administrators?
3164
+ *
3165
+ * The membership half of {@link matchesConfiguredPlatformAdmin}, spelled once
3166
+ * and exported, because the row-and-verified predicate above is not the shape
3167
+ * every reader of `OS_PLATFORM_OWNER_EMAIL` needs:
3168
+ *
3169
+ * - the walled platform-admin STANDING surface must keep the two halves
3170
+ * SEPARATE — `resolvePlatformAdminStanding`
3171
+ * (`plugin-security/platform-admin-service.ts`, reported at boot by
3172
+ * `bootstrap-platform-admin.ts`) answers `registered` and `verified` as two
3173
+ * independent per-entry fields, so the operator's log can tell "not
3174
+ * registered yet" apart from "registered, NOT verified". ⚠️ That reason
3175
+ * predates the #11663 re-anchor and survives it: the pair used to be the
3176
+ * retired elevation gate's `walled_owner_not_registered` /
3177
+ * `walled_owner_not_verified` reasons — the mechanism moved, the need to
3178
+ * keep the halves apart did not;
3179
+ * - the creation-time operator stamp (`plugin-auth`) is handed an email
3180
+ * STRING by better-auth, before any row exists to read;
3181
+ * - the Layer 0 wall bypass takes a fast negative on the session's
3182
+ * server-resolved email before it spends a `sys_user` read.
3183
+ *
3184
+ * ⛔ Those readers must NOT hand-roll `config.emails.includes(x.toLowerCase())`
3185
+ * instead. That expression is where a seventh dialect gets born: it silently
3186
+ * drops the trim, and a stray space in one list entry then makes an
3187
+ * administrator vanish with nothing to notice. One membership expression, one
3188
+ * normalization ({@link normalizePlatformAdminEmail}), one place to fix.
3189
+ *
3190
+ * Fail-closed like everything else here: an empty or refused config answers
3191
+ * `false` without looking at the candidate, and a blank/non-string candidate
3192
+ * answers `false` against any config.
3193
+ *
3194
+ * ⚠️ This is a match against CONFIGURATION only — it says nothing about whether
3195
+ * the address is verified, or whether the caller actually holds it. Standing
3196
+ * still requires {@link matchesConfiguredPlatformAdmin} over the caller's own
3197
+ * stored row; see this module's header for why `grants.email` is never it.
3198
+ */
3199
+ declare function isConfiguredPlatformAdminEmail(email: unknown, config: PlatformAdminEmailConfig): boolean;
3200
+ declare function reportLegacyPlatformAdminGrant(input: {
3201
+ userId: string;
3202
+ email?: unknown;
3203
+ }): void;
3204
+ /** Drop the once-per-process latch — for tests. */
3205
+ declare function resetLegacyPlatformAdminGrantReport(): void;
3206
+
2622
3207
  /**
2623
3208
  * [#7678] The `?status=` vocabulary of the audience-binding suggestion list
2624
3209
  * (ADR-0090 D5/D9) — ONE owner for a rule that had exactly one implementation
@@ -2685,7 +3270,12 @@ declare const unknownAudienceBindingSuggestionStatusMessage: (value: string) =>
2685
3270
  * `__expandRead` marks a read as a lookup EXPANSION sub-read (it no longer
2686
3271
  * relaxes any gate — #7626 removed that waiver — but it still travels with
2687
3272
  * one operation and must not be inherited by another), `__referentialFieldClear`
2688
- * authorizes the referential-clear write.
3273
+ * authorizes the referential-clear write. [#13644] The latter also has a
3274
+ * DECLARED, read-only projection — `HookContext.referentialFieldClear`
3275
+ * (`@objectstack/spec/data`), populated by objectql's `update()` assembly
3276
+ * and carried across the sandbox boundary by contract — which is what an
3277
+ * APP reads; the `__` key here remains the engine/middleware authorization
3278
+ * channel, and this file's stripping rule is unchanged by the projection.
2689
3279
  *
2690
3280
  * plugin-security is the PRODUCER of that vocabulary and would be the most
2691
3281
  * honest owner of the rule for consuming it, but none of the three consumers
@@ -2774,6 +3364,244 @@ declare const OPERATION_PRIVATE_KEY_PREFIX = "__";
2774
3364
  */
2775
3365
  declare function withoutOperationPrivateKeys(exec: Record<string, unknown>): ExecutionContext;
2776
3366
 
3367
+ /**
3368
+ * ── The `authz.invalidated` cluster channel (#11968, #11633 §3) ─────────────
3369
+ *
3370
+ * The cross-node half of the authorization invalidation substrate: one channel
3371
+ * name, one payload shape, and the contract statement that governs how both may
3372
+ * be read. It carries no cache and no consumer — leg B (#11967) is the first.
3373
+ *
3374
+ * ## ⭐ THE TTL IS THE CORRECTNESS CONTRACT. THIS CHANNEL IS NOT.
3375
+ *
3376
+ * A message on this channel is a **hint**, and **a missed message is EXPECTED**.
3377
+ * That is not a caveat about an unreliable network; it is the shipped
3378
+ * guarantee, measured rather than assumed:
3379
+ *
3380
+ * - `content/docs/kernel/cluster.mdx` §4.2, on `at-least-once`:
3381
+ * *"**No shipped driver provides this yet.** The `redis` driver publishes
3382
+ * over plain Redis pub/sub, which is *at-most-once* — fire-and-forget, no
3383
+ * persistence, no replay for a node that was down at publish time."*
3384
+ * - `@objectstack/service-cluster-redis`'s own `publish` docblock:
3385
+ * *"there is no delivery guarantee to subscribers and no replay for a node
3386
+ * that was down or slow at publish time. This is acceptable **only** for
3387
+ * events that are pure cache-invalidation hints, never the source of
3388
+ * truth."*
3389
+ * - The `memory` driver does not cross a process boundary at all
3390
+ * (`service-cluster/src/memory/pubsub.ts`, and the split-brain guard's
3391
+ * `IN_PROCESS_DRIVERS`).
3392
+ *
3393
+ * So a dropped message on an at-most-once transport is a staleness window with
3394
+ * **no upper bound**, and no amount of care at the publish site changes that.
3395
+ * What bounds it is the **TTL** every cached authorization answer must carry:
3396
+ * a peer that never hears the message still converges when its entry expires.
3397
+ *
3398
+ * ⇒ **A consumer that would be incorrect if a message were lost is misusing
3399
+ * this channel.** The channel exists for one thing: moving the *typical*
3400
+ * convergence from "one TTL" down to "one network hop". It never moves the
3401
+ * worst case, and it is never the mechanism that makes a cached authorization
3402
+ * answer safe.
3403
+ *
3404
+ * ⚠️ For the same reason this channel is **best-effort at the publish site
3405
+ * too**: a publish failure is logged and swallowed, never propagated into the
3406
+ * write that triggered it. A grant revocation must not fail because a cache
3407
+ * hint could not be delivered — the TTL already covers exactly that case.
3408
+ *
3409
+ * `IPubSub`'s own interface docblock (`@objectstack/spec/contracts`) states the
3410
+ * same thing from the contract side — delivery is whatever the configured
3411
+ * driver declares, no shipped driver exceeds at-most-once, and handlers must be
3412
+ * idempotent **and** tolerate loss. That docblock, `cluster.mdx` §4.2 and the
3413
+ * redis driver agree; there is no disagreement here for a later reader to go
3414
+ * looking for.
3415
+ *
3416
+ * ## Why a new channel on the existing bus, and not a new transport
3417
+ *
3418
+ * `MetadataClusterBridgePlugin` already shows the whole shape — a channel on
3419
+ * `IPubSub`, bridged by a plugin that late-binds at `kernel:ready` and does
3420
+ * nothing when the services it needs are absent. Reusing it adds no dependency
3421
+ * and no new failure mode. The one thing metadata's channel does NOT have to
3422
+ * carry is what makes this one different: a missed `metadata.changed` costs a
3423
+ * stale schema until reload and loses no data, while a missed
3424
+ * `authz.invalidated` would cost a permission honoured past its revocation —
3425
+ * which is why the bound lives in the TTL and why the absence of this bridge is
3426
+ * stated out loud at boot ({@link ../security/authz-cache-posture.js}).
3427
+ */
3428
+ /**
3429
+ * The cluster channel authorization-cache invalidation hints travel on.
3430
+ *
3431
+ * Named as a fact about authorization, not about any one cache, because a
3432
+ * second consumer must reuse this channel rather than mint a parallel one —
3433
+ * two channels would be two chances to miss a bridge.
3434
+ */
3435
+ declare const AUTHZ_INVALIDATED_CHANNEL = "authz.invalidated";
3436
+ /**
3437
+ * Why an authorization epoch advanced. Coarse on purpose (#11633 §2.2, Fork 1 →
3438
+ * A): the engine seam sees `update`/`delete` expressed as a `where`, from which
3439
+ * the affected user or organization is frequently **not derivable without
3440
+ * reading the row back**. So the substrate carries "something authorization-
3441
+ * relevant changed", never "whose entry to drop", and a consumer retires its
3442
+ * whole bucket. Keyed invalidation is gated behind a measurement of a
3443
+ * write-heavy tenant and is explicitly not the starting point.
3444
+ */
3445
+ type AuthzInvalidationReason =
3446
+ /** A write (`insert` / `update` / `delete`) passed the engine middleware seam. */
3447
+ 'write'
3448
+ /** A metadata change — a permission set can be DECLARED, so no row is written. */
3449
+ | 'metadata'
3450
+ /** A hint received from a peer node on this channel. */
3451
+ | 'remote'
3452
+ /** An explicit bump by a host that knows something the seam cannot see. */
3453
+ | 'manual';
3454
+ /**
3455
+ * The payload on {@link AUTHZ_INVALIDATED_CHANNEL}.
3456
+ *
3457
+ * Deliberately tiny and deliberately NOT a description of what to invalidate:
3458
+ * see {@link AuthzInvalidationReason} for why the seam cannot supply that. A
3459
+ * receiver's only correct response is to retire its authorization cache
3460
+ * wholesale — and to remain correct if this message never arrives.
3461
+ *
3462
+ * ⛔ Not a `packages/spec` contract type. #11633 §5 reserves a declared shape
3463
+ * for the invalidation event to the spec seat and does not pre-commit it; this
3464
+ * is the runtime shape the substrate publishes today.
3465
+ */
3466
+ interface AuthzInvalidatedPayload {
3467
+ /**
3468
+ * Publishing node, for loopback suppression — a node must not act on its own
3469
+ * hint. Mirrors `ClusterMetadataChangedPayload.originNode`.
3470
+ */
3471
+ originNode?: string;
3472
+ /** The publisher's local epoch after the bump. Diagnostic only. */
3473
+ epoch: number;
3474
+ /** What advanced the epoch. Diagnostic only — see the type's doc. */
3475
+ reason: AuthzInvalidationReason;
3476
+ /** Wall-clock publish time, ms since epoch. Best-effort. */
3477
+ at: number;
3478
+ }
3479
+
3480
+ /**
3481
+ * ── The boot-time authorization-cache posture statement (#11968, #11633 §3) ──
3482
+ *
3483
+ * ⭐ **Non-optional**, by the 2026-08-25 ruling on #11633 (Fork 2 → B): whenever
3484
+ * a grants cache is enabled and there is **no** cross-node invalidation bus, the
3485
+ * deployment is told so, **out loud**, at boot.
3486
+ *
3487
+ * ## Why a statement and not a refusal
3488
+ *
3489
+ * A per-process cache bounded only by its TTL is a legitimate configuration —
3490
+ * #11633 §3 rules the TTL, not the bus, as the correctness contract, so a
3491
+ * single-node deployment (or one that simply accepts the window) is correct
3492
+ * with no bus at all. What is NOT acceptable is arriving there **without
3493
+ * noticing**: that is the shape of #4785, where a control was silently disabled
3494
+ * by configuration and nothing said so. The metadata bridge logs its own
3495
+ * absence at `debug` and that is right for metadata — a missed
3496
+ * `metadata.changed` costs a stale schema until reload and loses no data. Here
3497
+ * the same silence would cost a permission honoured past its revocation.
3498
+ *
3499
+ * ⇒ Enabled cache + no bus is a `warn`, every boot, naming the window it just
3500
+ * accepted. Not a refusal — a statement.
3501
+ *
3502
+ * ## The three postures, and the reason `disabled` is silent
3503
+ *
3504
+ * - `disabled` — no cache is enabled. **Silent.** There is no window to
3505
+ * state, and a line every boot on the shipped default
3506
+ * (TTL `0`, Fork 4) would train operators to ignore it —
3507
+ * which is how the loud line stops being loud.
3508
+ * - `ttl-only` — cache enabled, no cross-node bus. **LOUD (`warn`).**
3509
+ * - `bus-narrowed` — cache enabled, bus bridged. `info`, so the bridge's
3510
+ * presence is on the record next to its absence.
3511
+ *
3512
+ * Both arms are pinned in `authz-cache-posture.test.ts`: a statement that
3513
+ * appears always is no more useful than one that never appears.
3514
+ */
3515
+ /** Deployment variable that turns the grants cache on. `0` (default) = off. */
3516
+ declare const AUTHZ_GRANTS_CACHE_TTL_ENV = "OS_AUTHZ_GRANTS_CACHE_TTL_MS";
3517
+ /**
3518
+ * What the local node has, in cross-node terms, for delivering
3519
+ * `authz.invalidated`.
3520
+ *
3521
+ * ⚠️ `in-process` is a distinct state on purpose, and it is the one that would
3522
+ * otherwise go unnoticed: `Runtime` auto-registers a **memory** cluster service
3523
+ * by default, so "is a `cluster` service registered?" answers *yes* on the
3524
+ * shipped default while the bus fans out to exactly nobody
3525
+ * (`service-cluster/src/memory/pubsub.ts`: *"No cross-process delivery"*; the
3526
+ * split-brain guard calls the same set `IN_PROCESS_DRIVERS`). A posture check
3527
+ * that asked only whether a service exists would therefore stay silent in
3528
+ * precisely the multi-replica deployment it exists to warn.
3529
+ */
3530
+ type AuthzInvalidationBusState =
3531
+ /** A cross-node transport is attached and carrying the channel. */
3532
+ 'bridged'
3533
+ /** A cluster service exists, but its driver does not cross a process boundary. */
3534
+ | 'in-process'
3535
+ /** No cluster service, or no engine seam to attach one to. */
3536
+ | 'absent';
3537
+ /** The posture a boot resolves to. */
3538
+ type AuthzCachePosture = 'disabled' | 'ttl-only' | 'bus-narrowed';
3539
+ interface AuthzCachePostureInput {
3540
+ /** Configured grants-cache TTL in ms. `<= 0` means the cache is off. */
3541
+ ttlMs: number;
3542
+ /** What the node has for cross-node invalidation. */
3543
+ bus: AuthzInvalidationBusState;
3544
+ /** Cluster driver name, when one is registered. Surfaced in the message. */
3545
+ driver?: string;
3546
+ }
3547
+ interface AuthzCachePostureStatement {
3548
+ posture: AuthzCachePosture;
3549
+ /** True when this must be said at `warn`. See the module doc. */
3550
+ loud: boolean;
3551
+ /** The statement itself. Empty only for the silent `disabled` posture. */
3552
+ message: string;
3553
+ }
3554
+ /**
3555
+ * Resolve the posture. Pure — it reads its inputs and nothing else, so both
3556
+ * arms of the acceptance criterion ("appears exactly when a cache flag is on
3557
+ * without a bus, and not otherwise") are testable without a boot.
3558
+ */
3559
+ declare function resolveAuthzCachePosture(input: AuthzCachePostureInput): AuthzCachePostureStatement;
3560
+ /** The reading of {@link AUTHZ_GRANTS_CACHE_TTL_ENV}, malformed input included. */
3561
+ interface AuthzGrantsCacheTtlReading {
3562
+ /** The effective TTL. `0` whenever the cache is off — including malformed. */
3563
+ ttlMs: number;
3564
+ /** The raw value read, when one was set. */
3565
+ raw?: string;
3566
+ /** True when a value was set but could not be read as a non-negative number. */
3567
+ malformed: boolean;
3568
+ }
3569
+ /**
3570
+ * Read the grants-cache TTL from deployment config.
3571
+ *
3572
+ * Deployment config, never a settings row (#11633 §5): the knob that bounds a
3573
+ * cache must not itself be served through a cached path, and operator-level
3574
+ * configuration comes from the environment.
3575
+ *
3576
+ * Default `0` — the grants cache is **off** unless a deployment turns it on and
3577
+ * accepts the staleness window explicitly (#11633 Fork 4, ruled 2026-08-25).
3578
+ *
3579
+ * ⚠️ A malformed value resolves to `0` but is reported as malformed rather than
3580
+ * folded into "off": `OS_AUTHZ_GRANTS_CACHE_TTL_MS=5OOO` (letter O) silently
3581
+ * meaning "disabled" is the same silent-disable class the posture statement
3582
+ * exists to prevent.
3583
+ */
3584
+ declare function readAuthzGrantsCacheTtlMs(env?: Record<string, string | undefined>): AuthzGrantsCacheTtlReading;
3585
+ /** Minimal sink shape — `warn` is the member every logger in this repo has. */
3586
+ interface AuthzPostureSink {
3587
+ warn(message: string, meta?: Record<string, unknown>): void;
3588
+ info?(message: string, meta?: Record<string, unknown>): void;
3589
+ debug?(message: string, meta?: Record<string, unknown>): void;
3590
+ }
3591
+ /**
3592
+ * State the posture at boot. `warn` for the loud arm, `info` for the bridged
3593
+ * one, and nothing at all when no cache is enabled (see the module doc for why
3594
+ * silence is the right default rather than a courtesy line).
3595
+ *
3596
+ * A malformed TTL value is warned about on its own, because "we read your
3597
+ * setting as off" is exactly what a deployment must not have to infer.
3598
+ */
3599
+ declare function reportAuthzCachePosture(input: AuthzCachePostureInput & {
3600
+ malformedTtl?: {
3601
+ raw?: string;
3602
+ };
3603
+ }, sink: AuthzPostureSink): AuthzCachePostureStatement;
3604
+
2777
3605
  /**
2778
3606
  * Environment utilities for universal (Node/Browser) compatibility.
2779
3607
  */
@@ -3395,6 +4223,204 @@ declare function temporalComparandKind(fieldType: unknown): TemporalComparandKin
3395
4223
  */
3396
4224
  declare function isUninterpretableTemporalComparand(kind: TemporalComparandKind, value: unknown): boolean;
3397
4225
 
4226
+ /**
4227
+ * [ADR-0126 §4] THE activation-ledger row contract — one implementation,
4228
+ * parameterized by `metadata_type`.
4229
+ *
4230
+ * ## Why this file exists (#12350)
4231
+ *
4232
+ * ADR-0126 §4 declares ONE activation ledger for the whole disable+clone
4233
+ * family. It briefly had two implementations of that one row contract:
4234
+ *
4235
+ * | Implementation | Package | Landed |
4236
+ * | :-------------------------------- | :--------------------------------- | :----- |
4237
+ * | `ObjectStoreFlowActivationStore` | `@objectstack/service-automation` | #12296 |
4238
+ * | `ObjectStoreActionActivationStore`| `@objectstack/objectql` | #12348 |
4239
+ *
4240
+ * They agreed on every load-bearing detail because the second was written from
4241
+ * the first — and nothing structurally held them together. ADR-0126 §8
4242
+ * pre-charts `tool`, `skill` and `position` as later consumers, and a third
4243
+ * and fourth copy is where the row semantics start drifting: the org-row skip
4244
+ * and the `0`-is-false read are exactly the kind of detail a copy loses
4245
+ * quietly, in a direction (an artifact silently re-arming) nothing else
4246
+ * measures.
4247
+ *
4248
+ * ## Why the code lives HERE and the object does not
4249
+ *
4250
+ * Neither consumer could import the other: `@objectstack/service-automation`
4251
+ * does not depend on `@objectstack/objectql` (devDependency only), and the
4252
+ * engine must not depend on a service — the dependency arrow points the other
4253
+ * way. `@objectstack/core` is the package BOTH already depend on, so this is
4254
+ * the one home that needs no new edge. ⛔ NOT `@objectstack/platform-objects`,
4255
+ * which declares the OBJECT: `objectql` does not depend on it and adding that
4256
+ * edge would invert the tiering, since platform-objects is a catalog the
4257
+ * engine serves. That is a MODULE-IMPORT question, and it is independent of
4258
+ * where the object's REGISTRATION lives (a composition question, ruled
4259
+ * separately on #12359 — `PlatformObjectsPlugin`).
4260
+ *
4261
+ * The table is reached by NAME, never by importing the declaration, exactly as
4262
+ * the engine already reaches `sys_metadata` / `sys_secret`.
4263
+ *
4264
+ * ## The row shape — ⛔ this module writes COLUMNS, never schema
4265
+ *
4266
+ * `metadata_type` · `name` · `package_id` · `active`, exactly the four
4267
+ * ADR-0126 §4 declares. Three properties are load-bearing and each is pinned on
4268
+ * both consumers' sides (`flow-activation-ledger.test.ts`,
4269
+ * `action-activation.test.ts` — unchanged by the consolidation, which is what
4270
+ * makes them the proof it lost nothing):
4271
+ *
4272
+ * - **The ledger is DEPLOYMENT-level, and carries no tenant column at all.**
4273
+ * A row says "this environment switched this managed item off" — a fact no
4274
+ * organization owns. The table briefly declared a nullable tenant column
4275
+ * marked RESERVED and never written, and this module correspondingly
4276
+ * filtered reads to the NULL ones and skipped any row carrying an
4277
+ * organization. Both are gone: a reserved nullable tenant
4278
+ * column is the shape the total-organization-ownership record proposed in
4279
+ * PR #14976 rules out, so the column was dropped before it ever shipped
4280
+ * (17.2.0 predates the table). There is no filter here any more because
4281
+ * there is no column to filter on — `list()` is simply every activation
4282
+ * row of this type. Should a per-organization dimension ever be wanted, it
4283
+ * returns as a separate org-owned object, never as a column here.
4284
+ * - **Absence of a row means ACTIVE.** Nothing here ever writes a row to say
4285
+ * "active by default", and `list()` returning nothing is the normal
4286
+ * stock-boot state, not an error. Re-enabling UPDATES the row to
4287
+ * `active: true` rather than deleting it, so the ledger records the
4288
+ * administrator's CHOICE instead of erasing it (§6 wall 3) — which is why
4289
+ * {@link MetadataActivationStoreEngine} deliberately has no `delete`.
4290
+ * - **A driver `0` reads as false.** SQLite/libsql round-trip booleans as
4291
+ * 0/1; a `!== false` test alone would read a disabled artifact as armed.
4292
+ *
4293
+ * ## The discriminator is never optional
4294
+ *
4295
+ * The ledger is generic and shared — flow rows and action rows live in the
4296
+ * same table today, and §8 charts more. Every read and write below is scoped
4297
+ * by `metadata_type`, so no consumer can touch a neighbour's state through a
4298
+ * table all of them are told to treat as generic. It is a constructor
4299
+ * argument, not a per-call one, so a caller cannot forget it at a single site.
4300
+ */
4301
+ /**
4302
+ * The ledger table. A NAME, not an import: the object is declared in
4303
+ * `@objectstack/platform-objects` and this package must not depend on it.
4304
+ */
4305
+ declare const METADATA_ACTIVATION_TABLE = "sys_metadata_activation";
4306
+ /**
4307
+ * [ADR-0126 §4] One packaged artifact's install-level activation row.
4308
+ *
4309
+ * The ledger's own columns are `metadata_type` / `name` / `package_id` /
4310
+ * `active`; `metadata_type` is fixed by the store, so it never reaches a
4311
+ * consumer's projection.
4312
+ */
4313
+ interface MetadataActivationRow {
4314
+ /** The packaged artifact's declarative machine name (ADR-0126 §4). */
4315
+ name: string;
4316
+ /** The package that ships the base artifact. */
4317
+ packageId: string;
4318
+ /** Is the packaged artifact armed for this installation. */
4319
+ active: boolean;
4320
+ }
4321
+ /**
4322
+ * [ADR-0126 §4] The durable off-switch for one class of packaged artifact.
4323
+ *
4324
+ * Absence of a row means the packaged default — ACTIVE — so a runtime with no
4325
+ * store attached, or a store with no rows, behaves exactly as a stock boot
4326
+ * always has.
4327
+ */
4328
+ interface MetadataActivationStore {
4329
+ /** Every activation row for this type — the ledger is deployment-wide. */
4330
+ list(): Promise<MetadataActivationRow[]>;
4331
+ /** Insert or update the row for one packaged artifact. */
4332
+ setActive(row: MetadataActivationRow): Promise<void>;
4333
+ }
4334
+ /**
4335
+ * The exact engine slice this store needs: a keyed read, an insert and an
4336
+ * update. Deliberately WITHOUT `delete` — re-enabling updates the `active`
4337
+ * bit, it never removes the row (see the module header), and demanding only
4338
+ * what is used keeps every test double honest about that.
4339
+ */
4340
+ interface MetadataActivationStoreEngine {
4341
+ find(object: string, options?: any): Promise<any[]>;
4342
+ insert(object: string, data: any, options?: any): Promise<any>;
4343
+ update(object: string, data: any, options?: any): Promise<any>;
4344
+ }
4345
+ /**
4346
+ * In-memory {@link MetadataActivationStore} — process-lifetime only, for tests
4347
+ * and for hosts with no durable plane. What it lacks versus the ObjectStore
4348
+ * implementation is DURABILITY, which is exactly the property ADR-0126 §6
4349
+ * wall 3 asks for; it is not a sanctioned production off-switch.
4350
+ *
4351
+ * No discriminator: an in-memory map is per-instance, so there is no shared
4352
+ * table for a neighbouring type's rows to be in.
4353
+ */
4354
+ declare class InMemoryMetadataActivationStore implements MetadataActivationStore {
4355
+ private readonly rows;
4356
+ list(): Promise<MetadataActivationRow[]>;
4357
+ setActive(row: MetadataActivationRow): Promise<void>;
4358
+ }
4359
+ /**
4360
+ * Durable {@link MetadataActivationStore} backed by the
4361
+ * `sys_metadata_activation` object (ADR-0126 §4), scoped to one
4362
+ * `metadata_type`.
4363
+ *
4364
+ * All access uses a system context: the object is `managedBy: 'engine-owned'`
4365
+ * and declares `apiMethods: ['get', 'list']`, i.e. the generic data API cannot
4366
+ * write it at all — these rows are written by the ADR-0126 enable/disable
4367
+ * doors and by nothing else.
4368
+ */
4369
+ declare class ObjectStoreMetadataActivationStore implements MetadataActivationStore {
4370
+ private readonly engine;
4371
+ /**
4372
+ * The ledger's `metadata_type` discriminator for this consumer —
4373
+ * `'flow'`, `'action'`, … Required, and fixed for the store's
4374
+ * lifetime: see the module header on why it is never a per-call
4375
+ * argument.
4376
+ */
4377
+ private readonly metadataType;
4378
+ constructor(engine: MetadataActivationStoreEngine,
4379
+ /**
4380
+ * The ledger's `metadata_type` discriminator for this consumer —
4381
+ * `'flow'`, `'action'`, … Required, and fixed for the store's
4382
+ * lifetime: see the module header on why it is never a per-call
4383
+ * argument.
4384
+ */
4385
+ metadataType: string);
4386
+ /**
4387
+ * Every row of this type. Read once at boot to hydrate the consumer's
4388
+ * projection.
4389
+ *
4390
+ * The only scoping is the `metadata_type` discriminator: the ledger is
4391
+ * deployment-wide and has no tenant column, so there is no second axis to
4392
+ * filter on (see the module header).
4393
+ */
4394
+ list(): Promise<MetadataActivationRow[]>;
4395
+ /**
4396
+ * Insert or update the row for one packaged artifact.
4397
+ *
4398
+ * Read-then-write rather than a blind upsert because the object's
4399
+ * uniqueness is a DECLARED index (`unique: 'global'` over
4400
+ * `(metadata_type, name)`), not a primary key this store controls: there is
4401
+ * no id to collide on, so an insert-and-catch could not tell "already
4402
+ * there" from a real store failure.
4403
+ *
4404
+ * That index is also why taking the FIRST match is taking the only one: the
4405
+ * read below is keyed on exactly the index's two columns, so it can match
4406
+ * at most one row. It used to pick the first row with a NULL organization
4407
+ * out of the result, back when the table carried a reserved tenant column;
4408
+ * with no such column the set it was choosing from can no longer hold more
4409
+ * than one member.
4410
+ */
4411
+ setActive(row: MetadataActivationRow): Promise<void>;
4412
+ /**
4413
+ * Read the backing table once so a misconfiguration surfaces at BOOT
4414
+ * rather than as a failed toggle later. Throws the driver error verbatim —
4415
+ * `no such table: sys_metadata_activation` means the object was never
4416
+ * registered (or its schema never synced) in this composition.
4417
+ *
4418
+ * ⚠️ Unscoped by design: the question is "does the TABLE read at all",
4419
+ * which is a property of the composition, not of one `metadata_type`.
4420
+ */
4421
+ probe(): Promise<void>;
4422
+ }
4423
+
3398
4424
  /**
3399
4425
  * [#4435] The 404 a single-record operation answers when the id names no row.
3400
4426
  *
@@ -3510,11 +4536,15 @@ declare function createMemoryQueue(): {
3510
4536
  };
3511
4537
 
3512
4538
  /**
3513
- * In-memory job scheduler fallback.
4539
+ * In-memory job registry — schedule/cancel/trigger bookkeeping with NO timer.
3514
4540
  *
3515
- * Implements the IJobService contract with basic schedule/cancel/trigger
3516
- * operations. Used by ObjectKernel as an automatic fallback when no real
3517
- * job plugin (e.g. Agenda / BullMQ) is registered.
4541
+ * [#10746] NOT pre-injected by ObjectKernel any more (it used to be, via
4542
+ * `CORE_FALLBACK_FACTORIES`): a fallback must not fake capability (maintainer
4543
+ * ruling 2026-08-22). Advertising a `schedule()` that records and never fires
4544
+ * made every "prefer the platform job service, else own a timer" consumer
4545
+ * take the job-service branch and then silently never run. The export remains
4546
+ * for embedders who deliberately want a manual-trigger job registry — e.g. in
4547
+ * tests that drive handlers via `trigger()` — and have read this docblock.
3518
4548
  *
3519
4549
  * [#4058] `degraded` (ADR-0076 D12), with the missing half named in the
3520
4550
  * message rather than left for a deployer to discover: `trigger()` really runs
@@ -3666,8 +4696,26 @@ declare function wireAuthoredTranslationSync(ctx: MinimalCtx): void;
3666
4696
 
3667
4697
  /**
3668
4698
  * Map of core-criticality service names to their in-memory fallback factories.
3669
- * Used by ObjectKernel.validateSystemRequirements() to auto-inject fallbacks
3670
- * when no real plugin provides the service.
4699
+ * This IS the kernel's pre-injection list: `ObjectKernel.preInjectCoreFallbacks()`
4700
+ * registers an entry for every unprovided `core` service before Phase 2, and
4701
+ * `validateSystemRequirements()` consults the same map as its final check.
4702
+ *
4703
+ * [#10746] `job` is deliberately ABSENT — a fallback must not fake capability
4704
+ * (maintainer ruling 2026-08-22). `createMemoryJob()`'s `schedule()` records a
4705
+ * job and never fires it, so pre-injecting it made every "prefer the platform
4706
+ * job service, else own a timer" consumer take the job-service branch and then
4707
+ * silently never run: `plugin-reports` logged `dispatcher registered with job
4708
+ * service` and dispatched nothing, ever (measured: 0 reads of
4709
+ * `sys_report_schedule` in 5600 ms with the success line present). With no
4710
+ * entry here, `getService('job')` throws when no job plugin is installed,
4711
+ * every consumer's documented no-job-service path becomes reachable (they all
4712
+ * already run on `LiteKernel`, which injects no fallbacks), and the kernel
4713
+ * says the absence out loud at boot: `validateSystemRequirements()` warns
4714
+ * "Core service missing, functionality may be degraded: job". Do NOT re-add
4715
+ * the entry to quiet that warning — install `@objectstack/service-job`, or
4716
+ * register a real scheduler, instead. `createMemoryJob` stays exported below
4717
+ * for embedders who deliberately want a manual-trigger job registry and have
4718
+ * read its docblock.
3671
4719
  */
3672
4720
  declare const CORE_FALLBACK_FACTORIES: Record<string, () => Record<string, any>>;
3673
4721
 
@@ -3676,7 +4724,7 @@ declare const CORE_FALLBACK_FACTORIES: Record<string, () => Record<string, any>>
3676
4724
  * (#7378 row 2). Folds a plural manifest spelling to the singular metadata
3677
4725
  * type name (`'objects'` → `'object'`, `'views'` → `'view'`, …) through the
3678
4726
  * platform's one plural↔singular map (`PLURAL_TO_SINGULAR`,
3679
- * `@objectstack/spec/shared`); a name with no plural mapping — which includes
4727
+ * `@objectstack/spec/meta-spelling`); a name with no plural mapping — which includes
3680
4728
  * every canonical singular type — passes through unchanged.
3681
4729
  */
3682
4730
  declare function canonicalMetadataServiceType(type: string): string;
@@ -3705,8 +4753,37 @@ declare function assertMetadataRegisterContract(type: string, name: string, data
3705
4753
  /**
3706
4754
  * Plugin Health Monitor
3707
4755
  *
3708
- * Monitors plugin health status and performs automatic recovery actions.
3709
- * Implements the advanced lifecycle health monitoring protocol.
4756
+ * Monitors plugin health status. It REPORTS; it does not act on what it finds.
4757
+ *
4758
+ * ## The monitor no longer "restarts" anything (#12032)
4759
+ *
4760
+ * It used to claim it did. `attemptRestart` called `plugin.destroy()` and
4761
+ * stopped there — the comment above the call read "Call destroy and init to
4762
+ * restart", and `init` appeared in this file ONLY inside that comment. What a
4763
+ * plugin got was: destroy, a log line reading 'Plugin restarted', status
4764
+ * `recovering`, and periodic checks continuing against the destroyed instance.
4765
+ * The default check when no `checkMethod` resolves is
4766
+ * `{ name: 'plugin-loaded', status: 'passed' }`, which a destroyed plugin
4767
+ * passes indefinitely, so the TERMINAL report on a destroyed, never
4768
+ * re-initialised plugin was `healthy` — and #11955 made that MORE convincing
4769
+ * rather than less, because reaching `healthy` now costs `successThreshold`
4770
+ * consecutive passing rounds.
4771
+ *
4772
+ * The restart could not be repaired in place. `Plugin.init(ctx)` needs a
4773
+ * `PluginContext`, and the only two `plugin.init(...)` call sites in the tree
4774
+ * are the kernel's own boot loops, over the full plugin list, with a context
4775
+ * that is `private` on `ObjectKernel` and `protected` on `KernelBase`. No host
4776
+ * can obtain one, so there was nothing for a re-init hook to call. ADR-0049
4777
+ * enforce-or-remove, with no roadmap to point EXPERIMENTAL at, therefore
4778
+ * removed the declaration: `autoRestart`, `maxRestartAttempts` and
4779
+ * `restartBackoff` are tombstoned in `@objectstack/spec` 18, and this class
4780
+ * refuses a config that still carries one instead of accepting it and doing
4781
+ * something else.
4782
+ *
4783
+ * What a failing plugin gets now is the truth: `degraded`, `unhealthy` or
4784
+ * `failed`, and no destroy. Acting on that is the HOST's job — this is a
4785
+ * host-driven library (#11825 route 2), and the host is the only party that
4786
+ * owns the plugin's lifetime.
3710
4787
  */
3711
4788
  declare class PluginHealthMonitor {
3712
4789
  private logger;
@@ -3716,7 +4793,6 @@ declare class PluginHealthMonitor {
3716
4793
  private checkIntervals;
3717
4794
  private failureCounters;
3718
4795
  private successCounters;
3719
- private restartAttempts;
3720
4796
  constructor(logger: ObjectLogger);
3721
4797
  /**
3722
4798
  * Register a plugin for health monitoring
@@ -3735,13 +4811,28 @@ declare class PluginHealthMonitor {
3735
4811
  */
3736
4812
  private performHealthCheck;
3737
4813
  /**
3738
- * Attempt to restart a plugin
3739
- */
3740
- private attemptRestart;
3741
- /**
3742
- * Calculate backoff delay for restarts
4814
+ * Handle one failed round — the single path BOTH failure routes take.
4815
+ *
4816
+ * `performHealthCheck` can fail two disjoint ways: the check *returns* a
4817
+ * failure (`false` or `{ status: 'unhealthy' }`), or it *throws* — which by
4818
+ * `raceCheckTimeout` includes every `timeout` overrun, the severest case of
4819
+ * the two. The routes used to be handled in separate blocks, and only the
4820
+ * returned one cleared `successCounters`, so the counters a declared
4821
+ * `failureThreshold` / `successThreshold` are counted with depended on which
4822
+ * way the round happened to fail (#11852).
4823
+ *
4824
+ * What stays route-specific is the *status label*, deliberately. A throw is
4825
+ * the separate `failed` status applied immediately with no threshold — that
4826
+ * is the documented contract (`content/docs/protocol/kernel/lifecycle.mdx`,
4827
+ * "Custom Health Checks") and is pinned by the timeout test. Only the
4828
+ * counters are shared, because that is what `failureThreshold` declares, and
4829
+ * it does not name a route.
4830
+ *
4831
+ * This round ENDS here. Nothing is done TO the plugin — see the #12032 note
4832
+ * on the class: a monitor that cannot re-initialise a plugin has no business
4833
+ * destroying one.
3743
4834
  */
3744
- private calculateBackoff;
4835
+ private recordFailedRound;
3745
4836
  /**
3746
4837
  * Get current health status of a plugin
3747
4838
  */
@@ -3823,7 +4914,6 @@ declare class HotReloadManager {
3823
4914
  private logger;
3824
4915
  private stateManager;
3825
4916
  private reloadConfigs;
3826
- private watchHandles;
3827
4917
  private reloadTimers;
3828
4918
  constructor(logger: ObjectLogger);
3829
4919
  /**
@@ -3831,11 +4921,35 @@ declare class HotReloadManager {
3831
4921
  */
3832
4922
  registerPlugin(pluginName: string, config: HotReloadConfigParsed): void;
3833
4923
  /**
3834
- * Start watching for changes (requires file system integration)
4924
+ * Refuse the file-watching call this class never implemented (#12428).
4925
+ *
4926
+ * The body used to be a guard plus `logger.info('File watching started')`
4927
+ * over an in-source note saying real watching "would require chokidar or
4928
+ * similar". Nothing was ever watched, so an operator who set
4929
+ * `enabled: true` and read that line at INFO had been told the opposite of
4930
+ * the truth — positive confirmation of a capability that did not exist.
4931
+ * ADR-0049 leaves three states and this surface qualified for none of the
4932
+ * other two: no runtime composes this class, so ENFORCE would build for a
4933
+ * caller that does not exist, and no roadmap entry anywhere claims the
4934
+ * feature, so EXPERIMENTAL would be a promise nobody made.
4935
+ *
4936
+ * Kept as a throwing door rather than deleted: removing the method leaves a
4937
+ * JavaScript host a bare `TypeError: not a function` with no prescription,
4938
+ * and this is the one place a caller of the old placeholder is guaranteed
4939
+ * to arrive. The refusal carries an ADR-0112 envelope so it can be asserted
4940
+ * rather than merely caught.
3835
4941
  */
3836
- startWatching(pluginName: string): void;
4942
+ startWatching(pluginName: string): never;
3837
4943
  /**
3838
- * Stop watching for changes
4944
+ * Cancel a pending debounced reload for a plugin.
4945
+ *
4946
+ * The name is historical (#12428). This never stopped a watcher, because
4947
+ * nothing in this class ever started one: its `watchHandles` cleanup branch
4948
+ * read a Map that had no writer anywhere in the tree, so the branch was
4949
+ * structurally unreachable rather than merely untaken, and it left with
4950
+ * `startWatching`'s placeholder. What survives is the half that always did
4951
+ * something — the debounce timer armed by `scheduleReload` is cleared, so a
4952
+ * reload that was scheduled but has not fired yet is cancelled.
3839
4953
  */
3840
4954
  stopWatching(pluginName: string): void;
3841
4955
  /**
@@ -4019,4 +5133,4 @@ declare class NamespaceResolver {
4019
5133
  private suggestAlternative;
4020
5134
  }
4021
5135
 
4022
- export { ADMIN_STANDING_SURFACE, ANONYMOUS_DENY_BODY, ANONYMOUS_DENY_CODE, ANONYMOUS_DENY_MESSAGE, ANONYMOUS_DENY_STATUS, API_KEY_PREFIX, AUDIENCE_BINDING_SUGGESTION_STATUSES, AUDIENCE_BINDING_SUGGESTION_STATUS_VALUES, type ActivatableRow, type AdminStandingTable, type AnonymousDenyInput, type ApiKeyAdmission, type ApiKeyPrincipal, type ApiKeyRefusalReason, type AudienceBindingSuggestionStatus, type AuthGate, type BucketGranularity, type BulkWriteOptions, type BulkWriteRowResult, CORE_FALLBACK_FACTORIES, type CalendarParts, DependencyResolver, ENTRY_EXECUTION_CONTEXT_FIELDS, type EngineWithTransaction, type EntryExecutionContextField, type EntryLocalization, type ExecutionContextAssemblyInput, type ExecutionContextEntryFields, type ExecutionContextLike, type FilterTokenResolutionContext, type GeneratedApiKey, type GrantValidityWindow, HotReloadManager, type InterruptedRun, type KernelState, type KeyInput, type LadderPrincipal, type LadderRow, LiteKernel, type MigrationChunk, type MigrationChunkContext, MigrationJournalRefusal, type MigrationPlan, type MigrationPlanProvider, MigrationPlanRegistry, type MigrationPlanStep, type MigrationRunResult, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, type OAuthTokenProvenance, OPERATION_PRIVATE_KEY_PREFIX, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, type OrderablePlugin, POSTURE_INJECTION_RULE, POSTURE_LADDER, POSTURE_RANK, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, type PluginArtifactVerifyResult, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PostureEvidence, type PublisherVerifyResult, index as QA, type ResolveAuthzInput, type ResolveLocalizationInput, type ResolveUserAuthzGrantsOptions, type ResolvedAuthzContext, type ResourceUsage, type RetryOptions, type RunMigrationJournalOptions, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type TemporalComparandKind, type TenancyPostureSource, UnknownFilterTokenError, UnresolvedFilterTokenError, type UserAuthzGrants, type VersionCompatibility, type WallClockParts, adminStandingColumns, adminStandingTables, assembleExecutionContext, assembleExecutionContextOrGuest, assertInitServiceRequirements, assertMetadataRegisterContract, bucketKeyToCalendarRange, buildPermissionsFromGrants, bulkWrite, calendarPartsInTz, calendarPartsInTzOrUtc, canonicalMetadataServiceType, collectInternalWriteResponseFields, counterSignPayload, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, deepMerge, defaultIsTransientError, derivePosture, describeInitOrderFault, effectiveTenancyPosture, engineCanRollBack, evaluateAuthGate, extractApiKey, filterTokenContextFrom, findInterruptedRuns, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hashApiKey, hashMigrationPlan, isAudienceBindingSuggestionStatus, isAuthGateAllowlisted, isExpired, isGrantActive, isGrantExpired, isNode, isRowActive, isUninterpretableTemporalComparand, normalizeAuthGate, omitInternalFieldsFromWriteResponse, parseScopes, parseSignature, planChunks, postureVisibleRows, readAuthoredTranslationLayer, readRunJournal, recordNotFoundError, resolveApiKeyAdmission, resolveApiKeyPrincipal, resolveAuthzContext, resolveFilterToken, resolveFilterTokens, resolveLocale, resolveLocalizationContext, resolvePluginOrder, resolveUserAuthzGrants, resumeMigrationJournal, runMigrationJournal, safeExit, shouldDenyAnonymous, signPayload, temporalComparandKind, unknownAudienceBindingSuggestionStatusMessage, validateInitServiceContract, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature, wireAuthoredTranslationSync, withTransientRetry, withoutOperationPrivateKeys, zonedDateStartToUtcMs, zonedWallClockToUtcMs };
5136
+ export { ADMIN_STANDING_NON_TABLE_INPUTS, ADMIN_STANDING_SURFACE, ANONYMOUS_DENY_BODY, ANONYMOUS_DENY_CODE, ANONYMOUS_DENY_MESSAGE, ANONYMOUS_DENY_STATUS, API_KEY_PREFIX, AUDIENCE_BINDING_SUGGESTION_STATUSES, AUDIENCE_BINDING_SUGGESTION_STATUS_VALUES, AUTHZ_GRANTS_CACHE_TTL_ENV, AUTHZ_INVALIDATED_CHANNEL, AUTHZ_STORE_UNAVAILABLE_CODE, AUTHZ_STORE_UNAVAILABLE_MESSAGE, AUTHZ_STORE_UNAVAILABLE_STATUS, type ActivatableRow, type AdminStandingNonTableInput, type AdminStandingTable, type AnonymousDenyInput, type ApiKeyAdmission, type ApiKeyPrincipal, type ApiKeyRefusalReason, type ArtifactPackageError, type AudienceBindingSuggestionStatus, type AuthGate, type AuthzCachePosture, type AuthzCachePostureInput, type AuthzCachePostureStatement, type AuthzGrantsCacheTtlReading, type AuthzInvalidatedPayload, type AuthzInvalidationBusState, type AuthzInvalidationReason, type AuthzPostureSink, AuthzStoreUnavailableError, type BucketGranularity, type BulkWriteOptions, type BulkWriteRowResult, CORE_FALLBACK_FACTORIES, type CalendarParts, DependencyResolver, ENTRY_EXECUTION_CONTEXT_FIELDS, type EngineWithTransaction, type EntryExecutionContextField, type EntryLocalization, type ExecutionContextAssemblyInput, type ExecutionContextEntryFields, type ExecutionContextLike, type FilterTokenResolutionContext, type GeneratedApiKey, type GrantValidityWindow, HotReloadManager, InMemoryMetadataActivationStore, type IntegrityFile, type IntegrityViolation, type IntegrityViolationKind, type InterruptedRun, type KernelState, type KeyInput, type LadderPrincipal, type LadderRow, LiteKernel, METADATA_ACTIVATION_TABLE, type MetadataActivationRow, type MetadataActivationStore, type MetadataActivationStoreEngine, type MigrationChunk, type MigrationChunkContext, MigrationJournalRefusal, type MigrationPlan, type MigrationPlanProvider, MigrationPlanRegistry, type MigrationPlanStep, type MigrationRunResult, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, type OAuthTokenProvenance, OPERATION_PRIVATE_KEY_PREFIX, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, ObjectStoreMetadataActivationStore, type OrderablePlugin, PLATFORM_ADMIN_EMAIL_SEPARATOR, POSTURE_INJECTION_RULE, POSTURE_LADDER, POSTURE_RANK, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type PlatformAdminConfigSink, type PlatformAdminEmailConfig, type Plugin, type PluginArtifactVerifyResult, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PluginType, type PostureEvidence, type PublisherVerifyResult, index as QA, type ResolveAuthzInput, type ResolveLocalizationInput, type ResolveUserAuthzGrantsOptions, type ResolvedAuthzContext, type ResourceUsage, type RetryOptions, type RunMigrationJournalOptions, SERVICE_NOT_REGISTERED_CODE, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type TemporalComparandKind, type TenancyPostureSource, UnknownFilterTokenError, UnresolvedFilterTokenError, type UserAuthzGrants, type VerifyIntegrityResult, type VersionCompatibility, type WallClockParts, adminStandingColumns, adminStandingTables, artifactPackageId, assembleExecutionContext, assembleExecutionContextOrGuest, assertInitServiceRequirements, assertMetadataRegisterContract, bucketKeyToCalendarRange, buildPermissionsFromGrants, bulkWrite, calendarPartsInTz, calendarPartsInTzOrUtc, canonicalMetadataServiceType, collectInternalWriteResponseFields, counterSignPayload, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginPermissionEnforcer, deepMerge, defaultIsTransientError, derivePosture, describeInitOrderFault, effectiveTenancyPosture, engineCanRollBack, evaluateAuthGate, extractApiKey, filterTokenContextFrom, findInterruptedRuns, formatIntegrityViolation, generateApiKey, generateEd25519KeyPair, getEnv, getMemoryUsage, hasPlatformAdminStanding, hashApiKey, hashMigrationPlan, isAudienceBindingSuggestionStatus, isAuthGateAllowlisted, isAuthzStoreUnavailableError, isConfiguredPlatformAdminEmail, isExpired, isGrantActive, isGrantExpired, isNode, isRowActive, isServiceNotRegisteredError, isUninterpretableTemporalComparand, matchesConfiguredPlatformAdmin, normalizeAuthGate, normalizePlatformAdminEmail, omitInternalFieldsFromWriteResponse, parsePlatformAdminEmails, parseScopes, parseSignature, planChunks, postureVisibleRows, readAuthoredTranslationLayer, readAuthzGrantsCacheTtlMs, readRunJournal, recordNotFoundError, reportAuthzCachePosture, reportLegacyPlatformAdminGrant, resetLegacyPlatformAdminGrantReport, resetPlatformAdminEmailMemo, resolveApiKeyAdmission, resolveApiKeyPrincipal, resolveArtifactPackageOrder, resolveAuthzCachePosture, resolveAuthzContext, resolveFilterToken, resolveFilterTokens, resolveLocale, resolveLocalizationContext, resolvePlatformAdminEmails, resolvePluginOrder, resolveUserAuthzGrants, resumeMigrationJournal, rethrowAuthzStoreUnavailable, runMigrationJournal, safeExit, setPlatformAdminConfigSink, shouldDenyAnonymous, signPayload, temporalComparandKind, unknownAudienceBindingSuggestionStatusMessage, validateInitServiceContract, verifyIntegrity, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature, wireAuthoredTranslationSync, withTransientRetry, withoutOperationPrivateKeys, zonedDateStartToUtcMs, zonedWallClockToUtcMs };