@objectstack/core 17.3.0 → 17.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +649 -0
- package/dist/index.cjs +251 -246
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +264 -132
- package/dist/index.d.ts +264 -132
- package/dist/index.js +247 -245
- package/dist/index.js.map +1 -1
- package/dist/logger.cjs.map +1 -1
- package/dist/logger.js.map +1 -1
- package/package.json +7 -6
package/dist/index.cjs
CHANGED
|
@@ -32,6 +32,7 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
ADMIN_STANDING_NON_TABLE_INPUTS: () => ADMIN_STANDING_NON_TABLE_INPUTS,
|
|
34
34
|
ADMIN_STANDING_SURFACE: () => ADMIN_STANDING_SURFACE,
|
|
35
|
+
ADVISORY_SAMPLE_ROWS: () => ADVISORY_SAMPLE_ROWS,
|
|
35
36
|
ANONYMOUS_DENY_BODY: () => ANONYMOUS_DENY_BODY,
|
|
36
37
|
ANONYMOUS_DENY_CODE: () => ANONYMOUS_DENY_CODE,
|
|
37
38
|
ANONYMOUS_DENY_MESSAGE: () => ANONYMOUS_DENY_MESSAGE,
|
|
@@ -69,7 +70,6 @@ __export(index_exports, {
|
|
|
69
70
|
PluginPermissionEnforcer: () => PluginPermissionEnforcer,
|
|
70
71
|
PluginPermissionManager: () => PluginPermissionManager,
|
|
71
72
|
PluginSandboxRuntime: () => PluginSandboxRuntime,
|
|
72
|
-
PluginSecurityScanner: () => PluginSecurityScanner,
|
|
73
73
|
PluginSignatureVerifier: () => PluginSignatureVerifier,
|
|
74
74
|
QA: () => qa_exports,
|
|
75
75
|
SERVICE_NOT_REGISTERED_CODE: () => SERVICE_NOT_REGISTERED_CODE,
|
|
@@ -120,6 +120,7 @@ __export(index_exports, {
|
|
|
120
120
|
hasPlatformAdminStanding: () => hasPlatformAdminStanding,
|
|
121
121
|
hashApiKey: () => hashApiKey,
|
|
122
122
|
hashMigrationPlan: () => hashMigrationPlan,
|
|
123
|
+
isAggregatingAdvisories: () => isAggregatingAdvisories,
|
|
123
124
|
isAudienceBindingSuggestionStatus: () => isAudienceBindingSuggestionStatus,
|
|
124
125
|
isAuthGateAllowlisted: () => isAuthGateAllowlisted,
|
|
125
126
|
isAuthzStoreUnavailableError: () => isAuthzStoreUnavailableError,
|
|
@@ -144,6 +145,7 @@ __export(index_exports, {
|
|
|
144
145
|
readAuthoredTranslationLayer: () => readAuthoredTranslationLayer,
|
|
145
146
|
readAuthzGrantsCacheTtlMs: () => readAuthzGrantsCacheTtlMs,
|
|
146
147
|
readRunJournal: () => readRunJournal,
|
|
148
|
+
recordAdvisoryHit: () => recordAdvisoryHit,
|
|
147
149
|
recordNotFoundError: () => recordNotFoundError,
|
|
148
150
|
reportAuthzCachePosture: () => reportAuthzCachePosture,
|
|
149
151
|
reportLegacyPlatformAdminGrant: () => reportLegacyPlatformAdminGrant,
|
|
@@ -164,6 +166,7 @@ __export(index_exports, {
|
|
|
164
166
|
resumeMigrationJournal: () => resumeMigrationJournal,
|
|
165
167
|
rethrowAuthzStoreUnavailable: () => rethrowAuthzStoreUnavailable,
|
|
166
168
|
runMigrationJournal: () => runMigrationJournal,
|
|
169
|
+
runWithAdvisoryAggregation: () => runWithAdvisoryAggregation,
|
|
167
170
|
safeExit: () => safeExit,
|
|
168
171
|
setPlatformAdminConfigSink: () => setPlatformAdminConfigSink,
|
|
169
172
|
shouldDenyAnonymous: () => shouldDenyAnonymous,
|
|
@@ -998,6 +1001,29 @@ function isServiceNotRegisteredError(err) {
|
|
|
998
1001
|
return typeof err === "object" && err !== null && err[SERVICE_NOT_REGISTERED_BRAND] === true;
|
|
999
1002
|
}
|
|
1000
1003
|
|
|
1004
|
+
// src/plugin-contract.ts
|
|
1005
|
+
var import_kernel = require("@objectstack/spec/kernel");
|
|
1006
|
+
var PLUGIN_CONTRACT_VIOLATION_CODE = "PLUGIN_CONTRACT_VIOLATION";
|
|
1007
|
+
function assertPluginContract(plugin) {
|
|
1008
|
+
const result = import_kernel.PluginSchema.safeParse(plugin);
|
|
1009
|
+
if (result.success) {
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
const issues = result.error.issues.filter((issue) => issue.path[0] !== "version");
|
|
1013
|
+
if (issues.length === 0) {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
const first = issues[0];
|
|
1017
|
+
const at = first.path.length > 0 ? first.path.join(".") : "(root)";
|
|
1018
|
+
const id = plugin.id;
|
|
1019
|
+
const named = typeof id === "string" && id.length > 0 ? `'${plugin.name}' (id: ${id})` : `'${plugin.name}'`;
|
|
1020
|
+
const error = new Error(
|
|
1021
|
+
`${PLUGIN_CONTRACT_VIOLATION_CODE}: plugin ${named} is refused by the declared plugin contract at '${at}': ${first.message}`
|
|
1022
|
+
);
|
|
1023
|
+
error.code = PLUGIN_CONTRACT_VIOLATION_CODE;
|
|
1024
|
+
throw error;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1001
1027
|
// src/plugin-loader.ts
|
|
1002
1028
|
var ServiceLifecycle = /* @__PURE__ */ ((ServiceLifecycle2) => {
|
|
1003
1029
|
ServiceLifecycle2["SINGLETON"] = "singleton";
|
|
@@ -1035,6 +1061,7 @@ var PluginLoader = class {
|
|
|
1035
1061
|
this.logger.info(`Loading plugin: ${plugin.name}`);
|
|
1036
1062
|
const metadata = this.toPluginMetadata(plugin);
|
|
1037
1063
|
this.validatePluginStructure(metadata);
|
|
1064
|
+
this.validatePluginContract(metadata);
|
|
1038
1065
|
const versionCheck = this.checkVersionCompatibility(metadata);
|
|
1039
1066
|
if (!versionCheck.compatible) {
|
|
1040
1067
|
throw new Error(`Version incompatible: ${versionCheck.message}`);
|
|
@@ -1219,6 +1246,27 @@ var PluginLoader = class {
|
|
|
1219
1246
|
throw new Error(`Invalid semantic version: ${plugin.version}`);
|
|
1220
1247
|
}
|
|
1221
1248
|
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Refuse a plugin object the DECLARED plugin contract refuses (#16049,
|
|
1251
|
+
* maintainer ruling 2026-09-06: "the protocol is the baseline; the runtime
|
|
1252
|
+
* aligns to it").
|
|
1253
|
+
*
|
|
1254
|
+
* The check itself — `PluginSchema.safeParse` for validation only, the
|
|
1255
|
+
* eight keys it reaches, the `version` exclusion and the
|
|
1256
|
+
* `PLUGIN_CONTRACT_VIOLATION` envelope — lives in `plugin-contract.ts`,
|
|
1257
|
+
* because since #16721 it is ONE statement run by BOTH kernels:
|
|
1258
|
+
* `LiteKernel.use()` calls it directly, and `ObjectKernel.use()` reaches
|
|
1259
|
+
* it here, through `loadPlugin`. That module's comment is the authority on
|
|
1260
|
+
* what is refused; this method adds nothing to it and subtracts nothing.
|
|
1261
|
+
*
|
|
1262
|
+
* What stays THIS loader's own, and is deliberately not shared: the
|
|
1263
|
+
* structural checks one call up ({@link validatePluginStructure} —
|
|
1264
|
+
* `name`, `init`, semver) and the version-compatibility check below.
|
|
1265
|
+
* The convergence is on the schema, not on the loader.
|
|
1266
|
+
*/
|
|
1267
|
+
validatePluginContract(plugin) {
|
|
1268
|
+
assertPluginContract(plugin);
|
|
1269
|
+
}
|
|
1222
1270
|
checkVersionCompatibility(plugin) {
|
|
1223
1271
|
const version = plugin.version;
|
|
1224
1272
|
if (!this.isValidSemanticVersion(version)) {
|
|
@@ -1428,6 +1476,7 @@ function createMemoryI18n() {
|
|
|
1428
1476
|
const authored = /* @__PURE__ */ new Map();
|
|
1429
1477
|
let defaultLocale = "en";
|
|
1430
1478
|
let supportedLocales;
|
|
1479
|
+
let fallbackLocale;
|
|
1431
1480
|
function resolveKey(data, key) {
|
|
1432
1481
|
const parts = key.split(".");
|
|
1433
1482
|
let current = data;
|
|
@@ -1464,7 +1513,11 @@ function createMemoryI18n() {
|
|
|
1464
1513
|
_serviceName: "i18n",
|
|
1465
1514
|
t(key, locale, params) {
|
|
1466
1515
|
const data = resolveTranslations(locale) ?? mergedLocale(defaultLocale);
|
|
1467
|
-
|
|
1516
|
+
let value = data ? resolveKey(data, key) : void 0;
|
|
1517
|
+
if (value === void 0 && fallbackLocale && fallbackLocale !== locale) {
|
|
1518
|
+
const fallbackData = resolveTranslations(fallbackLocale);
|
|
1519
|
+
value = fallbackData ? resolveKey(fallbackData, key) : void 0;
|
|
1520
|
+
}
|
|
1468
1521
|
if (value == null) return key;
|
|
1469
1522
|
if (!params) return value;
|
|
1470
1523
|
return value.replace(/\{\{(\w+)\}\}/g, (_, name) => String(params[name] ?? `{{${name}}}`));
|
|
@@ -1521,6 +1574,21 @@ function createMemoryI18n() {
|
|
|
1521
1574
|
},
|
|
1522
1575
|
setDefaultLocale(locale) {
|
|
1523
1576
|
defaultLocale = locale;
|
|
1577
|
+
},
|
|
1578
|
+
/**
|
|
1579
|
+
* @see II18nService.setFallbackLocale — [#15694]
|
|
1580
|
+
*
|
|
1581
|
+
* ⛔ There is deliberately NO `getFallbackLocale()` beside this. The two
|
|
1582
|
+
* are different questions: this one is what the provider was TOLD, the
|
|
1583
|
+
* accessor is what the serving layer ASKS it in order to build the
|
|
1584
|
+
* metadata-document translators' fallback chain (#14882). Answering the
|
|
1585
|
+
* second from `defaultLocale` — the only value that was always available
|
|
1586
|
+
* here — would settle the default-locale contract question #14882 leaves
|
|
1587
|
+
* deliberately open, from a degraded provider. Without the accessor those
|
|
1588
|
+
* reads keep the resolvers' own default, which is known and intentional.
|
|
1589
|
+
*/
|
|
1590
|
+
setFallbackLocale(locale) {
|
|
1591
|
+
fallbackLocale = locale;
|
|
1524
1592
|
}
|
|
1525
1593
|
};
|
|
1526
1594
|
}
|
|
@@ -1846,6 +1914,7 @@ async function raceWithTimeout(operation, timeoutMs, createTimeoutError) {
|
|
|
1846
1914
|
}
|
|
1847
1915
|
|
|
1848
1916
|
// src/kernel.ts
|
|
1917
|
+
var DEGRADED_CAPABILITIES_SERVICE = "kernel.degraded-capabilities";
|
|
1849
1918
|
var ObjectKernel = class {
|
|
1850
1919
|
constructor(config = {}) {
|
|
1851
1920
|
this.plugins = /* @__PURE__ */ new Map();
|
|
@@ -1853,7 +1922,12 @@ var ObjectKernel = class {
|
|
|
1853
1922
|
this.hooks = /* @__PURE__ */ new Map();
|
|
1854
1923
|
this.state = "idle";
|
|
1855
1924
|
this.startedPlugins = /* @__PURE__ */ new Set();
|
|
1856
|
-
|
|
1925
|
+
/**
|
|
1926
|
+
* Plugin name -> elapsed milliseconds that plugin's `start()` took. These
|
|
1927
|
+
* are DURATIONS, never start instants; the old spelling `pluginStartTimes`
|
|
1928
|
+
* said the opposite of what it held.
|
|
1929
|
+
*/
|
|
1930
|
+
this.pluginStartupDurations = /* @__PURE__ */ new Map();
|
|
1857
1931
|
this.shutdownHandlers = [];
|
|
1858
1932
|
this.config = {
|
|
1859
1933
|
defaultStartupTimeout: 3e4,
|
|
@@ -2040,9 +2114,40 @@ var ObjectKernel = class {
|
|
|
2040
2114
|
}
|
|
2041
2115
|
if (missingCoreServices.length > 0) {
|
|
2042
2116
|
this.logger.warn(`System started with degraded capabilities. Missing core services: ${missingCoreServices.join(", ")}`);
|
|
2117
|
+
this.publishDegradedCapabilities(missingCoreServices);
|
|
2043
2118
|
}
|
|
2044
2119
|
this.logger.info("System requirement check passed");
|
|
2045
2120
|
}
|
|
2121
|
+
/**
|
|
2122
|
+
* Publish this boot's degraded-capabilities conclusion on
|
|
2123
|
+
* {@link DEGRADED_CAPABILITIES_SERVICE} — the data half of the warning
|
|
2124
|
+
* `validateSystemRequirements()` just logged (#16630).
|
|
2125
|
+
*
|
|
2126
|
+
* ⛔ Best-effort, and silent on failure BY DESIGN: this is a diagnostic
|
|
2127
|
+
* readout, and a readout must never be able to fail a boot that the kernel
|
|
2128
|
+
* has just decided is good enough to run. The one way `registerService`
|
|
2129
|
+
* can throw here is a name collision, which the guard above already
|
|
2130
|
+
* forecloses; the `catch` is there so that stays true if either ever
|
|
2131
|
+
* changes. (`recordSeedOutcome` in `@objectstack/runtime` states the same
|
|
2132
|
+
* rule for the same reason.)
|
|
2133
|
+
*
|
|
2134
|
+
* The value is FROZEN and holds a COPY. `getService` hands out the stored
|
|
2135
|
+
* reference, so an unfrozen live array would let any reader edit the
|
|
2136
|
+
* kernel's own record of what was missing — and this record exists
|
|
2137
|
+
* precisely so that two packages cannot disagree about it.
|
|
2138
|
+
*/
|
|
2139
|
+
publishDegradedCapabilities(missingCoreServices) {
|
|
2140
|
+
try {
|
|
2141
|
+
if (this.services.has(DEGRADED_CAPABILITIES_SERVICE) || this.pluginLoader.hasService(DEGRADED_CAPABILITIES_SERVICE)) {
|
|
2142
|
+
return;
|
|
2143
|
+
}
|
|
2144
|
+
this.registerService(
|
|
2145
|
+
DEGRADED_CAPABILITIES_SERVICE,
|
|
2146
|
+
Object.freeze({ missingCoreServices: Object.freeze([...missingCoreServices]) })
|
|
2147
|
+
);
|
|
2148
|
+
} catch {
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2046
2151
|
/**
|
|
2047
2152
|
* Bootstrap the kernel with enhanced features
|
|
2048
2153
|
*/
|
|
@@ -2155,11 +2260,23 @@ var ObjectKernel = class {
|
|
|
2155
2260
|
}
|
|
2156
2261
|
return results;
|
|
2157
2262
|
}
|
|
2263
|
+
/**
|
|
2264
|
+
* Per-plugin startup durations: plugin name -> elapsed milliseconds that
|
|
2265
|
+
* plugin's `start()` took. Not start instants -- see
|
|
2266
|
+
* {@link PluginStartupResult.durationMs}.
|
|
2267
|
+
*/
|
|
2268
|
+
getPluginStartupDurations() {
|
|
2269
|
+
return new Map(this.pluginStartupDurations);
|
|
2270
|
+
}
|
|
2158
2271
|
/**
|
|
2159
2272
|
* Get plugin startup metrics
|
|
2273
|
+
*
|
|
2274
|
+
* @deprecated Renamed to {@link ObjectKernel.getPluginStartupDurations},
|
|
2275
|
+
* which states what the values are. Retained as a delegating alias so
|
|
2276
|
+
* nothing has to change on this release; slated for removal.
|
|
2160
2277
|
*/
|
|
2161
2278
|
getPluginMetrics() {
|
|
2162
|
-
return
|
|
2279
|
+
return this.getPluginStartupDurations();
|
|
2163
2280
|
}
|
|
2164
2281
|
/**
|
|
2165
2282
|
* Whether a plugin with the given name has been registered on this kernel.
|
|
@@ -2281,11 +2398,14 @@ var ObjectKernel = class {
|
|
|
2281
2398
|
);
|
|
2282
2399
|
const duration = Date.now() - startTime;
|
|
2283
2400
|
this.startedPlugins.add(plugin.name);
|
|
2284
|
-
this.
|
|
2401
|
+
this.pluginStartupDurations.set(plugin.name, duration);
|
|
2285
2402
|
this.logger.debug(`Plugin started: ${plugin.name} (${duration}ms)`);
|
|
2286
2403
|
return {
|
|
2287
2404
|
success: true,
|
|
2288
2405
|
pluginName: plugin.name,
|
|
2406
|
+
durationMs: duration,
|
|
2407
|
+
// Deprecated alias carrying the same elapsed value; see
|
|
2408
|
+
// PluginStartupResult.startTime.
|
|
2289
2409
|
startTime: duration
|
|
2290
2410
|
};
|
|
2291
2411
|
} catch (error) {
|
|
@@ -2295,6 +2415,9 @@ var ObjectKernel = class {
|
|
|
2295
2415
|
success: false,
|
|
2296
2416
|
pluginName: plugin.name,
|
|
2297
2417
|
error,
|
|
2418
|
+
durationMs: duration,
|
|
2419
|
+
// Deprecated alias carrying the same elapsed value; see
|
|
2420
|
+
// PluginStartupResult.startTime.
|
|
2298
2421
|
startTime: duration,
|
|
2299
2422
|
timedOut: isTimeout
|
|
2300
2423
|
};
|
|
@@ -2482,6 +2605,26 @@ var LiteKernel = class extends ObjectKernelBase {
|
|
|
2482
2605
|
* Register a plugin
|
|
2483
2606
|
* @param plugin - Plugin instance
|
|
2484
2607
|
*
|
|
2608
|
+
* A plugin object the DECLARED plugin contract refuses is refused here,
|
|
2609
|
+
* with `PLUGIN_CONTRACT_VIOLATION` — the same check, the same envelope,
|
|
2610
|
+
* that `ObjectKernel.use()` runs through `PluginLoader` (`plugin-contract.ts`
|
|
2611
|
+
* is the one statement both kernels call; #16721, maintainer ruling
|
|
2612
|
+
* 2026-09-08, option A under #9864's precedent that the kernels converge).
|
|
2613
|
+
*
|
|
2614
|
+
* This method used to write the object straight into the registry, so the
|
|
2615
|
+
* same plugin was accepted by this kernel and refused by `ObjectKernel` —
|
|
2616
|
+
* and `AGENTS.md` names THIS kernel for tests, so a plugin could be green
|
|
2617
|
+
* in vitest and refused at production boot. Measured before converging
|
|
2618
|
+
* (#16721 step 1): of 813 `LiteKernel.use()` calls reachable in this
|
|
2619
|
+
* repository's suites, 807 were accepted by the schema unchanged and the
|
|
2620
|
+
* six refusals came from three test-local fixture objects, none of them
|
|
2621
|
+
* product code.
|
|
2622
|
+
*
|
|
2623
|
+
* Ordering, and why it is pinned: state first (`validateIdle`), then the
|
|
2624
|
+
* contract, then registration — a refused plugin never reaches the
|
|
2625
|
+
* registry, so it can neither be booted nor supersede an earlier
|
|
2626
|
+
* registration under its name.
|
|
2627
|
+
*
|
|
2485
2628
|
* Duplicate names OVERWRITE, with one `warn` naming both versions — the
|
|
2486
2629
|
* declared contract in `plugin-registration.ts`, applied identically by
|
|
2487
2630
|
* `ObjectKernel.use()` (#9864, maintainer ruling 2026-08-19).
|
|
@@ -2495,6 +2638,7 @@ var LiteKernel = class extends ObjectKernelBase {
|
|
|
2495
2638
|
*/
|
|
2496
2639
|
use(plugin) {
|
|
2497
2640
|
this.validateIdle();
|
|
2641
|
+
assertPluginContract(plugin);
|
|
2498
2642
|
registerPluginByName(this.plugins, plugin, this.logger);
|
|
2499
2643
|
return this;
|
|
2500
2644
|
}
|
|
@@ -4120,228 +4264,6 @@ var _PluginSandboxRuntime = class _PluginSandboxRuntime {
|
|
|
4120
4264
|
_PluginSandboxRuntime.MONITORING_INTERVAL_MS = 5e3;
|
|
4121
4265
|
var PluginSandboxRuntime = _PluginSandboxRuntime;
|
|
4122
4266
|
|
|
4123
|
-
// src/security/security-scanner.ts
|
|
4124
|
-
var PluginSecurityScanner = class {
|
|
4125
|
-
constructor(logger, config) {
|
|
4126
|
-
// Known vulnerabilities database (CVE cache)
|
|
4127
|
-
this.vulnerabilityDb = /* @__PURE__ */ new Map();
|
|
4128
|
-
// Scan results cache
|
|
4129
|
-
this.scanResults = /* @__PURE__ */ new Map();
|
|
4130
|
-
this.passThreshold = 70;
|
|
4131
|
-
this.logger = logger.child({ component: "SecurityScanner" });
|
|
4132
|
-
if (config?.passThreshold !== void 0) {
|
|
4133
|
-
this.passThreshold = config.passThreshold;
|
|
4134
|
-
}
|
|
4135
|
-
}
|
|
4136
|
-
/**
|
|
4137
|
-
* Perform a comprehensive security scan on a plugin
|
|
4138
|
-
*/
|
|
4139
|
-
async scan(target) {
|
|
4140
|
-
this.logger.info("Starting security scan", {
|
|
4141
|
-
pluginId: target.pluginId,
|
|
4142
|
-
version: target.version
|
|
4143
|
-
});
|
|
4144
|
-
const issues = [];
|
|
4145
|
-
try {
|
|
4146
|
-
const codeIssues = await this.scanCode(target);
|
|
4147
|
-
issues.push(...codeIssues);
|
|
4148
|
-
const depIssues = await this.scanDependencies(target);
|
|
4149
|
-
issues.push(...depIssues);
|
|
4150
|
-
const malwareIssues = await this.scanMalware(target);
|
|
4151
|
-
issues.push(...malwareIssues);
|
|
4152
|
-
const licenseIssues = await this.scanLicenses(target);
|
|
4153
|
-
issues.push(...licenseIssues);
|
|
4154
|
-
const configIssues = await this.scanConfiguration(target);
|
|
4155
|
-
issues.push(...configIssues);
|
|
4156
|
-
const score = this.calculateSecurityScore(issues);
|
|
4157
|
-
const result = {
|
|
4158
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4159
|
-
scanner: { name: "ObjectStack Security Scanner", version: "1.0.0" },
|
|
4160
|
-
status: score >= this.passThreshold ? "passed" : "failed",
|
|
4161
|
-
vulnerabilities: issues.map((issue) => ({
|
|
4162
|
-
id: issue.id,
|
|
4163
|
-
severity: issue.severity,
|
|
4164
|
-
category: issue.category,
|
|
4165
|
-
title: issue.title,
|
|
4166
|
-
description: issue.description,
|
|
4167
|
-
location: issue.location ? `${issue.location.file}:${issue.location.line}` : void 0,
|
|
4168
|
-
remediation: issue.remediation,
|
|
4169
|
-
affectedVersions: [],
|
|
4170
|
-
exploitAvailable: false,
|
|
4171
|
-
patchAvailable: false
|
|
4172
|
-
})),
|
|
4173
|
-
summary: {
|
|
4174
|
-
totalVulnerabilities: issues.length,
|
|
4175
|
-
criticalCount: issues.filter((i) => i.severity === "critical").length,
|
|
4176
|
-
highCount: issues.filter((i) => i.severity === "high").length,
|
|
4177
|
-
mediumCount: issues.filter((i) => i.severity === "medium").length,
|
|
4178
|
-
lowCount: issues.filter((i) => i.severity === "low").length,
|
|
4179
|
-
infoCount: issues.filter((i) => i.severity === "info").length
|
|
4180
|
-
}
|
|
4181
|
-
};
|
|
4182
|
-
this.scanResults.set(`${target.pluginId}:${target.version}`, result);
|
|
4183
|
-
this.logger.info("Security scan complete", {
|
|
4184
|
-
pluginId: target.pluginId,
|
|
4185
|
-
score,
|
|
4186
|
-
status: result.status,
|
|
4187
|
-
summary: result.summary
|
|
4188
|
-
});
|
|
4189
|
-
return result;
|
|
4190
|
-
} catch (error) {
|
|
4191
|
-
this.logger.error("Security scan failed", {
|
|
4192
|
-
pluginId: target.pluginId,
|
|
4193
|
-
error
|
|
4194
|
-
});
|
|
4195
|
-
throw error;
|
|
4196
|
-
}
|
|
4197
|
-
}
|
|
4198
|
-
/**
|
|
4199
|
-
* Scan code for vulnerabilities
|
|
4200
|
-
*/
|
|
4201
|
-
async scanCode(target) {
|
|
4202
|
-
const issues = [];
|
|
4203
|
-
this.logger.debug("Code scan complete", {
|
|
4204
|
-
pluginId: target.pluginId,
|
|
4205
|
-
issuesFound: issues.length
|
|
4206
|
-
});
|
|
4207
|
-
return issues;
|
|
4208
|
-
}
|
|
4209
|
-
/**
|
|
4210
|
-
* Scan dependencies for known vulnerabilities
|
|
4211
|
-
*/
|
|
4212
|
-
async scanDependencies(target) {
|
|
4213
|
-
const issues = [];
|
|
4214
|
-
if (!target.dependencies) {
|
|
4215
|
-
return issues;
|
|
4216
|
-
}
|
|
4217
|
-
for (const [depName, version] of Object.entries(target.dependencies)) {
|
|
4218
|
-
const vulnKey = `${depName}@${version}`;
|
|
4219
|
-
const vulnerability = this.vulnerabilityDb.get(vulnKey);
|
|
4220
|
-
if (vulnerability) {
|
|
4221
|
-
issues.push({
|
|
4222
|
-
id: `vuln-${vulnerability.cve || depName}`,
|
|
4223
|
-
severity: vulnerability.severity,
|
|
4224
|
-
category: "vulnerability",
|
|
4225
|
-
title: `Vulnerable dependency: ${depName}`,
|
|
4226
|
-
description: `${depName}@${version} has known security vulnerabilities`,
|
|
4227
|
-
remediation: vulnerability.fixedIn ? `Upgrade to ${vulnerability.fixedIn.join(" or ")}` : "No fix available",
|
|
4228
|
-
cve: vulnerability.cve
|
|
4229
|
-
});
|
|
4230
|
-
}
|
|
4231
|
-
}
|
|
4232
|
-
this.logger.debug("Dependency scan complete", {
|
|
4233
|
-
pluginId: target.pluginId,
|
|
4234
|
-
dependencies: Object.keys(target.dependencies).length,
|
|
4235
|
-
vulnerabilities: issues.length
|
|
4236
|
-
});
|
|
4237
|
-
return issues;
|
|
4238
|
-
}
|
|
4239
|
-
/**
|
|
4240
|
-
* Scan for malware patterns
|
|
4241
|
-
*/
|
|
4242
|
-
async scanMalware(target) {
|
|
4243
|
-
const issues = [];
|
|
4244
|
-
this.logger.debug("Malware scan complete", {
|
|
4245
|
-
pluginId: target.pluginId,
|
|
4246
|
-
issuesFound: issues.length
|
|
4247
|
-
});
|
|
4248
|
-
return issues;
|
|
4249
|
-
}
|
|
4250
|
-
/**
|
|
4251
|
-
* Check license compliance
|
|
4252
|
-
*/
|
|
4253
|
-
async scanLicenses(target) {
|
|
4254
|
-
const issues = [];
|
|
4255
|
-
if (!target.dependencies) {
|
|
4256
|
-
return issues;
|
|
4257
|
-
}
|
|
4258
|
-
this.logger.debug("License scan complete", {
|
|
4259
|
-
pluginId: target.pluginId,
|
|
4260
|
-
issuesFound: issues.length
|
|
4261
|
-
});
|
|
4262
|
-
return issues;
|
|
4263
|
-
}
|
|
4264
|
-
/**
|
|
4265
|
-
* Check configuration security
|
|
4266
|
-
*/
|
|
4267
|
-
async scanConfiguration(target) {
|
|
4268
|
-
const issues = [];
|
|
4269
|
-
this.logger.debug("Configuration scan complete", {
|
|
4270
|
-
pluginId: target.pluginId,
|
|
4271
|
-
issuesFound: issues.length
|
|
4272
|
-
});
|
|
4273
|
-
return issues;
|
|
4274
|
-
}
|
|
4275
|
-
/**
|
|
4276
|
-
* Calculate security score based on issues
|
|
4277
|
-
*/
|
|
4278
|
-
calculateSecurityScore(issues) {
|
|
4279
|
-
let score = 100;
|
|
4280
|
-
for (const issue of issues) {
|
|
4281
|
-
switch (issue.severity) {
|
|
4282
|
-
case "critical":
|
|
4283
|
-
score -= 20;
|
|
4284
|
-
break;
|
|
4285
|
-
case "high":
|
|
4286
|
-
score -= 10;
|
|
4287
|
-
break;
|
|
4288
|
-
case "medium":
|
|
4289
|
-
score -= 5;
|
|
4290
|
-
break;
|
|
4291
|
-
case "low":
|
|
4292
|
-
score -= 2;
|
|
4293
|
-
break;
|
|
4294
|
-
case "info":
|
|
4295
|
-
score -= 0;
|
|
4296
|
-
break;
|
|
4297
|
-
}
|
|
4298
|
-
}
|
|
4299
|
-
return Math.max(0, score);
|
|
4300
|
-
}
|
|
4301
|
-
/**
|
|
4302
|
-
* Add a vulnerability to the database
|
|
4303
|
-
*/
|
|
4304
|
-
addVulnerability(packageName, version, vulnerability) {
|
|
4305
|
-
const key = `${packageName}@${version}`;
|
|
4306
|
-
this.vulnerabilityDb.set(key, vulnerability);
|
|
4307
|
-
this.logger.debug("Vulnerability added to database", {
|
|
4308
|
-
package: packageName,
|
|
4309
|
-
version,
|
|
4310
|
-
cve: vulnerability.cve
|
|
4311
|
-
});
|
|
4312
|
-
}
|
|
4313
|
-
/**
|
|
4314
|
-
* Get scan result from cache
|
|
4315
|
-
*/
|
|
4316
|
-
getScanResult(pluginId, version) {
|
|
4317
|
-
return this.scanResults.get(`${pluginId}:${version}`);
|
|
4318
|
-
}
|
|
4319
|
-
/**
|
|
4320
|
-
* Clear scan results cache
|
|
4321
|
-
*/
|
|
4322
|
-
clearCache() {
|
|
4323
|
-
this.scanResults.clear();
|
|
4324
|
-
this.logger.debug("Scan results cache cleared");
|
|
4325
|
-
}
|
|
4326
|
-
/**
|
|
4327
|
-
* Update vulnerability database from external source
|
|
4328
|
-
*/
|
|
4329
|
-
async updateVulnerabilityDatabase() {
|
|
4330
|
-
this.logger.info("Updating vulnerability database");
|
|
4331
|
-
this.logger.info("Vulnerability database updated", {
|
|
4332
|
-
entries: this.vulnerabilityDb.size
|
|
4333
|
-
});
|
|
4334
|
-
}
|
|
4335
|
-
/**
|
|
4336
|
-
* Shutdown security scanner
|
|
4337
|
-
*/
|
|
4338
|
-
shutdown() {
|
|
4339
|
-
this.vulnerabilityDb.clear();
|
|
4340
|
-
this.scanResults.clear();
|
|
4341
|
-
this.logger.info("Security scanner shutdown complete");
|
|
4342
|
-
}
|
|
4343
|
-
};
|
|
4344
|
-
|
|
4345
4267
|
// src/security/api-key.ts
|
|
4346
4268
|
var import_node_crypto3 = require("crypto");
|
|
4347
4269
|
var import_security = require("@objectstack/spec/security");
|
|
@@ -4428,19 +4350,22 @@ async function resolveApiKeyAdmission(ql, headers, nowMs = Date.now(), tenancyPo
|
|
|
4428
4350
|
const userId = row.user_id ?? row.userId;
|
|
4429
4351
|
if (!userId || typeof userId !== "string") return { outcome: "none" };
|
|
4430
4352
|
const tenantId = typeof row.active_organization_id === "string" && row.active_organization_id ? row.active_organization_id : void 0;
|
|
4353
|
+
const keyId = typeof row.id === "string" && row.id ? row.id : void 0;
|
|
4431
4354
|
if (!tenantId && tenancyPosture) {
|
|
4432
4355
|
const posture = tenancyPosture;
|
|
4433
4356
|
if ((0, import_security.postureEnforcesWall)(posture) && !(0, import_security.postureUsesUnionScope)(posture)) {
|
|
4434
4357
|
return {
|
|
4435
4358
|
outcome: "refused",
|
|
4436
4359
|
reason: "organization_required",
|
|
4360
|
+
keyId,
|
|
4361
|
+
userId,
|
|
4437
4362
|
message: "This API key carries no organization and cannot be used under the `isolated` tenancy posture, where every organization-scoped read is walled to an active organization. Mint a replacement key \u2014 new keys inherit the minter\u2019s active organization."
|
|
4438
4363
|
};
|
|
4439
4364
|
}
|
|
4440
4365
|
}
|
|
4441
4366
|
return {
|
|
4442
4367
|
outcome: "admitted",
|
|
4443
|
-
principal: { userId, tenantId, scopes: parseScopes(row.scopes) }
|
|
4368
|
+
principal: { userId, keyId, tenantId, scopes: parseScopes(row.scopes) }
|
|
4444
4369
|
};
|
|
4445
4370
|
}
|
|
4446
4371
|
function readHeader(headers, name) {
|
|
@@ -4498,6 +4423,7 @@ function rethrowAuthzStoreUnavailable(err) {
|
|
|
4498
4423
|
var import_types2 = require("@objectstack/types");
|
|
4499
4424
|
var import_spec2 = require("@objectstack/spec");
|
|
4500
4425
|
var import_security2 = require("@objectstack/spec/security");
|
|
4426
|
+
var import_shared = require("@objectstack/spec/shared");
|
|
4501
4427
|
|
|
4502
4428
|
// src/security/grant-validity.ts
|
|
4503
4429
|
function toEpochMs(value) {
|
|
@@ -4832,6 +4758,18 @@ function safeJsonParse2(s, fallback) {
|
|
|
4832
4758
|
return fallback;
|
|
4833
4759
|
}
|
|
4834
4760
|
}
|
|
4761
|
+
function warnApiKeyRefusal(details) {
|
|
4762
|
+
const { reason, keyId, userId, organizationId } = details;
|
|
4763
|
+
console.warn(
|
|
4764
|
+
`[security] API key refused (${reason}): key=${keyId ?? "<unknown>"} principal=${userId ?? "<unknown>"} organization=${organizationId ?? "<none>"}. The caller received the generic 401 UNAUTHENTICATED \u2014 this reason is server-side only.`
|
|
4765
|
+
);
|
|
4766
|
+
}
|
|
4767
|
+
function warnSessionOrganizationClaimDropped(details) {
|
|
4768
|
+
const { reason, sessionId, userId, organizationId } = details;
|
|
4769
|
+
console.warn(
|
|
4770
|
+
`[security] Session organization claim dropped (${reason}): session=${sessionId ?? "<unknown>"} principal=${userId ?? "<unknown>"} organization=${organizationId ?? "<none>"}. The session stays authenticated with NO active organization \u2014 the wire is unchanged.`
|
|
4771
|
+
);
|
|
4772
|
+
}
|
|
4835
4773
|
async function tryFind(ql, object, where, limit = 100, organizationId) {
|
|
4836
4774
|
if (!ql || typeof ql.find !== "function") return [];
|
|
4837
4775
|
try {
|
|
@@ -4855,9 +4793,15 @@ async function resolveAuthzContext(input) {
|
|
|
4855
4793
|
};
|
|
4856
4794
|
let userId;
|
|
4857
4795
|
let tenantId;
|
|
4796
|
+
let sessionId;
|
|
4858
4797
|
const admission = await resolveApiKeyAdmission(ql, headers, input.nowMs, input.tenancyPosture);
|
|
4859
4798
|
if (admission.outcome === "refused") {
|
|
4860
|
-
|
|
4799
|
+
warnApiKeyRefusal({
|
|
4800
|
+
reason: admission.reason,
|
|
4801
|
+
keyId: admission.keyId,
|
|
4802
|
+
userId: admission.userId,
|
|
4803
|
+
organizationId: admission.organizationId
|
|
4804
|
+
});
|
|
4861
4805
|
return ctx;
|
|
4862
4806
|
}
|
|
4863
4807
|
const keyPrincipal = admission.outcome === "admitted" ? admission.principal : void 0;
|
|
@@ -4873,6 +4817,8 @@ async function resolveAuthzContext(input) {
|
|
|
4873
4817
|
const sessionData = await input.getSession(headers);
|
|
4874
4818
|
userId = sessionData?.user?.id ?? sessionData?.session?.userId;
|
|
4875
4819
|
tenantId = tenantId ?? sessionData?.session?.activeOrganizationId;
|
|
4820
|
+
const rawSessionId = sessionData?.session?.id;
|
|
4821
|
+
sessionId = typeof rawSessionId === "string" && rawSessionId ? rawSessionId : void 0;
|
|
4876
4822
|
ctx.accessToken = sessionData?.session?.token ?? ctx.accessToken;
|
|
4877
4823
|
if (sessionData?.user?.email) ctx.email = String(sessionData.user.email);
|
|
4878
4824
|
} catch {
|
|
@@ -4882,7 +4828,7 @@ async function resolveAuthzContext(input) {
|
|
|
4882
4828
|
ctx.userId = userId;
|
|
4883
4829
|
if (tenantId) ctx.tenantId = tenantId;
|
|
4884
4830
|
if (!ql || typeof ql.find !== "function") return ctx;
|
|
4885
|
-
|
|
4831
|
+
let grants = await resolveUserAuthzGrants(ql, userId, {
|
|
4886
4832
|
tenantId,
|
|
4887
4833
|
nowMs: input.nowMs,
|
|
4888
4834
|
seedPermissions: ctx.permissions,
|
|
@@ -4891,19 +4837,37 @@ async function resolveAuthzContext(input) {
|
|
|
4891
4837
|
if (keyPrincipal?.tenantId && input.tenancyPosture) {
|
|
4892
4838
|
const posture = input.tenancyPosture;
|
|
4893
4839
|
if ((0, import_security2.postureEnforcesWall)(posture) && !grants.accessible_org_ids.includes(keyPrincipal.tenantId)) {
|
|
4840
|
+
warnApiKeyRefusal({
|
|
4841
|
+
reason: "organization_membership_ended",
|
|
4842
|
+
keyId: keyPrincipal.keyId,
|
|
4843
|
+
userId: keyPrincipal.userId,
|
|
4844
|
+
organizationId: keyPrincipal.tenantId
|
|
4845
|
+
});
|
|
4894
4846
|
return {
|
|
4895
4847
|
positions: [],
|
|
4896
4848
|
permissions: [],
|
|
4897
4849
|
systemPermissions: [],
|
|
4898
4850
|
org_user_ids: [],
|
|
4899
|
-
accessible_org_ids: []
|
|
4900
|
-
authRefusal: {
|
|
4901
|
-
reason: "organization_membership_ended",
|
|
4902
|
-
message: "This API key authenticates into an organization its owner is no longer a member of. The key was not revoked \u2014 the membership that backed it ended."
|
|
4903
|
-
}
|
|
4851
|
+
accessible_org_ids: []
|
|
4904
4852
|
};
|
|
4905
4853
|
}
|
|
4906
4854
|
}
|
|
4855
|
+
if (!keyPrincipal && tenantId && input.tenancyPosture && (0, import_security2.postureEnforcesWall)(input.tenancyPosture) && !grants.accessible_org_ids.includes(tenantId)) {
|
|
4856
|
+
warnSessionOrganizationClaimDropped({
|
|
4857
|
+
reason: "organization_membership_ended",
|
|
4858
|
+
sessionId,
|
|
4859
|
+
userId,
|
|
4860
|
+
organizationId: tenantId
|
|
4861
|
+
});
|
|
4862
|
+
tenantId = void 0;
|
|
4863
|
+
delete ctx.tenantId;
|
|
4864
|
+
grants = await resolveUserAuthzGrants(ql, userId, {
|
|
4865
|
+
tenantId,
|
|
4866
|
+
nowMs: input.nowMs,
|
|
4867
|
+
seedPermissions: ctx.permissions,
|
|
4868
|
+
seedEmail: ctx.email
|
|
4869
|
+
});
|
|
4870
|
+
}
|
|
4907
4871
|
ctx.positions = grants.positions;
|
|
4908
4872
|
ctx.permissions = grants.permissions;
|
|
4909
4873
|
ctx.systemPermissions = grants.systemPermissions;
|
|
@@ -5080,17 +5044,9 @@ async function hasPlatformAdminStanding(ql, userId, opts = {}) {
|
|
|
5080
5044
|
return false;
|
|
5081
5045
|
}
|
|
5082
5046
|
}
|
|
5083
|
-
function isValidTimeZone(tz) {
|
|
5084
|
-
try {
|
|
5085
|
-
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
5086
|
-
return true;
|
|
5087
|
-
} catch {
|
|
5088
|
-
return false;
|
|
5089
|
-
}
|
|
5090
|
-
}
|
|
5091
5047
|
function coerceTimeZone(value) {
|
|
5092
5048
|
const s = typeof value === "string" ? value.trim() : value != null ? String(value).trim() : "";
|
|
5093
|
-
return s &&
|
|
5049
|
+
return s && (0, import_shared.isValueDomainMember)("iana_time_zone", s) ? s : void 0;
|
|
5094
5050
|
}
|
|
5095
5051
|
function coerceLocale(value) {
|
|
5096
5052
|
const s = typeof value === "string" ? value.trim() : value != null ? String(value).trim() : "";
|
|
@@ -6177,6 +6133,52 @@ function errText(err) {
|
|
|
6177
6133
|
}
|
|
6178
6134
|
}
|
|
6179
6135
|
|
|
6136
|
+
// src/utils/advisory-aggregation.ts
|
|
6137
|
+
var import_node_async_hooks = require("async_hooks");
|
|
6138
|
+
var ADVISORY_SAMPLE_ROWS = 5;
|
|
6139
|
+
var storage = new import_node_async_hooks.AsyncLocalStorage();
|
|
6140
|
+
function keyOf(object, rule) {
|
|
6141
|
+
return JSON.stringify([object, rule]);
|
|
6142
|
+
}
|
|
6143
|
+
function recordAdvisoryHit(hit) {
|
|
6144
|
+
const collector = storage.getStore();
|
|
6145
|
+
if (!collector) return false;
|
|
6146
|
+
const key = keyOf(hit.object, hit.rule);
|
|
6147
|
+
const existing = collector.groups.get(key);
|
|
6148
|
+
if (existing) {
|
|
6149
|
+
existing.rows += 1;
|
|
6150
|
+
if (hit.recordRef != null && existing.sampleRows.length < ADVISORY_SAMPLE_ROWS) {
|
|
6151
|
+
existing.sampleRows.push(hit.recordRef);
|
|
6152
|
+
}
|
|
6153
|
+
return true;
|
|
6154
|
+
}
|
|
6155
|
+
collector.groups.set(key, {
|
|
6156
|
+
object: hit.object,
|
|
6157
|
+
rule: hit.rule,
|
|
6158
|
+
severity: hit.severity,
|
|
6159
|
+
message: hit.message,
|
|
6160
|
+
rows: 1,
|
|
6161
|
+
sampleRows: hit.recordRef != null ? [hit.recordRef] : []
|
|
6162
|
+
});
|
|
6163
|
+
return true;
|
|
6164
|
+
}
|
|
6165
|
+
function isAggregatingAdvisories() {
|
|
6166
|
+
return storage.getStore() !== void 0;
|
|
6167
|
+
}
|
|
6168
|
+
async function runWithAdvisoryAggregation(fn, report) {
|
|
6169
|
+
const collector = { groups: /* @__PURE__ */ new Map() };
|
|
6170
|
+
try {
|
|
6171
|
+
return await storage.run(collector, fn);
|
|
6172
|
+
} finally {
|
|
6173
|
+
if (collector.groups.size > 0) {
|
|
6174
|
+
try {
|
|
6175
|
+
report([...collector.groups.values()]);
|
|
6176
|
+
} catch {
|
|
6177
|
+
}
|
|
6178
|
+
}
|
|
6179
|
+
}
|
|
6180
|
+
}
|
|
6181
|
+
|
|
6180
6182
|
// src/utils/filter-tokens.ts
|
|
6181
6183
|
var import_data2 = require("@objectstack/spec/data");
|
|
6182
6184
|
var UnknownFilterTokenError = class extends Error {
|
|
@@ -6692,7 +6694,7 @@ var PluginHealthMonitor = class {
|
|
|
6692
6694
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6693
6695
|
message,
|
|
6694
6696
|
metrics: {
|
|
6695
|
-
|
|
6697
|
+
uptimeMs: Date.now() - startTime
|
|
6696
6698
|
},
|
|
6697
6699
|
checks: checks.length > 0 ? checks : void 0
|
|
6698
6700
|
};
|
|
@@ -7554,6 +7556,7 @@ var import_contracts = require("@objectstack/spec/contracts");
|
|
|
7554
7556
|
0 && (module.exports = {
|
|
7555
7557
|
ADMIN_STANDING_NON_TABLE_INPUTS,
|
|
7556
7558
|
ADMIN_STANDING_SURFACE,
|
|
7559
|
+
ADVISORY_SAMPLE_ROWS,
|
|
7557
7560
|
ANONYMOUS_DENY_BODY,
|
|
7558
7561
|
ANONYMOUS_DENY_CODE,
|
|
7559
7562
|
ANONYMOUS_DENY_MESSAGE,
|
|
@@ -7591,7 +7594,6 @@ var import_contracts = require("@objectstack/spec/contracts");
|
|
|
7591
7594
|
PluginPermissionEnforcer,
|
|
7592
7595
|
PluginPermissionManager,
|
|
7593
7596
|
PluginSandboxRuntime,
|
|
7594
|
-
PluginSecurityScanner,
|
|
7595
7597
|
PluginSignatureVerifier,
|
|
7596
7598
|
QA,
|
|
7597
7599
|
SERVICE_NOT_REGISTERED_CODE,
|
|
@@ -7642,6 +7644,7 @@ var import_contracts = require("@objectstack/spec/contracts");
|
|
|
7642
7644
|
hasPlatformAdminStanding,
|
|
7643
7645
|
hashApiKey,
|
|
7644
7646
|
hashMigrationPlan,
|
|
7647
|
+
isAggregatingAdvisories,
|
|
7645
7648
|
isAudienceBindingSuggestionStatus,
|
|
7646
7649
|
isAuthGateAllowlisted,
|
|
7647
7650
|
isAuthzStoreUnavailableError,
|
|
@@ -7666,6 +7669,7 @@ var import_contracts = require("@objectstack/spec/contracts");
|
|
|
7666
7669
|
readAuthoredTranslationLayer,
|
|
7667
7670
|
readAuthzGrantsCacheTtlMs,
|
|
7668
7671
|
readRunJournal,
|
|
7672
|
+
recordAdvisoryHit,
|
|
7669
7673
|
recordNotFoundError,
|
|
7670
7674
|
reportAuthzCachePosture,
|
|
7671
7675
|
reportLegacyPlatformAdminGrant,
|
|
@@ -7686,6 +7690,7 @@ var import_contracts = require("@objectstack/spec/contracts");
|
|
|
7686
7690
|
resumeMigrationJournal,
|
|
7687
7691
|
rethrowAuthzStoreUnavailable,
|
|
7688
7692
|
runMigrationJournal,
|
|
7693
|
+
runWithAdvisoryAggregation,
|
|
7689
7694
|
safeExit,
|
|
7690
7695
|
setPlatformAdminConfigSink,
|
|
7691
7696
|
shouldDenyAnonymous,
|