@objectstack/core 17.0.0-rc.0 → 17.0.0-rc.2

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.cjs CHANGED
@@ -40,6 +40,8 @@ __export(index_exports, {
40
40
  DependencyResolver: () => DependencyResolver,
41
41
  HotReloadManager: () => HotReloadManager,
42
42
  LiteKernel: () => LiteKernel,
43
+ MigrationJournalRefusal: () => MigrationJournalRefusal,
44
+ MigrationPlanRegistry: () => MigrationPlanRegistry,
43
45
  NamespaceResolver: () => NamespaceResolver,
44
46
  ObjectKernel: () => ObjectKernel,
45
47
  ObjectKernelBase: () => ObjectKernelBase,
@@ -62,6 +64,7 @@ __export(index_exports, {
62
64
  ServiceLifecycle: () => ServiceLifecycle,
63
65
  UnknownFilterTokenError: () => UnknownFilterTokenError,
64
66
  UnresolvedFilterTokenError: () => UnresolvedFilterTokenError,
67
+ assertInitServiceRequirements: () => assertInitServiceRequirements,
65
68
  bucketKeyToCalendarRange: () => bucketKeyToCalendarRange,
66
69
  buildPermissionsFromGrants: () => buildPermissionsFromGrants,
67
70
  bulkWrite: () => bulkWrite,
@@ -80,33 +83,45 @@ __export(index_exports, {
80
83
  deepMerge: () => deepMerge,
81
84
  defaultIsTransientError: () => defaultIsTransientError,
82
85
  derivePosture: () => derivePosture,
86
+ describeInitOrderFault: () => describeInitOrderFault,
87
+ engineCanRollBack: () => engineCanRollBack,
83
88
  evaluateAuthGate: () => evaluateAuthGate,
84
89
  extractApiKey: () => extractApiKey,
85
90
  filterTokenContextFrom: () => filterTokenContextFrom,
91
+ findInterruptedRuns: () => findInterruptedRuns,
86
92
  generateApiKey: () => generateApiKey,
87
93
  generateEd25519KeyPair: () => generateEd25519KeyPair,
88
94
  getEnv: () => getEnv,
89
95
  getMemoryUsage: () => getMemoryUsage,
90
96
  hashApiKey: () => hashApiKey,
97
+ hashMigrationPlan: () => hashMigrationPlan,
91
98
  isAuthGateAllowlisted: () => isAuthGateAllowlisted,
92
99
  isExpired: () => isExpired,
93
100
  isGrantActive: () => isGrantActive,
94
101
  isGrantExpired: () => isGrantExpired,
95
102
  isNode: () => isNode,
103
+ nextUtcCalendarDay: () => import_data.nextUtcCalendarDay,
96
104
  parseScopes: () => parseScopes,
97
105
  parseSignature: () => parseSignature,
106
+ planChunks: () => planChunks,
98
107
  postureVisibleRows: () => postureVisibleRows,
99
108
  readAuthoredTranslationLayer: () => readAuthoredTranslationLayer,
109
+ readRunJournal: () => readRunJournal,
100
110
  resolveApiKeyPrincipal: () => resolveApiKeyPrincipal,
101
111
  resolveAuthzContext: () => resolveAuthzContext,
102
112
  resolveFilterToken: () => resolveFilterToken,
103
113
  resolveFilterTokens: () => resolveFilterTokens,
104
114
  resolveLocale: () => resolveLocale,
105
115
  resolveLocalizationContext: () => resolveLocalizationContext,
116
+ resolvePluginOrder: () => resolvePluginOrder,
106
117
  resolveUserAuthzGrants: () => resolveUserAuthzGrants,
118
+ resumeMigrationJournal: () => resumeMigrationJournal,
119
+ runMigrationJournal: () => runMigrationJournal,
107
120
  safeExit: () => safeExit,
108
121
  shouldDenyAnonymous: () => shouldDenyAnonymous,
109
122
  signPayload: () => signPayload,
123
+ utcInstantMs: () => import_data.utcInstantMs,
124
+ validateInitServiceContract: () => validateInitServiceContract,
110
125
  verifyPayload: () => verifyPayload,
111
126
  verifyPlatformSignature: () => verifyPlatformSignature,
112
127
  verifyPluginArtifact: () => verifyPluginArtifact,
@@ -117,6 +132,89 @@ __export(index_exports, {
117
132
  });
118
133
  module.exports = __toCommonJS(index_exports);
119
134
 
135
+ // src/plugin-order.ts
136
+ function resolvePluginOrder(plugins) {
137
+ const resolved = [];
138
+ const visited = /* @__PURE__ */ new Set();
139
+ const visiting = /* @__PURE__ */ new Set();
140
+ const visit = (pluginName) => {
141
+ if (visited.has(pluginName)) return;
142
+ if (visiting.has(pluginName)) {
143
+ throw new Error(`[Kernel] Circular dependency detected: ${pluginName}`);
144
+ }
145
+ const plugin = plugins.get(pluginName);
146
+ if (!plugin) {
147
+ throw new Error(`[Kernel] Plugin '${pluginName}' not found`);
148
+ }
149
+ visiting.add(pluginName);
150
+ for (const dep of plugin.dependencies ?? []) {
151
+ if (!plugins.has(dep)) {
152
+ throw new Error(
153
+ `[Kernel] Dependency '${dep}' not found for plugin '${pluginName}'`
154
+ );
155
+ }
156
+ visit(dep);
157
+ }
158
+ for (const dep of plugin.optionalDependencies ?? []) {
159
+ if (plugins.has(dep)) visit(dep);
160
+ }
161
+ visiting.delete(pluginName);
162
+ visited.add(pluginName);
163
+ resolved.push(plugin);
164
+ };
165
+ for (const pluginName of plugins.keys()) {
166
+ visit(pluginName);
167
+ }
168
+ return resolved;
169
+ }
170
+ function validateInitServiceContract(ordered, isServiceRegistered) {
171
+ const providerSlot = /* @__PURE__ */ new Map();
172
+ ordered.forEach((plugin, slot) => {
173
+ for (const service of plugin.providesServices ?? []) {
174
+ if (!providerSlot.has(service)) {
175
+ providerSlot.set(service, { plugin: plugin.name, slot });
176
+ }
177
+ }
178
+ });
179
+ const violations = [];
180
+ ordered.forEach((plugin, slot) => {
181
+ for (const service of plugin.requiresServices ?? []) {
182
+ if (isServiceRegistered(service)) continue;
183
+ const provider = providerSlot.get(service);
184
+ if (provider && provider.slot > slot) {
185
+ violations.push(
186
+ `'${plugin.name}' requires service '${service}' during init, but '${service}' is provided by '${provider.plugin}', which initializes later (slot ${provider.slot} vs ${slot}). Registration order is not a contract \u2014 declare '${provider.plugin}' in '${plugin.name}'.dependencies (hard) or .optionalDependencies (order-if-present) so the kernel hoists it.`
187
+ );
188
+ }
189
+ }
190
+ });
191
+ if (violations.length > 0) {
192
+ throw new Error(
193
+ `[Kernel] Plugin ordering contract violated (#4131):
194
+ - ${violations.join("\n - ")}`
195
+ );
196
+ }
197
+ }
198
+ function describeInitOrderFault(currentlyInitializing, plugins, serviceName) {
199
+ if (!currentlyInitializing) return "";
200
+ let providerHint = "";
201
+ for (const plugin of plugins) {
202
+ if (plugin.providesServices?.includes(serviceName)) {
203
+ providerHint = ` '${serviceName}' is provided by composed plugin '${plugin.name}', which has not initialized yet \u2014 declare it in the requiring plugin's dependencies/optionalDependencies.`;
204
+ break;
205
+ }
206
+ }
207
+ return ` (while plugin '${currentlyInitializing}' was initializing \u2014 a composition/ordering fault, #4131.${providerHint})`;
208
+ }
209
+ function assertInitServiceRequirements(plugin, isServiceRegistered) {
210
+ for (const service of plugin.requiresServices ?? []) {
211
+ if (isServiceRegistered(service)) continue;
212
+ throw new Error(
213
+ `[Kernel] Plugin '${plugin.name}' requires service '${service}' at init, but no such service is registered at this point of the boot. No composed plugin that initializes earlier provides it \u2014 compose a provider (and, if it initializes later without declaring '${service}' in providesServices, order it ahead via this plugin's dependencies/optionalDependencies) (#4131).`
214
+ );
215
+ }
216
+ }
217
+
120
218
  // src/kernel-base.ts
121
219
  var ObjectKernelBase = class {
122
220
  constructor(logger) {
@@ -167,7 +265,9 @@ var ObjectKernelBase = class {
167
265
  if (this.services instanceof Map) {
168
266
  const service = this.services.get(name);
169
267
  if (!service) {
170
- throw new Error(`[Kernel] Service '${name}' not found`);
268
+ throw new Error(
269
+ `[Kernel] Service '${name}' not found${this.describeInitOrderFault(name)}`
270
+ );
171
271
  }
172
272
  return service;
173
273
  } else {
@@ -218,40 +318,37 @@ var ObjectKernelBase = class {
218
318
  };
219
319
  }
220
320
  /**
221
- * Resolve plugin dependencies using topological sort
321
+ * Resolve plugin dependencies using topological sort — `dependencies`
322
+ * hard, `optionalDependencies` order-if-present (ADR-0116, #4131). One
323
+ * implementation shared with ObjectKernel via `plugin-order.ts`.
222
324
  * @returns Ordered list of plugins (dependencies first)
223
325
  */
224
326
  resolveDependencies() {
225
- const resolved = [];
226
- const visited = /* @__PURE__ */ new Set();
227
- const visiting = /* @__PURE__ */ new Set();
228
- const visit = (pluginName) => {
229
- if (visited.has(pluginName)) return;
230
- if (visiting.has(pluginName)) {
231
- throw new Error(`[Kernel] Circular dependency detected: ${pluginName}`);
232
- }
233
- const plugin = this.plugins.get(pluginName);
234
- if (!plugin) {
235
- throw new Error(`[Kernel] Plugin '${pluginName}' not found`);
236
- }
237
- visiting.add(pluginName);
238
- const deps = plugin.dependencies || [];
239
- for (const dep of deps) {
240
- if (!this.plugins.has(dep)) {
241
- throw new Error(
242
- `[Kernel] Dependency '${dep}' not found for plugin '${pluginName}'`
243
- );
244
- }
245
- visit(dep);
246
- }
247
- visiting.delete(pluginName);
248
- visited.add(pluginName);
249
- resolved.push(plugin);
250
- };
251
- for (const pluginName of this.plugins.keys()) {
252
- visit(pluginName);
253
- }
254
- return resolved;
327
+ return resolvePluginOrder(this.plugins);
328
+ }
329
+ /**
330
+ * Whether a service is registered on this kernel right now. Backs the
331
+ * init-service contract checks (#4131).
332
+ */
333
+ hasRegisteredService(name) {
334
+ return this.services.has(name);
335
+ }
336
+ /**
337
+ * Pre-Phase-1 ordering validation (ADR-0116, #4131): a plugin whose
338
+ * `requiresServices` names a service provided only by a LATER plugin is
339
+ * a named boot error before any init side effects.
340
+ */
341
+ validateInitServices(ordered) {
342
+ validateInitServiceContract(ordered, (name) => this.hasRegisteredService(name));
343
+ }
344
+ /**
345
+ * When a getService miss happens while a plugin's init() is running,
346
+ * append the structural diagnosis (#4131): which plugin was initializing,
347
+ * and — when a composed plugin declares the service — who provides it.
348
+ * Empty string outside Phase 1, so non-boot messages stay unchanged.
349
+ */
350
+ describeInitOrderFault(serviceName) {
351
+ return describeInitOrderFault(this.currentlyInitializing, this.plugins.values(), serviceName);
255
352
  }
256
353
  /**
257
354
  * Run plugin init phase
@@ -260,12 +357,16 @@ var ObjectKernelBase = class {
260
357
  async runPluginInit(plugin) {
261
358
  const pluginName = plugin.name;
262
359
  this.logger.info(`Initializing plugin: ${pluginName}`);
360
+ assertInitServiceRequirements(plugin, (name) => this.hasRegisteredService(name));
361
+ this.currentlyInitializing = pluginName;
263
362
  try {
264
363
  await plugin.init(this.context);
265
364
  this.logger.info(`Plugin initialized: ${pluginName}`);
266
365
  } catch (error) {
267
366
  this.logger.error(`Plugin init failed: ${pluginName}`, error);
268
367
  throw error;
368
+ } finally {
369
+ this.currentlyInitializing = void 0;
269
370
  }
270
371
  }
271
372
  /**
@@ -1112,7 +1213,11 @@ function createMemoryCache() {
1112
1213
  let hits = 0;
1113
1214
  let misses = 0;
1114
1215
  return {
1115
- _fallback: true,
1216
+ __serviceInfo: {
1217
+ status: "degraded",
1218
+ handlerReady: false,
1219
+ message: "In-process Map cache \u2014 not shared across instances, lost on restart. Register a cache plugin (e.g. Redis) for a real one."
1220
+ },
1116
1221
  _serviceName: "cache",
1117
1222
  async get(key) {
1118
1223
  const entry = store.get(key);
@@ -1147,7 +1252,11 @@ function createMemoryQueue() {
1147
1252
  const handlers = /* @__PURE__ */ new Map();
1148
1253
  let msgId = 0;
1149
1254
  return {
1150
- _fallback: true,
1255
+ __serviceInfo: {
1256
+ status: "degraded",
1257
+ handlerReady: false,
1258
+ message: "Synchronous in-process delivery \u2014 no durability, retry, or cross-instance fan-out. Register a queue plugin (e.g. BullMQ) for a real one."
1259
+ },
1151
1260
  _serviceName: "queue",
1152
1261
  async publish(queue, data) {
1153
1262
  const id = `fallback-msg-${++msgId}`;
@@ -1174,7 +1283,11 @@ function createMemoryQueue() {
1174
1283
  function createMemoryJob() {
1175
1284
  const jobs = /* @__PURE__ */ new Map();
1176
1285
  return {
1177
- _fallback: true,
1286
+ __serviceInfo: {
1287
+ status: "degraded",
1288
+ handlerReady: false,
1289
+ message: "In-process job registry \u2014 trigger() runs handlers, but scheduled jobs never fire on their own (no timer). Register a job plugin (e.g. Agenda) for real scheduling."
1290
+ },
1178
1291
  _serviceName: "job",
1179
1292
  async schedule(name, schedule, handler) {
1180
1293
  jobs.set(name, { schedule, handler });
@@ -1253,7 +1366,15 @@ function createMemoryI18n() {
1253
1366
  return void 0;
1254
1367
  }
1255
1368
  return {
1256
- _fallback: true,
1369
+ // [#4058] `degraded` (ADR-0076 D12): translations, locale fallback and
1370
+ // interpolation are all real — what is missing is persistence and the
1371
+ // authoring surface service-i18n adds. `handlerReady` left at the
1372
+ // `degraded` default (true): the dispatcher's `/i18n` domain does serve
1373
+ // this implementation.
1374
+ __serviceInfo: {
1375
+ status: "degraded",
1376
+ message: "In-memory translations \u2014 real lookup and locale fallback, but nothing is persisted. Register I18nServicePlugin from @objectstack/service-i18n for the full implementation."
1377
+ },
1257
1378
  _serviceName: "i18n",
1258
1379
  t(key, locale, params) {
1259
1380
  const data = resolveTranslations(locale) ?? mergedLocale(defaultLocale);
@@ -1310,7 +1431,14 @@ function createMemoryMetadata() {
1310
1431
  return map;
1311
1432
  }
1312
1433
  return {
1313
- _fallback: true,
1434
+ // [#4058] `degraded` (ADR-0076 D12): the registry is real — everything
1435
+ // registered is listable and readable back — it simply never reaches disk
1436
+ // or a database. `handlerReady` keeps the `degraded` default (true): the
1437
+ // dispatcher's `/meta` domain serves this implementation.
1438
+ __serviceInfo: {
1439
+ status: "degraded",
1440
+ message: "In-memory metadata registry \u2014 real reads and writes, no persistence (lost on restart). Register MetadataPlugin for a persisted registry."
1441
+ },
1314
1442
  _serviceName: "metadata",
1315
1443
  async register(type, name, data) {
1316
1444
  getTypeMap(type).set(name, data);
@@ -1526,24 +1654,12 @@ var ObjectKernel = class {
1526
1654
  this.services.set(name, loaderService);
1527
1655
  return loaderService;
1528
1656
  }
1529
- try {
1530
- const service2 = this.pluginLoader.getService(name);
1531
- if (service2 instanceof Promise) {
1532
- service2.catch(() => {
1533
- });
1534
- throw new Error(`Service '${name}' is async - use await`);
1535
- }
1536
- return service2;
1537
- } catch (error) {
1538
- if (error.message?.includes("is async")) {
1539
- throw error;
1540
- }
1541
- const isNotFoundError = error.message === `Service '${name}' not found`;
1542
- if (!isNotFoundError) {
1543
- throw error;
1544
- }
1545
- throw new Error(`[Kernel] Service '${name}' not found`);
1657
+ if (!this.pluginLoader.hasService(name)) {
1658
+ throw new Error(
1659
+ `[Kernel] Service '${name}' not found${this.describeInitOrderFault(name)}`
1660
+ );
1546
1661
  }
1662
+ throw new Error(`Service '${name}' is async - use await`);
1547
1663
  },
1548
1664
  replaceService: (name, implementation) => {
1549
1665
  const hasService = this.services.has(name) || this.pluginLoader.hasService(name);
@@ -1702,6 +1818,7 @@ var ObjectKernel = class {
1702
1818
  this.logger.warn("Circular service dependencies detected:", { cycles });
1703
1819
  }
1704
1820
  const orderedPlugins = this.resolveDependencies();
1821
+ validateInitServiceContract(orderedPlugins, (name) => this.hasAnyService(name));
1705
1822
  this.logger.info("Phase 1: Init plugins");
1706
1823
  for (const plugin of orderedPlugins) {
1707
1824
  await this.initPluginWithTimeout(plugin);
@@ -1847,13 +1964,70 @@ var ObjectKernel = class {
1847
1964
  async initPluginWithTimeout(plugin) {
1848
1965
  const timeout = plugin.startupTimeout || this.config.defaultStartupTimeout;
1849
1966
  this.logger.debug(`Init: ${plugin.name}`, { plugin: plugin.name });
1850
- const initPromise = plugin.init(this.context);
1967
+ assertInitServiceRequirements(plugin, (name) => this.hasAnyService(name));
1968
+ this.currentlyInitializing = plugin.name;
1969
+ try {
1970
+ await this.raceStartupTimeout(
1971
+ plugin.init(this.context),
1972
+ timeout,
1973
+ `Plugin ${plugin.name} init timeout after ${timeout}ms`
1974
+ );
1975
+ } finally {
1976
+ this.currentlyInitializing = void 0;
1977
+ }
1978
+ }
1979
+ /**
1980
+ * Race a plugin lifecycle hook against its startup-timeout guard, and
1981
+ * reclaim the guard the moment the race settles (#4813).
1982
+ *
1983
+ * The guard used to be armed and then abandoned: when the plugin won the
1984
+ * race, its `setTimeout` stayed ref'd in the event loop for the full
1985
+ * `startupTimeout`, so every process idled that long after its work was
1986
+ * done. One `os migrate` finished in 3s and then sat for 120s
1987
+ * (`ObjectQLPlugin.startupTimeout`), held open by 8 orphaned guards — one
1988
+ * per init plus one per start.
1989
+ *
1990
+ * Clearing on settle rather than `unref()`-ing at arm time is deliberate.
1991
+ * An unref'd guard also stops pinning the loop, but it stops being a guard
1992
+ * as well: if the hook never settles and nothing else keeps the loop alive,
1993
+ * Node exits before the timer can fire and the timeout is never reported.
1994
+ * The guard has to stay ref'd exactly as long as the race is undecided,
1995
+ * which is what `clearTimeout` in a `finally` expresses.
1996
+ *
1997
+ * `operation` is widened to `T | PromiseLike<T>` because the Plugin
1998
+ * contract permits a synchronous hook (`init`/`start` return
1999
+ * `void | Promise<void>`); such a hook wins the race immediately and the
2000
+ * guard is reclaimed on the same turn.
2001
+ */
2002
+ async raceStartupTimeout(operation, timeout, message) {
2003
+ let guard;
1851
2004
  const timeoutPromise = new Promise((_, reject) => {
1852
- setTimeout(() => {
1853
- reject(new Error(`Plugin ${plugin.name} init timeout after ${timeout}ms`));
2005
+ guard = setTimeout(() => {
2006
+ reject(new Error(message));
1854
2007
  }, timeout);
1855
2008
  });
1856
- await Promise.race([initPromise, timeoutPromise]);
2009
+ try {
2010
+ return await Promise.race([operation, timeoutPromise]);
2011
+ } finally {
2012
+ clearTimeout(guard);
2013
+ }
2014
+ }
2015
+ /**
2016
+ * Whether a service is resolvable on this kernel right now — direct
2017
+ * registration or a loader-registered factory. Backs the init-service
2018
+ * contract checks (#4131).
2019
+ */
2020
+ hasAnyService(name) {
2021
+ return this.services.has(name) || this.pluginLoader.hasService(name);
2022
+ }
2023
+ /**
2024
+ * When a getService miss happens while a plugin's init() is running,
2025
+ * append the structural diagnosis (#4131): which plugin was initializing,
2026
+ * and — when a composed plugin declares the service — who provides it.
2027
+ * Empty string outside Phase 1, so non-boot messages stay unchanged.
2028
+ */
2029
+ describeInitOrderFault(serviceName) {
2030
+ return describeInitOrderFault(this.currentlyInitializing, this.plugins.values(), serviceName);
1857
2031
  }
1858
2032
  async startPluginWithTimeout(plugin) {
1859
2033
  if (!plugin.start) {
@@ -1863,13 +2037,11 @@ var ObjectKernel = class {
1863
2037
  const startTime = Date.now();
1864
2038
  this.logger.debug(`Start: ${plugin.name}`, { plugin: plugin.name });
1865
2039
  try {
1866
- const startPromise = plugin.start(this.context);
1867
- const timeoutPromise = new Promise((_, reject) => {
1868
- setTimeout(() => {
1869
- reject(new Error(`Plugin ${plugin.name} start timeout after ${timeout}ms`));
1870
- }, timeout);
1871
- });
1872
- await Promise.race([startPromise, timeoutPromise]);
2040
+ await this.raceStartupTimeout(
2041
+ plugin.start(this.context),
2042
+ timeout,
2043
+ `Plugin ${plugin.name} start timeout after ${timeout}ms`
2044
+ );
1873
2045
  const duration = Date.now() - startTime;
1874
2046
  this.startedPlugins.add(plugin.name);
1875
2047
  this.pluginStartTimes.set(plugin.name, duration);
@@ -1927,35 +2099,13 @@ var ObjectKernel = class {
1927
2099
  }
1928
2100
  }
1929
2101
  }
2102
+ /**
2103
+ * Topological order over `dependencies` (hard) + `optionalDependencies`
2104
+ * (order-if-present) — ADR-0116, #4131. One implementation shared with
2105
+ * LiteKernel via `plugin-order.ts`.
2106
+ */
1930
2107
  resolveDependencies() {
1931
- const resolved = [];
1932
- const visited = /* @__PURE__ */ new Set();
1933
- const visiting = /* @__PURE__ */ new Set();
1934
- const visit = (pluginName) => {
1935
- if (visited.has(pluginName)) return;
1936
- if (visiting.has(pluginName)) {
1937
- throw new Error(`[Kernel] Circular dependency detected: ${pluginName}`);
1938
- }
1939
- const plugin = this.plugins.get(pluginName);
1940
- if (!plugin) {
1941
- throw new Error(`[Kernel] Plugin '${pluginName}' not found`);
1942
- }
1943
- visiting.add(pluginName);
1944
- const deps = plugin.dependencies || [];
1945
- for (const dep of deps) {
1946
- if (!this.plugins.has(dep)) {
1947
- throw new Error(`[Kernel] Dependency '${dep}' not found for plugin '${pluginName}'`);
1948
- }
1949
- visit(dep);
1950
- }
1951
- visiting.delete(pluginName);
1952
- visited.add(pluginName);
1953
- resolved.push(plugin);
1954
- };
1955
- for (const pluginName of this.plugins.keys()) {
1956
- visit(pluginName);
1957
- }
1958
- return resolved;
2108
+ return resolvePluginOrder(this.plugins);
1959
2109
  }
1960
2110
  registerShutdownSignals() {
1961
2111
  const signals = ["SIGINT", "SIGTERM", "SIGQUIT"];
@@ -2021,6 +2171,7 @@ var LiteKernel = class extends ObjectKernelBase {
2021
2171
  this.state = "initializing";
2022
2172
  this.logger.info("Bootstrap started");
2023
2173
  const orderedPlugins = this.resolveDependencies();
2174
+ this.validateInitServices(orderedPlugins);
2024
2175
  this.logger.info("Phase 1: Init plugins");
2025
2176
  for (const plugin of orderedPlugins) {
2026
2177
  await this.runPluginInit(plugin);
@@ -2621,6 +2772,11 @@ function createApiRegistryPlugin(config = {}) {
2621
2772
  } = config;
2622
2773
  return {
2623
2774
  name: "com.objectstack.core.api-registry",
2775
+ /**
2776
+ * Services init() registers on every path (ADR-0116, #4131) — lets the
2777
+ * kernel name this plugin when a consumer requires one before it inits.
2778
+ */
2779
+ providesServices: ["api-registry"],
2624
2780
  type: "standard",
2625
2781
  version: "1.0.0",
2626
2782
  init: async (ctx) => {
@@ -4683,14 +4839,13 @@ function evaluateAuthGate(sessionUser, path) {
4683
4839
 
4684
4840
  // src/security/anonymous-deny.ts
4685
4841
  var ANONYMOUS_DENY_STATUS = 401;
4686
- var ANONYMOUS_DENY_CODE = "unauthenticated";
4842
+ var ANONYMOUS_DENY_CODE = "UNAUTHENTICATED";
4687
4843
  var ANONYMOUS_DENY_MESSAGE = "Authentication is required to access this endpoint.";
4688
4844
  var ANONYMOUS_DENY_BODY = {
4689
4845
  error: ANONYMOUS_DENY_CODE,
4690
4846
  message: ANONYMOUS_DENY_MESSAGE
4691
4847
  };
4692
4848
  function shouldDenyAnonymous(input) {
4693
- if (!input.requireAuth) return false;
4694
4849
  if (typeof input.method === "string" && input.method.toUpperCase() === "OPTIONS") {
4695
4850
  return false;
4696
4851
  }
@@ -4702,6 +4857,7 @@ function shouldDenyAnonymous(input) {
4702
4857
  }
4703
4858
 
4704
4859
  // src/utils/datetime.ts
4860
+ var import_data = require("@objectstack/spec/data");
4705
4861
  function calendarPartsInTz(d, tz) {
4706
4862
  const parts = new Intl.DateTimeFormat("en-US", {
4707
4863
  timeZone: tz,
@@ -4942,8 +5098,395 @@ async function bulkWrite(rows, opts) {
4942
5098
  return results;
4943
5099
  }
4944
5100
 
5101
+ // src/utils/migration-journal.ts
5102
+ var import_node_crypto3 = require("crypto");
5103
+ var import_system3 = require("@objectstack/spec/system");
5104
+ var SYSTEM_CTX = { isSystem: true };
5105
+ var DEFAULT_CHUNK_SIZE = 200;
5106
+ function engineCanRollBack(engine) {
5107
+ const e = engine;
5108
+ if (typeof e?.transaction !== "function") return false;
5109
+ const defaultDriverName = e.getDefaultDriverName?.();
5110
+ const defaultDriver = defaultDriverName ? e.getDriverByName?.(defaultDriverName) : void 0;
5111
+ return !defaultDriver || typeof defaultDriver.beginTransaction === "function";
5112
+ }
5113
+ var MigrationPlanRegistry = class {
5114
+ constructor() {
5115
+ this.plans = /* @__PURE__ */ new Map();
5116
+ }
5117
+ register(plan) {
5118
+ this.plans.set(plan.id, plan);
5119
+ }
5120
+ get(planId) {
5121
+ return this.plans.get(planId);
5122
+ }
5123
+ list() {
5124
+ return [...this.plans.values()];
5125
+ }
5126
+ };
5127
+ var MigrationJournalRefusal = class extends Error {
5128
+ constructor(code, message) {
5129
+ super(message);
5130
+ this.name = "MigrationJournalRefusal";
5131
+ this.code = code;
5132
+ }
5133
+ };
5134
+ function planChunks(plan, rowCounts, chunkSize = plan.chunkSize ?? DEFAULT_CHUNK_SIZE) {
5135
+ const size = Math.max(1, chunkSize);
5136
+ const chunks = [];
5137
+ plan.steps.forEach((step, stepIndex) => {
5138
+ const total = rowCounts[stepIndex] ?? 0;
5139
+ for (let offset = 0; offset < total; offset += size) {
5140
+ chunks.push({
5141
+ index: chunks.length,
5142
+ stepIndex,
5143
+ stepName: step.name,
5144
+ offset,
5145
+ length: Math.min(size, total - offset)
5146
+ });
5147
+ }
5148
+ });
5149
+ return chunks;
5150
+ }
5151
+ function hashMigrationPlan(plan, chunks) {
5152
+ const shape = JSON.stringify({
5153
+ id: plan.id,
5154
+ steps: plan.steps.map((s) => s.name),
5155
+ chunks: chunks.map((c) => [c.stepIndex, c.offset, c.length])
5156
+ });
5157
+ return (0, import_node_crypto3.createHash)("sha256").update(shape, "utf8").digest("hex").slice(0, 32);
5158
+ }
5159
+ async function appendEvent(engine, event, execContext) {
5160
+ await engine.insert(
5161
+ import_system3.MIGRATION_JOURNAL_OBJECT,
5162
+ { ...event, created_at: event.created_at ?? (/* @__PURE__ */ new Date()).toISOString() },
5163
+ { context: execContext ?? { ...SYSTEM_CTX } }
5164
+ );
5165
+ }
5166
+ async function readRunJournal(engine, runId) {
5167
+ const rows = await engine.find(
5168
+ import_system3.MIGRATION_JOURNAL_OBJECT,
5169
+ { where: { run_id: runId } },
5170
+ { context: { ...SYSTEM_CTX } }
5171
+ );
5172
+ return [...rows ?? []].sort((a, b) => Number(a.seq) - Number(b.seq));
5173
+ }
5174
+ function chunkSetOf(events, kind) {
5175
+ const out = /* @__PURE__ */ new Set();
5176
+ for (const e of events) {
5177
+ if (e.kind === kind && typeof e.chunk_index === "number") out.add(e.chunk_index);
5178
+ }
5179
+ return out;
5180
+ }
5181
+ async function findInterruptedRuns(engine) {
5182
+ const started = await engine.find(
5183
+ import_system3.MIGRATION_JOURNAL_OBJECT,
5184
+ { where: { kind: "run_started" } },
5185
+ { context: { ...SYSTEM_CTX } }
5186
+ );
5187
+ const out = [];
5188
+ for (const start of started ?? []) {
5189
+ const events = await readRunJournal(engine, start.run_id);
5190
+ if (events.some((e) => e.kind === "run_done")) continue;
5191
+ const committed = chunkSetOf(events, "chunk_done");
5192
+ const compensated = chunkSetOf(events, "compensated");
5193
+ const outstanding = [...committed].filter((i) => !compensated.has(i));
5194
+ if (events.some((e) => e.kind === "run_failed") && outstanding.length === 0) continue;
5195
+ const unknown = [...chunkSetOf(events, "chunk_started")].filter((i) => !committed.has(i));
5196
+ let planId = start.run_id;
5197
+ try {
5198
+ planId = start.detail ? JSON.parse(start.detail).planId ?? start.run_id : start.run_id;
5199
+ } catch {
5200
+ }
5201
+ out.push({
5202
+ runId: start.run_id,
5203
+ planId,
5204
+ planHash: start.plan_hash ?? "",
5205
+ migrationId: start.migration_id,
5206
+ startedAt: start.created_at,
5207
+ committedChunks: [...committed].sort((a, b) => a - b),
5208
+ unknownChunks: unknown.sort((a, b) => a - b),
5209
+ compensatedChunks: [...compensated].sort((a, b) => a - b)
5210
+ });
5211
+ }
5212
+ return out;
5213
+ }
5214
+ async function loadPlan(engine, plan, chunkSize) {
5215
+ const rowsByStep = [];
5216
+ for (const step of plan.steps) rowsByStep.push(await step.load(engine) ?? []);
5217
+ const chunks = planChunks(plan, rowsByStep.map((r) => r.length), chunkSize ?? plan.chunkSize);
5218
+ return { chunks, planHash: hashMigrationPlan(plan, chunks), rowsByStep };
5219
+ }
5220
+ async function runMigrationJournal(engine, plan, options = {}) {
5221
+ const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
5222
+ if (!engineCanRollBack(engine)) {
5223
+ throw new MigrationJournalRefusal(
5224
+ "NOT_IMPLEMENTED",
5225
+ `Migration plan '${plan.id}' requires engine transaction support; this runtime cannot roll back. The journal's chunk_done markers would not mean "committed", so the run is refused rather than started.`
5226
+ );
5227
+ }
5228
+ const { chunks, planHash, rowsByStep } = await loadPlan(engine, plan, options.chunkSize);
5229
+ const resuming = Boolean(options.runId);
5230
+ const runId = options.runId ?? (0, import_node_crypto3.randomUUID)();
5231
+ let events = [];
5232
+ let seq = 0;
5233
+ let committed = /* @__PURE__ */ new Set();
5234
+ let compensated = /* @__PURE__ */ new Set();
5235
+ const attemptsByChunk = /* @__PURE__ */ new Map();
5236
+ if (resuming) {
5237
+ events = await readRunJournal(engine, runId);
5238
+ if (events.length === 0) {
5239
+ throw new MigrationJournalRefusal("NO_SUCH_RUN", `No journal rows for run '${runId}'.`);
5240
+ }
5241
+ const start = events.find((e) => e.kind === "run_started");
5242
+ if (start?.plan_hash && start.plan_hash !== planHash) {
5243
+ throw new MigrationJournalRefusal(
5244
+ "PLAN_CHANGED",
5245
+ `Refusing to resume run '${runId}': plan hash ${planHash} does not match the journal's ${start.plan_hash}. The chunk boundaries recorded in the journal describe a different plan.`
5246
+ );
5247
+ }
5248
+ if (events.some((e) => e.kind === "run_done")) {
5249
+ return {
5250
+ runId,
5251
+ status: "completed",
5252
+ chunksTotal: chunks.length,
5253
+ chunksCommitted: chunkSetOf(events, "chunk_done").size,
5254
+ chunksCompensated: chunkSetOf(events, "compensated").size,
5255
+ planHash
5256
+ };
5257
+ }
5258
+ seq = events.reduce((m, e) => Math.max(m, Number(e.seq) + 1), 0);
5259
+ committed = chunkSetOf(events, "chunk_done");
5260
+ compensated = chunkSetOf(events, "compensated");
5261
+ for (const e of events) {
5262
+ if (e.kind === "chunk_started" && typeof e.chunk_index === "number") {
5263
+ attemptsByChunk.set(e.chunk_index, (attemptsByChunk.get(e.chunk_index) ?? 0) + 1);
5264
+ }
5265
+ }
5266
+ }
5267
+ for (const step of plan.steps) {
5268
+ if (!step.preflight) continue;
5269
+ try {
5270
+ await step.preflight(engine);
5271
+ } catch (err) {
5272
+ throw new MigrationJournalRefusal(
5273
+ "PREFLIGHT_FAILED",
5274
+ `Migration plan '${plan.id}' refused: preflight for step '${step.name}' failed: ${errText(err)}`
5275
+ );
5276
+ }
5277
+ }
5278
+ if (plan.onCrash === "compensate") {
5279
+ const missing = plan.steps.filter((s) => !s.compensate).map((s) => s.name);
5280
+ if (missing.length > 0) {
5281
+ throw new MigrationJournalRefusal(
5282
+ "NOT_COMPENSABLE",
5283
+ `Migration plan '${plan.id}' declares onCrash: 'compensate' but step(s) ${missing.join(", ")} declare no compensate().`
5284
+ );
5285
+ }
5286
+ }
5287
+ const rowsOf = (c) => rowsByStep[c.stepIndex].slice(c.offset, c.offset + c.length);
5288
+ const next = () => seq++;
5289
+ if (!resuming) {
5290
+ await appendEvent(engine, {
5291
+ run_id: runId,
5292
+ seq: next(),
5293
+ kind: "run_started",
5294
+ plan_hash: planHash,
5295
+ migration_id: plan.migrationId,
5296
+ created_at: now(),
5297
+ detail: JSON.stringify({
5298
+ planId: plan.id,
5299
+ onCrash: plan.onCrash ?? "resume",
5300
+ chunks: chunks.map((c) => ({ i: c.index, step: c.stepName, offset: c.offset, length: c.length }))
5301
+ })
5302
+ });
5303
+ }
5304
+ if (resuming && plan.onCrash === "compensate") {
5305
+ return await unwind(engine, plan, {
5306
+ runId,
5307
+ planHash,
5308
+ chunks,
5309
+ rowsOf,
5310
+ next,
5311
+ now,
5312
+ committed,
5313
+ compensated,
5314
+ chunksTotal: chunks.length,
5315
+ cause: new Error(`run '${runId}' rediscovered after interruption; plan policy is compensate`)
5316
+ });
5317
+ }
5318
+ for (const chunk of chunks) {
5319
+ if (committed.has(chunk.index)) continue;
5320
+ const attempt = (attemptsByChunk.get(chunk.index) ?? 0) + 1;
5321
+ attemptsByChunk.set(chunk.index, attempt);
5322
+ const step = plan.steps[chunk.stepIndex];
5323
+ const rows = rowsOf(chunk);
5324
+ await appendEvent(engine, {
5325
+ run_id: runId,
5326
+ seq: next(),
5327
+ kind: "chunk_started",
5328
+ chunk_index: chunk.index,
5329
+ attempt,
5330
+ migration_id: plan.migrationId,
5331
+ created_at: now()
5332
+ });
5333
+ try {
5334
+ await engine.transaction(async (trxCtx) => {
5335
+ await step.forward(rows, { runId, chunkIndex: chunk.index, attempt, context: trxCtx }, engine);
5336
+ await appendEvent(
5337
+ engine,
5338
+ {
5339
+ run_id: runId,
5340
+ seq: next(),
5341
+ kind: "chunk_done",
5342
+ chunk_index: chunk.index,
5343
+ attempt,
5344
+ migration_id: plan.migrationId,
5345
+ created_at: now()
5346
+ },
5347
+ trxCtx
5348
+ );
5349
+ }, { ...SYSTEM_CTX });
5350
+ committed.add(chunk.index);
5351
+ } catch (err) {
5352
+ return await unwind(engine, plan, {
5353
+ runId,
5354
+ planHash,
5355
+ chunks,
5356
+ rowsOf,
5357
+ next,
5358
+ now,
5359
+ committed,
5360
+ compensated,
5361
+ chunksTotal: chunks.length,
5362
+ cause: err
5363
+ });
5364
+ }
5365
+ }
5366
+ await appendEvent(engine, {
5367
+ run_id: runId,
5368
+ seq: next(),
5369
+ kind: "run_done",
5370
+ migration_id: plan.migrationId,
5371
+ created_at: now()
5372
+ });
5373
+ return {
5374
+ runId,
5375
+ status: "completed",
5376
+ chunksTotal: chunks.length,
5377
+ chunksCommitted: committed.size,
5378
+ chunksCompensated: compensated.size,
5379
+ planHash
5380
+ };
5381
+ }
5382
+ async function unwind(engine, plan, a) {
5383
+ const order = [...a.committed].sort((x, y) => y - x);
5384
+ for (const index of order) {
5385
+ if (a.compensated.has(index)) continue;
5386
+ const chunk = a.chunks[index];
5387
+ const step = plan.steps[chunk.stepIndex];
5388
+ if (!step.compensate) {
5389
+ await appendEvent(engine, {
5390
+ run_id: a.runId,
5391
+ seq: a.next(),
5392
+ kind: "run_failed",
5393
+ chunk_index: index,
5394
+ migration_id: plan.migrationId,
5395
+ created_at: a.now(),
5396
+ detail: JSON.stringify({
5397
+ phase: "compensate",
5398
+ reason: "step declares no compensate()",
5399
+ step: step.name,
5400
+ cause: errText(a.cause)
5401
+ })
5402
+ });
5403
+ return {
5404
+ runId: a.runId,
5405
+ status: "failed",
5406
+ chunksTotal: a.chunksTotal,
5407
+ chunksCommitted: a.committed.size,
5408
+ chunksCompensated: a.compensated.size,
5409
+ planHash: a.planHash,
5410
+ error: a.cause
5411
+ };
5412
+ }
5413
+ const attempt = 1;
5414
+ try {
5415
+ await engine.transaction(async (trxCtx) => {
5416
+ await step.compensate(a.rowsOf(chunk), { runId: a.runId, chunkIndex: index, attempt, context: trxCtx }, engine);
5417
+ await appendEvent(
5418
+ engine,
5419
+ {
5420
+ run_id: a.runId,
5421
+ seq: a.next(),
5422
+ kind: "compensated",
5423
+ chunk_index: index,
5424
+ attempt,
5425
+ migration_id: plan.migrationId,
5426
+ created_at: a.now()
5427
+ },
5428
+ trxCtx
5429
+ );
5430
+ }, { ...SYSTEM_CTX });
5431
+ a.compensated.add(index);
5432
+ } catch (err) {
5433
+ await appendEvent(engine, {
5434
+ run_id: a.runId,
5435
+ seq: a.next(),
5436
+ kind: "run_failed",
5437
+ chunk_index: index,
5438
+ migration_id: plan.migrationId,
5439
+ created_at: a.now(),
5440
+ detail: JSON.stringify({
5441
+ phase: "compensate",
5442
+ step: step.name,
5443
+ error: errText(err),
5444
+ cause: errText(a.cause)
5445
+ })
5446
+ });
5447
+ return {
5448
+ runId: a.runId,
5449
+ status: "failed",
5450
+ chunksTotal: a.chunksTotal,
5451
+ chunksCommitted: a.committed.size,
5452
+ chunksCompensated: a.compensated.size,
5453
+ planHash: a.planHash,
5454
+ error: err
5455
+ };
5456
+ }
5457
+ }
5458
+ await appendEvent(engine, {
5459
+ run_id: a.runId,
5460
+ seq: a.next(),
5461
+ kind: "run_failed",
5462
+ migration_id: plan.migrationId,
5463
+ created_at: a.now(),
5464
+ detail: JSON.stringify({ phase: "forward", error: errText(a.cause), compensated: [...a.compensated].sort((x, y) => x - y) })
5465
+ });
5466
+ return {
5467
+ runId: a.runId,
5468
+ status: "compensated",
5469
+ chunksTotal: a.chunksTotal,
5470
+ chunksCommitted: a.committed.size,
5471
+ chunksCompensated: a.compensated.size,
5472
+ planHash: a.planHash,
5473
+ error: a.cause
5474
+ };
5475
+ }
5476
+ async function resumeMigrationJournal(engine, plan, runId, options = {}) {
5477
+ return runMigrationJournal(engine, plan, { ...options, runId });
5478
+ }
5479
+ function errText(err) {
5480
+ if (err instanceof Error) return err.message;
5481
+ try {
5482
+ return String(err);
5483
+ } catch {
5484
+ return "<unprintable error>";
5485
+ }
5486
+ }
5487
+
4945
5488
  // src/utils/filter-tokens.ts
4946
- var import_data = require("@objectstack/spec/data");
5489
+ var import_data2 = require("@objectstack/spec/data");
4947
5490
  var UnknownFilterTokenError = class extends Error {
4948
5491
  constructor(token, suggestion) {
4949
5492
  super(
@@ -5094,7 +5637,7 @@ function resolveFilterToken(token, ctx = {}) {
5094
5637
  }
5095
5638
  const period = resolvePeriodToken(token, today);
5096
5639
  if (period !== void 0) return period;
5097
- const param = (0, import_data.parseDateMacroParam)(token);
5640
+ const param = (0, import_data2.parseDateMacroParam)(token);
5098
5641
  if (param) {
5099
5642
  const sign = param.direction === "ago" ? -1 : 1;
5100
5643
  if (param.unit === "minute" || param.unit === "hour") {
@@ -5105,7 +5648,7 @@ function resolveFilterToken(token, ctx = {}) {
5105
5648
  return void 0;
5106
5649
  }
5107
5650
  function hasFilterToken(node) {
5108
- if (typeof node === "string") return (0, import_data.classifyFilterToken)(node) !== null;
5651
+ if (typeof node === "string") return (0, import_data2.classifyFilterToken)(node) !== null;
5109
5652
  if (Array.isArray(node)) return node.some(hasFilterToken);
5110
5653
  if (node && typeof node === "object" && !(node instanceof Date)) {
5111
5654
  return Object.values(node).some(hasFilterToken);
@@ -5118,7 +5661,7 @@ function resolveFilterTokens(filter, ctx = {}) {
5118
5661
  const pinned = { ...ctx, now: ctx.now ?? /* @__PURE__ */ new Date() };
5119
5662
  const walk = (node) => {
5120
5663
  if (typeof node === "string") {
5121
- const cls = (0, import_data.classifyFilterToken)(node);
5664
+ const cls = (0, import_data2.classifyFilterToken)(node);
5122
5665
  if (!cls) return node;
5123
5666
  if (cls.kind === "unknown") throw new UnknownFilterTokenError(cls.token, cls.suggestion);
5124
5667
  const resolved = resolveFilterToken(cls.token, pinned);
@@ -5388,7 +5931,7 @@ var PluginHealthMonitor = class {
5388
5931
  };
5389
5932
 
5390
5933
  // src/hot-reload.ts
5391
- var import_node_crypto3 = require("crypto");
5934
+ var import_node_crypto4 = require("crypto");
5392
5935
  var generateUUID = () => {
5393
5936
  if (typeof crypto !== "undefined" && crypto.randomUUID) {
5394
5937
  return crypto.randomUUID();
@@ -5483,7 +6026,7 @@ var PluginStateManager = class {
5483
6026
  */
5484
6027
  calculateChecksum(state) {
5485
6028
  const stateStr = JSON.stringify(state);
5486
- return (0, import_node_crypto3.createHash)("sha256").update(stateStr).digest("hex");
6029
+ return (0, import_node_crypto4.createHash)("sha256").update(stateStr).digest("hex");
5487
6030
  }
5488
6031
  /**
5489
6032
  * Shutdown state manager
@@ -6070,6 +6613,8 @@ var NamespaceResolver = class {
6070
6613
  DependencyResolver,
6071
6614
  HotReloadManager,
6072
6615
  LiteKernel,
6616
+ MigrationJournalRefusal,
6617
+ MigrationPlanRegistry,
6073
6618
  NamespaceResolver,
6074
6619
  ObjectKernel,
6075
6620
  ObjectKernelBase,
@@ -6092,6 +6637,7 @@ var NamespaceResolver = class {
6092
6637
  ServiceLifecycle,
6093
6638
  UnknownFilterTokenError,
6094
6639
  UnresolvedFilterTokenError,
6640
+ assertInitServiceRequirements,
6095
6641
  bucketKeyToCalendarRange,
6096
6642
  buildPermissionsFromGrants,
6097
6643
  bulkWrite,
@@ -6110,33 +6656,45 @@ var NamespaceResolver = class {
6110
6656
  deepMerge,
6111
6657
  defaultIsTransientError,
6112
6658
  derivePosture,
6659
+ describeInitOrderFault,
6660
+ engineCanRollBack,
6113
6661
  evaluateAuthGate,
6114
6662
  extractApiKey,
6115
6663
  filterTokenContextFrom,
6664
+ findInterruptedRuns,
6116
6665
  generateApiKey,
6117
6666
  generateEd25519KeyPair,
6118
6667
  getEnv,
6119
6668
  getMemoryUsage,
6120
6669
  hashApiKey,
6670
+ hashMigrationPlan,
6121
6671
  isAuthGateAllowlisted,
6122
6672
  isExpired,
6123
6673
  isGrantActive,
6124
6674
  isGrantExpired,
6125
6675
  isNode,
6676
+ nextUtcCalendarDay,
6126
6677
  parseScopes,
6127
6678
  parseSignature,
6679
+ planChunks,
6128
6680
  postureVisibleRows,
6129
6681
  readAuthoredTranslationLayer,
6682
+ readRunJournal,
6130
6683
  resolveApiKeyPrincipal,
6131
6684
  resolveAuthzContext,
6132
6685
  resolveFilterToken,
6133
6686
  resolveFilterTokens,
6134
6687
  resolveLocale,
6135
6688
  resolveLocalizationContext,
6689
+ resolvePluginOrder,
6136
6690
  resolveUserAuthzGrants,
6691
+ resumeMigrationJournal,
6692
+ runMigrationJournal,
6137
6693
  safeExit,
6138
6694
  shouldDenyAnonymous,
6139
6695
  signPayload,
6696
+ utcInstantMs,
6697
+ validateInitServiceContract,
6140
6698
  verifyPayload,
6141
6699
  verifyPlatformSignature,
6142
6700
  verifyPluginArtifact,