@sdux-vault/engine 0.13.0 → 0.26.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/fesm2022/sdux-vault-engine.mjs +688 -152
- package/fesm2022/sdux-vault-engine.mjs.map +1 -1
- package/package.json +6 -4
- package/types/sdux-vault-engine.d.ts +205 -25
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import { registerVersion, vaultDebug, ControllerVotes, ControllerMessageTypes, OperationTypes, vaultWarn, VaultController, defineControllerKey, ControllerTypes, DevMode, DecisionOutcomeTypes, vaultError, defineBehaviorKey, DEVTOOLS_LOGGING_KEY_CONSTANT, DEVTOOLS_AGGREGATE_KEY_CONSTANT, createVaultError, EventTypes, EventBoundaryTypes, setVaultLogLevel, isTestEnv, BEHAVIOR_META,
|
|
1
|
+
import { registerVersion, vaultDebug, ControllerVotes, ControllerMessageTypes, OperationTypes, vaultWarn, VaultController, defineControllerKey, ControllerTypes, DevMode, DecisionOutcomeTypes, vaultError, defineBehaviorKey, DEVTOOLS_LOGGING_KEY_CONSTANT, DEVTOOLS_AGGREGATE_KEY_CONSTANT, createVaultError, EventTypes, EventBoundaryTypes, setVaultLogLevel, isTestEnv, BEHAVIOR_META, BehaviorTypes, validateBehaviorKey, VAULT_STOP, isVaultNoop, VaultLicenseError, VaultBehavior, VaultPrivateErrorService, isHttpResourceRef, isFunction, isStateInputShape, isolateValue, isVaultClearState, VAULT_CLEAR_STATE, VAULT_NOOP, VAULT_CONTINUE, isVaultContinue, isUndefined, isDefined, isNullish, ResolveTypes, isPromise, VaultUsagePromiseError, CONTROLLER_META, validateControllerKey } from '@sdux-vault/shared';
|
|
2
2
|
import { of, map, catchError, forkJoin, Subject, ReplaySubject, isObservable, firstValueFrom, tap } from 'rxjs';
|
|
3
3
|
import { __decorate } from 'tslib';
|
|
4
4
|
import { filter } from 'rxjs/operators';
|
|
5
5
|
import { EventBus, initDevtoolsWidget } from '@sdux-vault/devtools';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
// FilePath: projects > engine > src > lib > utils > version > version.register.ts
|
|
9
|
-
// Updated: 2026-03-03 08:09
|
|
10
|
-
// Generated by pathcomment [tab] (see .vscode/typescript.code-snippets) or
|
|
11
|
-
// cmd+alt+j (see .vscode/keybindings.json)
|
|
12
|
-
// --- END AI MODEL FILE PATH ---
|
|
7
|
+
/** Package name used for version registration. */
|
|
13
8
|
const SDUX_PACKAGE = '@sdux-vault/engine';
|
|
14
|
-
|
|
9
|
+
/** Current package version registered with the Vault runtime. */
|
|
10
|
+
const SDUX_VERSION = '0.26.0';
|
|
15
11
|
registerVersion(SDUX_PACKAGE, SDUX_VERSION);
|
|
16
12
|
|
|
17
13
|
/**
|
|
@@ -24,11 +20,6 @@ var withCoreAbstainController_1;
|
|
|
24
20
|
* Core abstain controller responsible for participating in controller vote resolution.
|
|
25
21
|
* This controller observes incoming controller messages and emits a neutral vote without modifying pipeline flow.
|
|
26
22
|
*
|
|
27
|
-
* --RelatedStart--
|
|
28
|
-
* ControllerMessageShape
|
|
29
|
-
* ControllerVote
|
|
30
|
-
* ControllerVotes
|
|
31
|
-
* --RelatedEnd--
|
|
32
23
|
*/
|
|
33
24
|
let withCoreAbstainController = class withCoreAbstainController {
|
|
34
25
|
static { withCoreAbstainController_1 = this; }
|
|
@@ -57,7 +48,9 @@ let withCoreAbstainController = class withCoreAbstainController {
|
|
|
57
48
|
* Unique controller key identifying this instance.
|
|
58
49
|
*/
|
|
59
50
|
key;
|
|
51
|
+
/** Whether the FeatureCell has completed its initial pipeline cycle. */
|
|
60
52
|
#isInitialized = false;
|
|
53
|
+
/** Whether the initialization pipeline cycle failed. */
|
|
61
54
|
#initializationFailed = false;
|
|
62
55
|
/**
|
|
63
56
|
* Creates a new core conductor controller instance.
|
|
@@ -129,6 +122,11 @@ withCoreAbstainController = withCoreAbstainController_1 = __decorate([
|
|
|
129
122
|
], withCoreAbstainController);
|
|
130
123
|
|
|
131
124
|
var withCoreErrorController_1;
|
|
125
|
+
/**
|
|
126
|
+
* Core error controller responsible for handling failure messages in the
|
|
127
|
+
* controller vote resolution pipeline. This controller requests an abort
|
|
128
|
+
* on failure and abstains from all other message types.
|
|
129
|
+
*/
|
|
132
130
|
let withCoreErrorController = class withCoreErrorController {
|
|
133
131
|
static { withCoreErrorController_1 = this; }
|
|
134
132
|
controllerCtx;
|
|
@@ -156,6 +154,7 @@ let withCoreErrorController = class withCoreErrorController {
|
|
|
156
154
|
* Unique controller key identifying this instance.
|
|
157
155
|
*/
|
|
158
156
|
key;
|
|
157
|
+
/** Controller class context providing execution metadata. */
|
|
159
158
|
ctx;
|
|
160
159
|
/**
|
|
161
160
|
* Creates a new core conductor controller instance.
|
|
@@ -208,6 +207,7 @@ withCoreErrorController = withCoreErrorController_1 = __decorate([
|
|
|
208
207
|
})
|
|
209
208
|
], withCoreErrorController);
|
|
210
209
|
|
|
210
|
+
/** Enumeration of license lifecycle event identifiers. */
|
|
211
211
|
const LicenseEventTypes = {
|
|
212
212
|
RequireLicense: 'requireLicense',
|
|
213
213
|
ValidateLicense: 'validateLicense',
|
|
@@ -217,38 +217,79 @@ const LicenseEventTypes = {
|
|
|
217
217
|
DescribeControllers: 'describe-controllers'
|
|
218
218
|
};
|
|
219
219
|
|
|
220
|
+
/** Cached singleton instance of the licensing service. */
|
|
220
221
|
//eslint-disable-next-line
|
|
221
222
|
let singleton = null;
|
|
223
|
+
/**
|
|
224
|
+
* Initializes the global licensing service singleton.
|
|
225
|
+
*
|
|
226
|
+
* @param events$ - Subject used to emit licensing events.
|
|
227
|
+
* @param validation$ - Observable providing license validation results.
|
|
228
|
+
*/
|
|
222
229
|
function InitializeLicensingService(events$, validation$) {
|
|
223
230
|
if (singleton)
|
|
224
231
|
return;
|
|
225
232
|
singleton = new LicensingServiceInstance(events$, validation$);
|
|
226
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Returns the global licensing service singleton.
|
|
236
|
+
*
|
|
237
|
+
* @returns The initialized licensing service contract.
|
|
238
|
+
*/
|
|
227
239
|
function LicensingService() {
|
|
228
240
|
if (!singleton) {
|
|
229
241
|
throw new Error('[vault] LicensingService not initialized.');
|
|
230
242
|
}
|
|
231
243
|
return singleton;
|
|
232
244
|
}
|
|
245
|
+
/** Internal implementation of the licensing service contract. */
|
|
233
246
|
class LicensingServiceInstance {
|
|
234
247
|
events$;
|
|
235
248
|
validation$;
|
|
249
|
+
/**
|
|
250
|
+
* Creates a new licensing service backed by the supplied event streams.
|
|
251
|
+
*
|
|
252
|
+
* @param events$ - Subject used to emit licensing events.
|
|
253
|
+
* @param validation$ - Observable providing license validation results.
|
|
254
|
+
*/
|
|
236
255
|
constructor(events$, validation$) {
|
|
237
256
|
this.events$ = events$;
|
|
238
257
|
this.validation$ = validation$;
|
|
239
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Emits a feature description event for licensing inspection.
|
|
261
|
+
*
|
|
262
|
+
* @param event - Feature description event to emit.
|
|
263
|
+
*/
|
|
240
264
|
describeFeature(event) {
|
|
241
265
|
event.type = LicenseEventTypes.DescribeFeature;
|
|
242
266
|
this.events$.next(event);
|
|
243
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Emits a behavior description event for licensing inspection.
|
|
270
|
+
*
|
|
271
|
+
* @param event - Behavior description event to emit.
|
|
272
|
+
*/
|
|
244
273
|
describeBehaviors(event) {
|
|
245
274
|
event.type = LicenseEventTypes.DescribeBehaviors;
|
|
246
275
|
this.events$.next(event);
|
|
247
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* Emits a controller description event for licensing inspection.
|
|
279
|
+
*
|
|
280
|
+
* @param event - Controller description event to emit.
|
|
281
|
+
*/
|
|
248
282
|
describeControllers(event) {
|
|
249
283
|
event.type = LicenseEventTypes.DescribeControllers;
|
|
250
284
|
this.events$.next(event);
|
|
251
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* Requests a license token for a behavior or controller.
|
|
288
|
+
*
|
|
289
|
+
* @param featureCellKey - Key of the FeatureCell requesting the license.
|
|
290
|
+
* @param key - Key of the behavior or controller requiring a license.
|
|
291
|
+
* @returns The generated license token.
|
|
292
|
+
*/
|
|
252
293
|
requestLicense(featureCellKey, key) {
|
|
253
294
|
if (!key) {
|
|
254
295
|
throw new Error('[vault] Cannot register controller license without a key.');
|
|
@@ -262,6 +303,14 @@ class LicensingServiceInstance {
|
|
|
262
303
|
});
|
|
263
304
|
return licenseToken;
|
|
264
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Validates a previously requested license token.
|
|
308
|
+
*
|
|
309
|
+
* @param featureCellKey - Key of the FeatureCell owning the license.
|
|
310
|
+
* @param key - Key of the behavior or controller being validated.
|
|
311
|
+
* @param licenseToken - License token to validate.
|
|
312
|
+
* @param valid - Whether the license is approved.
|
|
313
|
+
*/
|
|
265
314
|
validateLicense(featureCellKey, key, licenseToken, valid) {
|
|
266
315
|
this.events$.next({
|
|
267
316
|
featureCellKey,
|
|
@@ -271,15 +320,22 @@ class LicensingServiceInstance {
|
|
|
271
320
|
valid
|
|
272
321
|
});
|
|
273
322
|
}
|
|
323
|
+
/** Generates a random alphanumeric license token. */
|
|
274
324
|
#generateLicenseToken() {
|
|
275
325
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
276
326
|
const randomPart = (length) => Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
|
|
277
327
|
return `${randomPart(5)}-${randomPart(5)}`;
|
|
278
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Returns an observable of license validation results.
|
|
331
|
+
*
|
|
332
|
+
* @returns Observable emitting validation outcomes.
|
|
333
|
+
*/
|
|
279
334
|
getLicenseValidation$() {
|
|
280
335
|
return this.validation$;
|
|
281
336
|
}
|
|
282
337
|
}
|
|
338
|
+
/** Resets the licensing service singleton for test isolation. */
|
|
283
339
|
const resetLicensingServiceForTests = () => {
|
|
284
340
|
if (!DevMode.active)
|
|
285
341
|
return;
|
|
@@ -287,17 +343,37 @@ const resetLicensingServiceForTests = () => {
|
|
|
287
343
|
};
|
|
288
344
|
|
|
289
345
|
var withCoreLicenseController_1;
|
|
346
|
+
/**
|
|
347
|
+
* Core license controller that gates pipeline execution based on the
|
|
348
|
+
* licensing service validation result. The controller subscribes to
|
|
349
|
+
* the license validation stream on construction and votes Deny until
|
|
350
|
+
* a license decision arrives, Abstain on approval, or Abort on denial.
|
|
351
|
+
*/
|
|
290
352
|
let withCoreLicenseController = class withCoreLicenseController {
|
|
291
353
|
static { withCoreLicenseController_1 = this; }
|
|
292
354
|
controllerCtx;
|
|
355
|
+
/** Static controller type assigned by the VaultController decorator. */
|
|
293
356
|
static type;
|
|
357
|
+
/** Static controller key assigned by the VaultController decorator. */
|
|
294
358
|
static key;
|
|
359
|
+
/** Static critical flag assigned by the VaultController decorator. */
|
|
295
360
|
static critical;
|
|
361
|
+
/** Instance controller type mirrored from the static declaration. */
|
|
296
362
|
type = withCoreLicenseController_1.type;
|
|
363
|
+
/** Instance critical flag mirrored from the static declaration. */
|
|
297
364
|
critical = withCoreLicenseController_1.critical;
|
|
365
|
+
/** Unique key identifying this controller instance. */
|
|
298
366
|
key;
|
|
367
|
+
/** Resolved license approval state: true, false, or null if pending. */
|
|
299
368
|
#licenseApproved = null;
|
|
369
|
+
/** Subscription to the licensing validation stream. */
|
|
300
370
|
#sub;
|
|
371
|
+
/**
|
|
372
|
+
* Subscribes to the licensing validation stream and signals approval or denial.
|
|
373
|
+
*
|
|
374
|
+
* @param key - Unique key for this controller instance.
|
|
375
|
+
* @param controllerCtx - Controller class context providing lifecycle hooks.
|
|
376
|
+
*/
|
|
301
377
|
constructor(key, controllerCtx) {
|
|
302
378
|
this.controllerCtx = controllerCtx;
|
|
303
379
|
this.key = key;
|
|
@@ -317,6 +393,12 @@ let withCoreLicenseController = class withCoreLicenseController {
|
|
|
317
393
|
}
|
|
318
394
|
});
|
|
319
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* Evaluates a controller message and returns a license-based vote.
|
|
398
|
+
*
|
|
399
|
+
* @param msg - The incoming controller message.
|
|
400
|
+
* @returns An observable emitting the controller vote.
|
|
401
|
+
*/
|
|
320
402
|
handleMessage(msg) {
|
|
321
403
|
vaultDebug(`${this.key} received "${msg.type}" for trace "${msg.traceId}".`);
|
|
322
404
|
switch (msg.type) {
|
|
@@ -334,10 +416,12 @@ let withCoreLicenseController = class withCoreLicenseController {
|
|
|
334
416
|
return of();
|
|
335
417
|
}
|
|
336
418
|
}
|
|
419
|
+
/** Unsubscribes from the licensing validation stream. */
|
|
337
420
|
destroy() {
|
|
338
421
|
this.#sub?.unsubscribe();
|
|
339
422
|
vaultWarn(`${this.key} - destroy unsubscribe`);
|
|
340
423
|
}
|
|
424
|
+
/** No-op reset handler for this controller. */
|
|
341
425
|
reset() {
|
|
342
426
|
vaultWarn(`${this.key} - reset noop`);
|
|
343
427
|
}
|
|
@@ -437,28 +521,21 @@ class Arbitrator {
|
|
|
437
521
|
}
|
|
438
522
|
}
|
|
439
523
|
|
|
440
|
-
|
|
441
|
-
// projects > engine > src > lib > types > monitor > vault-monitor-event-category.type.ts
|
|
524
|
+
/** Enumeration of monitor event category classifications. */
|
|
442
525
|
const VaultMonitorEventCategoryType = {
|
|
443
526
|
Boundary: 'boundary',
|
|
444
527
|
State: 'state',
|
|
445
528
|
Error: 'error'
|
|
446
529
|
};
|
|
447
530
|
|
|
448
|
-
|
|
449
|
-
// projects > engine > src > lib > types > monitor > vault-monitor-field-rule.type.ts
|
|
531
|
+
/** Enumeration of field emission rules for monitor event policies. */
|
|
450
532
|
const FieldRuleTypes = {
|
|
451
533
|
Never: 'never',
|
|
452
534
|
Optional: 'optional',
|
|
453
535
|
Required: 'required'
|
|
454
536
|
};
|
|
455
537
|
|
|
456
|
-
|
|
457
|
-
// FilePath: projects > engine > src > lib > monitor > event-policy > vault-monitor-category-field-policy.constant.ts
|
|
458
|
-
// Updated: 2026-03-06 16:29
|
|
459
|
-
// Generated by pathcomment [tab] (see .vscode/typescript.code-snippets) or
|
|
460
|
-
// cmd+alt+j (see .vscode/keybindings.json)
|
|
461
|
-
// --- END AI MODEL FILE PATH ---
|
|
538
|
+
/** Field emission rules indexed by monitor event category. */
|
|
462
539
|
const CATEGORY_FIELD_POLICY_CONSTANT = {
|
|
463
540
|
[VaultMonitorEventCategoryType.Boundary]: {
|
|
464
541
|
state: FieldRuleTypes.Never,
|
|
@@ -477,12 +554,7 @@ const CATEGORY_FIELD_POLICY_CONSTANT = {
|
|
|
477
554
|
}
|
|
478
555
|
};
|
|
479
556
|
|
|
480
|
-
|
|
481
|
-
// FilePath: projects > engine > src > lib > monitor > event-policy > vault-monitor-event-policy.constant.ts
|
|
482
|
-
// Updated: 2026-03-06 16:30
|
|
483
|
-
// Generated by pathcomment [tab] (see .vscode/typescript.code-snippets) or
|
|
484
|
-
// cmd+alt+j (see .vscode/keybindings.json)
|
|
485
|
-
// --- END AI MODEL FILE PATH ---
|
|
557
|
+
/** Maps normalized event names to their monitor event category classification. */
|
|
486
558
|
const EVENT_POLICY_CONSTANT = {
|
|
487
559
|
// ─────────────────────────────────────────────
|
|
488
560
|
// STATE
|
|
@@ -564,11 +636,6 @@ const EVENT_POLICY_CONSTANT = {
|
|
|
564
636
|
let instance$2 = null;
|
|
565
637
|
/**
|
|
566
638
|
* Returns the global VaultMonitorSharedState singleton instance.
|
|
567
|
-
* This function ensures a single shared state instance is created and reused across the runtime.
|
|
568
|
-
*
|
|
569
|
-
* --RelatedStart--
|
|
570
|
-
* VaultMonitorSharedStateInstance
|
|
571
|
-
* --RelatedEnd--
|
|
572
639
|
*
|
|
573
640
|
* @returns The global VaultMonitorSharedState instance.
|
|
574
641
|
*/
|
|
@@ -578,6 +645,13 @@ function VaultMonitorSharedState() {
|
|
|
578
645
|
}
|
|
579
646
|
return instance$2;
|
|
580
647
|
}
|
|
648
|
+
/**
|
|
649
|
+
* Mutable shared state consumed by all VaultMonitor instances.
|
|
650
|
+
*
|
|
651
|
+
* This class holds the global insight override and the per-cell
|
|
652
|
+
* insight registry so that every monitor operates on the same
|
|
653
|
+
* canonical state.
|
|
654
|
+
*/
|
|
581
655
|
class VaultMonitorSharedStateInstance {
|
|
582
656
|
/**
|
|
583
657
|
* Optional global insight definition that overrides per-cell insight
|
|
@@ -589,18 +663,11 @@ class VaultMonitorSharedStateInstance {
|
|
|
589
663
|
* Registry of FeatureCell insight configurations keyed by cell ID.
|
|
590
664
|
*
|
|
591
665
|
* Each entry indicates whether insight is enabled and contains the
|
|
592
|
-
* resolved list of
|
|
593
|
-
* that FeatureCell.
|
|
666
|
+
* resolved list of InsightConfig values associated with that FeatureCell.
|
|
594
667
|
*/
|
|
595
668
|
cellRegistry = new Map();
|
|
596
669
|
}
|
|
597
670
|
|
|
598
|
-
// --- AI Model File Path (DO NOT DELETE) ---
|
|
599
|
-
// FilePath: projects > engine > src > lib > monitor > vault-monitor.helper.ts
|
|
600
|
-
// Updated: 2026-03-06 16:27
|
|
601
|
-
// Generated by pathcomment [tab] (see .vscode/typescript.code-snippets) or
|
|
602
|
-
// cmd+alt+j (see .vscode/keybindings.json)
|
|
603
|
-
// --- END AI MODEL FILE PATH ---
|
|
604
671
|
/**
|
|
605
672
|
* Abstract helper base class used by both `VaultMonitor` and
|
|
606
673
|
* `VaultQueueMonitor`.
|
|
@@ -679,9 +746,22 @@ class VaultMonitorHelper {
|
|
|
679
746
|
activateGlobalInsights(definition) {
|
|
680
747
|
this.globalInsightOverride = definition;
|
|
681
748
|
}
|
|
749
|
+
/**
|
|
750
|
+
* Determines whether the given cell key belongs to a Chrome DevTools channel.
|
|
751
|
+
*
|
|
752
|
+
* @param cell - The FeatureCell key to check.
|
|
753
|
+
* @returns True when the cell is a DevTools-internal channel.
|
|
754
|
+
*/
|
|
682
755
|
isChromeDevTools(cell) {
|
|
683
756
|
return cell === DEVTOOLS_LOGGING_KEY_CONSTANT || cell === DEVTOOLS_AGGREGATE_KEY_CONSTANT;
|
|
684
757
|
}
|
|
758
|
+
/**
|
|
759
|
+
* Applies field-level emission policy to a pipeline event.
|
|
760
|
+
*
|
|
761
|
+
* @param event - The raw event to filter.
|
|
762
|
+
* @param insight - Optional insight configuration controlling field visibility.
|
|
763
|
+
* @returns The policy-filtered event.
|
|
764
|
+
*/
|
|
685
765
|
applyPolicy(event, insight) {
|
|
686
766
|
const category = EVENT_POLICY_CONSTANT[event.name]?.category ?? VaultMonitorEventCategoryType.Boundary;
|
|
687
767
|
const policy = CATEGORY_FIELD_POLICY_CONSTANT[category];
|
|
@@ -715,12 +795,6 @@ class VaultMonitorHelper {
|
|
|
715
795
|
}
|
|
716
796
|
}
|
|
717
797
|
|
|
718
|
-
// --- AI Model File Path (DO NOT DELETE) ---
|
|
719
|
-
// FilePath: projects > engine > src > lib > monitor > vault-monitor.service.ts
|
|
720
|
-
// Updated: 2026-03-03 09:10
|
|
721
|
-
// Generated by pathcomment [tab] (see .vscode/typescript.code-snippets) or
|
|
722
|
-
// cmd+alt+j (see .vscode/keybindings.json)
|
|
723
|
-
// --- END AI MODEL FILE PATH ---
|
|
724
798
|
/**
|
|
725
799
|
* Holds the singleton VaultMonitor instance.
|
|
726
800
|
*/
|
|
@@ -729,9 +803,6 @@ let instance$1 = null;
|
|
|
729
803
|
* Returns the global VaultMonitor singleton instance.
|
|
730
804
|
* This function ensures a single monitor instance is created and reused across the runtime.
|
|
731
805
|
*
|
|
732
|
-
* --RelatedStart--
|
|
733
|
-
* VaultMonitorContract
|
|
734
|
-
* --RelatedEnd--
|
|
735
806
|
*
|
|
736
807
|
* @returns The global VaultMonitor instance.
|
|
737
808
|
*/
|
|
@@ -745,9 +816,6 @@ function VaultMonitor() {
|
|
|
745
816
|
* Implements the VaultMonitorContract using shared helper functionality.
|
|
746
817
|
* This class provides the concrete singleton implementation backing the VaultMonitor entry point.
|
|
747
818
|
*
|
|
748
|
-
* --RelatedStart--
|
|
749
|
-
* VaultMonitorContract
|
|
750
|
-
* --RelatedEnd--
|
|
751
819
|
*/
|
|
752
820
|
class VaultMonitorInstance extends VaultMonitorHelper {
|
|
753
821
|
/**
|
|
@@ -769,10 +837,7 @@ class VaultMonitorInstance extends VaultMonitorHelper {
|
|
|
769
837
|
}
|
|
770
838
|
/**
|
|
771
839
|
* Maps a BehaviorContext into a StateSnapshot suitable for DevTools.
|
|
772
|
-
* This ensures each pipeline event includes a normalized representation
|
|
773
|
-
* of loading, value, error, and hasValue.
|
|
774
840
|
*
|
|
775
|
-
* @typeParam T - State value type.
|
|
776
841
|
* @param ctx - Immutable behavior context provided by the orchestrator.
|
|
777
842
|
* @returns A standardized StateSnapshot.
|
|
778
843
|
*/
|
|
@@ -1193,50 +1258,60 @@ class VaultMonitorInstance extends VaultMonitorHelper {
|
|
|
1193
1258
|
const name = 'warn';
|
|
1194
1259
|
this.#emitEventV2LifecycleNotification({ cell, behaviorKey, name, ctx, error: errorMessage });
|
|
1195
1260
|
}
|
|
1261
|
+
/** Prepends event type and boundary to the event name. */
|
|
1196
1262
|
#buildEventName(eventInput) {
|
|
1197
1263
|
eventInput.name = `${eventInput.type}:${eventInput.boundary}:${eventInput.name}`;
|
|
1198
1264
|
return eventInput;
|
|
1199
1265
|
}
|
|
1266
|
+
/** Emits a stage-start monitoring event. */
|
|
1200
1267
|
#emitEventV2StageStart(eventInput) {
|
|
1201
1268
|
eventInput.type = EventTypes.Stage;
|
|
1202
1269
|
eventInput.boundary = EventBoundaryTypes.Start;
|
|
1203
1270
|
this.#emitEventV2(this.#buildEventName(eventInput));
|
|
1204
1271
|
}
|
|
1272
|
+
/** Emits a stage-end monitoring event. */
|
|
1205
1273
|
#emitEventV2StageEnd(eventInput) {
|
|
1206
1274
|
eventInput.type = EventTypes.Stage;
|
|
1207
1275
|
eventInput.boundary = EventBoundaryTypes.End;
|
|
1208
1276
|
this.#emitEventV2(this.#buildEventName(eventInput));
|
|
1209
1277
|
}
|
|
1278
|
+
/** Emits a lifecycle-start monitoring event. */
|
|
1210
1279
|
#emitEventV2LifecycleStart(eventInput) {
|
|
1211
1280
|
eventInput.type = EventTypes.Lifecycle;
|
|
1212
1281
|
eventInput.boundary = EventBoundaryTypes.Start;
|
|
1213
1282
|
this.#emitEventV2(this.#buildEventName(eventInput));
|
|
1214
1283
|
}
|
|
1284
|
+
/** Emits a lifecycle-end monitoring event. */
|
|
1215
1285
|
#emitEventV2LifecycleEnd(eventInput) {
|
|
1216
1286
|
eventInput.type = EventTypes.Lifecycle;
|
|
1217
1287
|
eventInput.boundary = EventBoundaryTypes.End;
|
|
1218
1288
|
this.#emitEventV2(this.#buildEventName(eventInput));
|
|
1219
1289
|
}
|
|
1290
|
+
/** Emits a lifecycle-notification monitoring event. */
|
|
1220
1291
|
#emitEventV2LifecycleNotification(eventInput) {
|
|
1221
1292
|
eventInput.type = EventTypes.Lifecycle;
|
|
1222
1293
|
eventInput.boundary = EventBoundaryTypes.Notification;
|
|
1223
1294
|
this.#emitEventV2(this.#buildEventName(eventInput));
|
|
1224
1295
|
}
|
|
1296
|
+
/** Emits a conductor-notification monitoring event. */
|
|
1225
1297
|
#emitEventV2ConductorNotification(eventInput) {
|
|
1226
1298
|
eventInput.type = EventTypes.Conductor;
|
|
1227
1299
|
eventInput.boundary = EventBoundaryTypes.Notification;
|
|
1228
1300
|
this.#emitEventV2(this.#buildEventName(eventInput));
|
|
1229
1301
|
}
|
|
1302
|
+
/** Emits a controller-start monitoring event. */
|
|
1230
1303
|
#emitEventV2ControllerStart(eventInput) {
|
|
1231
1304
|
eventInput.type = EventTypes.Controller;
|
|
1232
1305
|
eventInput.boundary = EventBoundaryTypes.Start;
|
|
1233
1306
|
this.#emitEventV2(this.#buildEventName(eventInput));
|
|
1234
1307
|
}
|
|
1308
|
+
/** Emits a controller-end monitoring event. */
|
|
1235
1309
|
#emitEventV2ControllerEnd(eventInput) {
|
|
1236
1310
|
eventInput.type = EventTypes.Controller;
|
|
1237
1311
|
eventInput.boundary = EventBoundaryTypes.End;
|
|
1238
1312
|
this.#emitEventV2(this.#buildEventName(eventInput));
|
|
1239
1313
|
}
|
|
1314
|
+
/** Emits a controller-notification monitoring event. */
|
|
1240
1315
|
#emitEventV2ControllerNotification(eventInput) {
|
|
1241
1316
|
eventInput.type = EventTypes.Controller;
|
|
1242
1317
|
eventInput.boundary = EventBoundaryTypes.Notification;
|
|
@@ -1327,7 +1402,9 @@ const ControllerEventTypes = {
|
|
|
1327
1402
|
class DecisionEngine {
|
|
1328
1403
|
controllers;
|
|
1329
1404
|
events$;
|
|
1405
|
+
/** Arbitrator responsible for aggregating controller votes. */
|
|
1330
1406
|
#arbitrator = new Arbitrator();
|
|
1407
|
+
/** Vault monitor instance used to emit controller lifecycle events. */
|
|
1331
1408
|
#vaultMonitor = VaultMonitor();
|
|
1332
1409
|
/**
|
|
1333
1410
|
* Creates a new decision engine instance.
|
|
@@ -1472,12 +1549,7 @@ const PROTECTED_FEATURE_CELL_KEYS = new Set([
|
|
|
1472
1549
|
'hydrate'
|
|
1473
1550
|
]);
|
|
1474
1551
|
|
|
1475
|
-
|
|
1476
|
-
// FilePath: projects > engine > src > lib > types > vault-registration-license-status.type.ts
|
|
1477
|
-
// Updated: 2026-03-03 10:09
|
|
1478
|
-
// Generated by pathcomment [tab] (see .vscode/typescript.code-snippets) or
|
|
1479
|
-
// cmd+alt+j (see .vscode/keybindings.json)
|
|
1480
|
-
// --- END AI MODEL FILE PATH ---
|
|
1552
|
+
/** Enumeration of FeatureCell registration license validation statuses. */
|
|
1481
1553
|
const VaultRegistrationLicenseStatusTypes = {
|
|
1482
1554
|
NotRequired: 'not-required',
|
|
1483
1555
|
Pending: 'pending',
|
|
@@ -1551,6 +1623,10 @@ const KNOWN_VAULT_KEYS = new Set([
|
|
|
1551
1623
|
'SDUX::Controller::Policy::CoreError',
|
|
1552
1624
|
'SDUX::Controller::Policy::CoreLicense',
|
|
1553
1625
|
// ---------------------------------------------------------------------------
|
|
1626
|
+
// Core Controllers (libs/core)
|
|
1627
|
+
// ---------------------------------------------------------------------------
|
|
1628
|
+
'SDUX::Controller::Policy::TabSync',
|
|
1629
|
+
// ---------------------------------------------------------------------------
|
|
1554
1630
|
// Addon Controllers (libs/addons)
|
|
1555
1631
|
// ---------------------------------------------------------------------------
|
|
1556
1632
|
'SDUX::Controller::Policy::Delay',
|
|
@@ -1568,34 +1644,58 @@ const KNOWN_VAULT_KEYS = new Set([
|
|
|
1568
1644
|
*/
|
|
1569
1645
|
const VAULT_LICENSE_ID = 'sdux-vault';
|
|
1570
1646
|
|
|
1571
|
-
|
|
1572
|
-
// FilePath: projects > engine > src > lib > factories > vault > vault-core.function.ts
|
|
1573
|
-
// Updated: 2026-03-02 19:52
|
|
1574
|
-
// Generated by pathcomment [tab] (see .vscode/typescript.code-snippets) or
|
|
1575
|
-
// cmd+alt+j (see .vscode/keybindings.json)
|
|
1576
|
-
// --- END AI MODEL FILE PATH ---
|
|
1647
|
+
/** Behavior key used to identify the core license behavior during validation. */
|
|
1577
1648
|
const CORE_LICENSE_BEHAVIOR_KEY = 'SDUX::Behavior::Core::License';
|
|
1649
|
+
/** Cached singleton instance of the VaultCore runtime. */
|
|
1578
1650
|
let instance = null;
|
|
1651
|
+
/**
|
|
1652
|
+
* Initializes the global Vault runtime singleton with the supplied configuration.
|
|
1653
|
+
*
|
|
1654
|
+
* @param config - Optional Vault configuration overriding defaults.
|
|
1655
|
+
*/
|
|
1579
1656
|
function VaultCore(config = {}) {
|
|
1580
1657
|
if (!instance) {
|
|
1581
1658
|
instance = new VaultCoreInstance(config);
|
|
1582
1659
|
}
|
|
1583
1660
|
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Internal singleton managing Vault configuration, licensing, and the
|
|
1663
|
+
* FeatureCell registry. All mutable state is held in private fields and
|
|
1664
|
+
* exposed only through controlled public methods.
|
|
1665
|
+
*/
|
|
1584
1666
|
class VaultCoreInstance {
|
|
1667
|
+
/** Subscription to the licensing event bus. */
|
|
1585
1668
|
#licensingSub;
|
|
1669
|
+
/** Subscription to the validation result bus. */
|
|
1586
1670
|
#validaionSub;
|
|
1671
|
+
/** Map of license IDs to their decoded payloads. */
|
|
1587
1672
|
#licenseMap = new Map();
|
|
1673
|
+
/** Tracks FeatureCells that have reached a terminal license status. */
|
|
1588
1674
|
#terminalStatus = new Map();
|
|
1675
|
+
/** Whether AI-assist capabilities have been enabled. */
|
|
1589
1676
|
#aiAssistEnabled = false;
|
|
1677
|
+
/** Whether licensing checks are bypassed in dev mode. */
|
|
1590
1678
|
#bypassLicensing = false;
|
|
1679
|
+
/** Timeout duration in milliseconds for pending license evaluations. */
|
|
1591
1680
|
#licenseTimeoutMs;
|
|
1681
|
+
/** Active timeout handles for pending license evaluations keyed by FeatureCell. */
|
|
1592
1682
|
// eslint-disable-next-line
|
|
1593
1683
|
#licenseTimers = new Map();
|
|
1684
|
+
/** Subject emitting licensing events to the licensing service. */
|
|
1594
1685
|
#licensingBus$ = new Subject();
|
|
1686
|
+
/** ReplaySubject emitting license validation results. */
|
|
1595
1687
|
#validationBus$ = new ReplaySubject();
|
|
1688
|
+
/** Last published license status per FeatureCell key. */
|
|
1596
1689
|
#lastStatus = new Map();
|
|
1690
|
+
/** Frozen global Vault configuration. */
|
|
1597
1691
|
#vaultConfig;
|
|
1692
|
+
/** Registry of FeatureCell registration records keyed by cell key. */
|
|
1598
1693
|
#registry = new Map();
|
|
1694
|
+
/**
|
|
1695
|
+
* Creates the VaultCore singleton and initializes licensing infrastructure.
|
|
1696
|
+
*
|
|
1697
|
+
* @param config - Raw Vault configuration.
|
|
1698
|
+
*/
|
|
1599
1699
|
constructor(config) {
|
|
1600
1700
|
InitializeLicensingService(this.#licensingBus$, this.#validationBus$.asObservable());
|
|
1601
1701
|
this.setVaultConfig(config);
|
|
@@ -1630,6 +1730,7 @@ class VaultCoreInstance {
|
|
|
1630
1730
|
this.#licenseTimeoutMs = config.licenseTimeoutMs ?? 15_000;
|
|
1631
1731
|
this.#warnIfAccidentalDevMode();
|
|
1632
1732
|
}
|
|
1733
|
+
/** Resets all internal state for test isolation. */
|
|
1633
1734
|
resetForTesting() {
|
|
1634
1735
|
this.#licensingSub?.unsubscribe();
|
|
1635
1736
|
this.#licensingSub = undefined;
|
|
@@ -1640,40 +1741,87 @@ class VaultCoreInstance {
|
|
|
1640
1741
|
this.#lastStatus.clear();
|
|
1641
1742
|
this.#licenseMap.clear();
|
|
1642
1743
|
}
|
|
1744
|
+
/** Clears the FeatureCell registration registry. */
|
|
1643
1745
|
resetFeatureCellRegistry() {
|
|
1644
1746
|
this.#registry.clear();
|
|
1645
1747
|
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Registers a FeatureCell key in the runtime registry.
|
|
1750
|
+
*
|
|
1751
|
+
* @param key - Unique FeatureCell identifier.
|
|
1752
|
+
*/
|
|
1646
1753
|
registerCellRuntime(key) {
|
|
1647
1754
|
this.#ensureRecord(key);
|
|
1648
1755
|
}
|
|
1756
|
+
/**
|
|
1757
|
+
* Registers behavior entities for a FeatureCell.
|
|
1758
|
+
*
|
|
1759
|
+
* @param key - FeatureCell key.
|
|
1760
|
+
* @param behaviors - Array of behavior entity descriptors.
|
|
1761
|
+
*/
|
|
1649
1762
|
registerBehaviors(key, behaviors) {
|
|
1650
1763
|
const record = this.#ensureRecord(key);
|
|
1651
1764
|
record.behaviors = this.#registerEntities(behaviors);
|
|
1652
1765
|
record.behaviorsRegistered = true;
|
|
1653
1766
|
}
|
|
1767
|
+
/**
|
|
1768
|
+
* Registers controller entities for a FeatureCell.
|
|
1769
|
+
*
|
|
1770
|
+
* @param key - FeatureCell key.
|
|
1771
|
+
* @param controllers - Array of controller entity descriptors.
|
|
1772
|
+
*/
|
|
1654
1773
|
registerControllers(key, controllers) {
|
|
1655
1774
|
const record = this.#ensureRecord(key);
|
|
1656
1775
|
record.controllers = this.#registerEntities(controllers);
|
|
1657
1776
|
record.controllersRegistered = true;
|
|
1658
1777
|
}
|
|
1778
|
+
/**
|
|
1779
|
+
* Registers fluent API callback counts for a FeatureCell.
|
|
1780
|
+
*
|
|
1781
|
+
* @param key - FeatureCell key.
|
|
1782
|
+
* @param summary - Object summarizing the count of each fluent API callback type.
|
|
1783
|
+
*/
|
|
1659
1784
|
registerFluentApis(key, summary) {
|
|
1660
1785
|
const record = this.#ensureRecord(key);
|
|
1661
1786
|
record.fluentApis = Object.freeze(summary);
|
|
1662
1787
|
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Retrieves the decoded license payload for a given license ID.
|
|
1790
|
+
*
|
|
1791
|
+
* @param licenseId - License identifier to look up.
|
|
1792
|
+
* @returns The decoded payload, or undefined if not found.
|
|
1793
|
+
*/
|
|
1663
1794
|
getLicensePayload(licenseId) {
|
|
1664
1795
|
return this.#licenseMap.get(licenseId);
|
|
1665
1796
|
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Returns whether licensing checks are bypassed.
|
|
1799
|
+
*
|
|
1800
|
+
* @returns True when bypass licensing is active.
|
|
1801
|
+
*/
|
|
1666
1802
|
isBypassLicensing() {
|
|
1667
1803
|
return this.#bypassLicensing;
|
|
1668
1804
|
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Checks whether a key belongs to the set of known Vault-internal keys.
|
|
1807
|
+
*
|
|
1808
|
+
* @param key - The key to check.
|
|
1809
|
+
* @returns True when the key is a recognized Vault key.
|
|
1810
|
+
*/
|
|
1669
1811
|
isAuthorizedKey(key) {
|
|
1670
1812
|
return KNOWN_VAULT_KEYS.has(key);
|
|
1671
1813
|
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Returns whether a Vault-level license has been registered.
|
|
1816
|
+
*
|
|
1817
|
+
* @returns True when a Vault license exists.
|
|
1818
|
+
*/
|
|
1672
1819
|
hasVaultLicense() {
|
|
1673
1820
|
return this.#licenseMap.has(VAULT_LICENSE_ID);
|
|
1674
1821
|
}
|
|
1675
1822
|
//#endregion
|
|
1676
1823
|
//#region private Methods
|
|
1824
|
+
/** Populates the license map from the supplied license configurations. */
|
|
1677
1825
|
#initializeLicenses(licenses) {
|
|
1678
1826
|
if (!licenses?.length)
|
|
1679
1827
|
return;
|
|
@@ -1683,6 +1831,7 @@ class VaultCoreInstance {
|
|
|
1683
1831
|
this.#licenseMap.set(license.licenseId, license.payload);
|
|
1684
1832
|
}
|
|
1685
1833
|
}
|
|
1834
|
+
/** Freezes and registers an array of entity descriptors into a keyed map. */
|
|
1686
1835
|
#registerEntities(entities) {
|
|
1687
1836
|
return new Map(entities.map((entity) => {
|
|
1688
1837
|
let needsLicense;
|
|
@@ -1704,6 +1853,7 @@ class VaultCoreInstance {
|
|
|
1704
1853
|
return [entity.key, Object.freeze(frozen)];
|
|
1705
1854
|
}));
|
|
1706
1855
|
}
|
|
1856
|
+
/** Subscribes to the licensing bus and routes events to the appropriate handlers. */
|
|
1707
1857
|
#subscribeToLicensingEvents() {
|
|
1708
1858
|
this.#licensingSub = this.#licensingBus$.subscribe((event) => {
|
|
1709
1859
|
switch (event.type) {
|
|
@@ -1737,6 +1887,7 @@ class VaultCoreInstance {
|
|
|
1737
1887
|
}
|
|
1738
1888
|
});
|
|
1739
1889
|
}
|
|
1890
|
+
/** Starts a timeout timer for pending licenses on a FeatureCell. */
|
|
1740
1891
|
#startLicenseTimeout(featureCellKey) {
|
|
1741
1892
|
if (!this.#licenseTimeoutMs)
|
|
1742
1893
|
return;
|
|
@@ -1748,6 +1899,7 @@ class VaultCoreInstance {
|
|
|
1748
1899
|
}, this.#licenseTimeoutMs);
|
|
1749
1900
|
this.#licenseTimers.set(featureCellKey, timer);
|
|
1750
1901
|
}
|
|
1902
|
+
/** Expires all pending licenses for a FeatureCell and publishes a denial. */
|
|
1751
1903
|
#expirePendingLicenses(featureCellKey) {
|
|
1752
1904
|
const record = this.#registry.get(featureCellKey);
|
|
1753
1905
|
if (!record)
|
|
@@ -1771,6 +1923,7 @@ class VaultCoreInstance {
|
|
|
1771
1923
|
this.#clearLicenseTimer(featureCellKey);
|
|
1772
1924
|
}
|
|
1773
1925
|
// Do not evaluate until both sides registered
|
|
1926
|
+
/** Evaluates and emits the aggregate license status for a FeatureCell. */
|
|
1774
1927
|
#emitLicenseStatus(featureCellKey) {
|
|
1775
1928
|
const record = this.#registry.get(featureCellKey);
|
|
1776
1929
|
if (!record)
|
|
@@ -1800,6 +1953,7 @@ class VaultCoreInstance {
|
|
|
1800
1953
|
// All valid
|
|
1801
1954
|
this.#publishStatus(featureCellKey, true);
|
|
1802
1955
|
}
|
|
1956
|
+
/** Clears the license timeout timer for a FeatureCell. */
|
|
1803
1957
|
#clearLicenseTimer(featureCellKey) {
|
|
1804
1958
|
const timer = this.#licenseTimers.get(featureCellKey);
|
|
1805
1959
|
if (timer) {
|
|
@@ -1807,6 +1961,7 @@ class VaultCoreInstance {
|
|
|
1807
1961
|
this.#licenseTimers.delete(featureCellKey);
|
|
1808
1962
|
}
|
|
1809
1963
|
}
|
|
1964
|
+
/** Publishes a terminal license validation result for a FeatureCell. */
|
|
1810
1965
|
#publishStatus(featureCellKey, approved) {
|
|
1811
1966
|
if (this.#terminalStatus.has(featureCellKey))
|
|
1812
1967
|
return;
|
|
@@ -1818,6 +1973,7 @@ class VaultCoreInstance {
|
|
|
1818
1973
|
approved
|
|
1819
1974
|
});
|
|
1820
1975
|
}
|
|
1976
|
+
/** Routes a license validation event to the appropriate entity maps. */
|
|
1821
1977
|
#handleLicenseValidation(event) {
|
|
1822
1978
|
const { featureCellKey, key, licenseToken, valid } = event;
|
|
1823
1979
|
if (this.#terminalStatus.has(event.featureCellKey))
|
|
@@ -1835,6 +1991,7 @@ class VaultCoreInstance {
|
|
|
1835
1991
|
this.#enableAiAssist();
|
|
1836
1992
|
}
|
|
1837
1993
|
}
|
|
1994
|
+
/** Applies a license validation result to a single entity within a map. */
|
|
1838
1995
|
#applyLicenseValidation(entities, key, licenseId, valid) {
|
|
1839
1996
|
if (!entities?.has(key))
|
|
1840
1997
|
return;
|
|
@@ -1857,6 +2014,7 @@ class VaultCoreInstance {
|
|
|
1857
2014
|
validLicense: valid ? VaultRegistrationLicenseStatusTypes.Valid : VaultRegistrationLicenseStatusTypes.Revoked
|
|
1858
2015
|
}));
|
|
1859
2016
|
}
|
|
2017
|
+
/** Records a license token against the matching entity in the registry. */
|
|
1860
2018
|
#recordLicenses(event) {
|
|
1861
2019
|
const { featureCellKey, key, licenseToken } = event;
|
|
1862
2020
|
const record = this.#registry.get(featureCellKey);
|
|
@@ -1869,6 +2027,7 @@ class VaultCoreInstance {
|
|
|
1869
2027
|
this.#recordLicense(record.behaviors, key, licenseToken);
|
|
1870
2028
|
this.#recordLicense(record.controllers, key, licenseToken);
|
|
1871
2029
|
}
|
|
2030
|
+
/** Writes a license ID to a single entity if it requires a license. */
|
|
1872
2031
|
#recordLicense(entities, key, licenseId) {
|
|
1873
2032
|
if (!entities?.has(key))
|
|
1874
2033
|
return;
|
|
@@ -1886,6 +2045,7 @@ class VaultCoreInstance {
|
|
|
1886
2045
|
licenseId
|
|
1887
2046
|
}));
|
|
1888
2047
|
}
|
|
2048
|
+
/** Emits a console warning when dev mode is active outside a test environment. */
|
|
1889
2049
|
#warnIfAccidentalDevMode() {
|
|
1890
2050
|
if (DevMode.active && !isTestEnv.active) {
|
|
1891
2051
|
// eslint-disable-next-line
|
|
@@ -1895,6 +2055,7 @@ class VaultCoreInstance {
|
|
|
1895
2055
|
`If this is intentional, you can safely ignore this message.`);
|
|
1896
2056
|
}
|
|
1897
2057
|
}
|
|
2058
|
+
/** Summarizes fluent API callback counts from a feature description event. */
|
|
1898
2059
|
#summarizeFluent(event) {
|
|
1899
2060
|
const fluent = event?.fluentApis ?? {};
|
|
1900
2061
|
return {
|
|
@@ -1906,12 +2067,14 @@ class VaultCoreInstance {
|
|
|
1906
2067
|
errorCallbacks: Array.isArray(fluent?.errorCallbacks) ? fluent.errorCallbacks.length : 0
|
|
1907
2068
|
};
|
|
1908
2069
|
}
|
|
2070
|
+
/** Returns or creates the registry record for a FeatureCell key. */
|
|
1909
2071
|
#ensureRecord(key) {
|
|
1910
2072
|
if (!this.#registry.has(key)) {
|
|
1911
2073
|
this.#registry.set(key, { key, behaviorsRegistered: false, controllersRegistered: false });
|
|
1912
2074
|
}
|
|
1913
2075
|
return this.#registry.get(key);
|
|
1914
2076
|
}
|
|
2077
|
+
/** Enables AI-assist capabilities when a core license is validated. */
|
|
1915
2078
|
#enableAiAssist() {
|
|
1916
2079
|
if (this.#aiAssistEnabled)
|
|
1917
2080
|
return;
|
|
@@ -1926,6 +2089,7 @@ class VaultCoreInstance {
|
|
|
1926
2089
|
globalThis.sdux.debugWidget.aiAssistEnabled = true;
|
|
1927
2090
|
document.dispatchEvent(new CustomEvent('sdux-license-resolved'));
|
|
1928
2091
|
}
|
|
2092
|
+
/** Injects the DevTools debug widget into the global scope. */
|
|
1929
2093
|
#injectDebugWidget() {
|
|
1930
2094
|
if (!DevMode.active)
|
|
1931
2095
|
return;
|
|
@@ -1941,10 +2105,21 @@ class VaultCoreInstance {
|
|
|
1941
2105
|
}
|
|
1942
2106
|
//#endregion
|
|
1943
2107
|
//#region Public Testing Method
|
|
2108
|
+
/**
|
|
2109
|
+
* Registers a settled callback for a FeatureCell.
|
|
2110
|
+
*
|
|
2111
|
+
* @param key - FeatureCell key.
|
|
2112
|
+
* @param fn - Async function invoked when the cell settles.
|
|
2113
|
+
*/
|
|
1944
2114
|
registerVaultSettled(key, fn) {
|
|
1945
2115
|
const record = this.#ensureRecord(key);
|
|
1946
2116
|
record.vaultSettled = fn;
|
|
1947
2117
|
}
|
|
2118
|
+
/**
|
|
2119
|
+
* Awaits the settled callback for a specific FeatureCell.
|
|
2120
|
+
*
|
|
2121
|
+
* @param key - FeatureCell key to await.
|
|
2122
|
+
*/
|
|
1948
2123
|
async awaitFeatureCellSettled(key) {
|
|
1949
2124
|
const record = this.#registry.get(key);
|
|
1950
2125
|
if (!record) {
|
|
@@ -1955,6 +2130,7 @@ class VaultCoreInstance {
|
|
|
1955
2130
|
await Promise.resolve(); // flush microtasks
|
|
1956
2131
|
}
|
|
1957
2132
|
}
|
|
2133
|
+
/** Awaits settled callbacks for all registered FeatureCells. */
|
|
1958
2134
|
async awaitAllSettled() {
|
|
1959
2135
|
for (const record of this.#registry.values()) {
|
|
1960
2136
|
if (typeof record.vaultSettled === 'function') {
|
|
@@ -1964,11 +2140,21 @@ class VaultCoreInstance {
|
|
|
1964
2140
|
await Promise.resolve();
|
|
1965
2141
|
}
|
|
1966
2142
|
// TESTING ONLY — do not use in production code
|
|
2143
|
+
/**
|
|
2144
|
+
* Returns a shallow clone of the internal registry for test inspection.
|
|
2145
|
+
*
|
|
2146
|
+
* @returns A read-only copy of the FeatureCell registry.
|
|
2147
|
+
*/
|
|
1967
2148
|
getRegistrySnapshot() {
|
|
1968
2149
|
// Return a shallow cloned map so tests cannot mutate internal state
|
|
1969
2150
|
return new Map(this.#registry);
|
|
1970
2151
|
}
|
|
1971
2152
|
}
|
|
2153
|
+
/**
|
|
2154
|
+
* Registers a FeatureCell entry in the global Vault registry.
|
|
2155
|
+
*
|
|
2156
|
+
* @param entry - Registration descriptor containing the FeatureCell key.
|
|
2157
|
+
*/
|
|
1972
2158
|
function registerFeatureCell(entry) {
|
|
1973
2159
|
if (!instance) {
|
|
1974
2160
|
throw new Error('[vault] Vault not initialized.');
|
|
@@ -1981,6 +2167,12 @@ function registerFeatureCell(entry) {
|
|
|
1981
2167
|
}
|
|
1982
2168
|
instance.registerCellRuntime(entry.key);
|
|
1983
2169
|
}
|
|
2170
|
+
/**
|
|
2171
|
+
* Retrieves the decoded license payload for a given license ID.
|
|
2172
|
+
*
|
|
2173
|
+
* @param licenseId - License identifier to look up.
|
|
2174
|
+
* @returns The decoded payload.
|
|
2175
|
+
*/
|
|
1984
2176
|
function getLicensePayload(licenseId) {
|
|
1985
2177
|
if (!instance) {
|
|
1986
2178
|
throw new Error('[vault] Vault not initialized.');
|
|
@@ -1990,17 +2182,29 @@ function getLicensePayload(licenseId) {
|
|
|
1990
2182
|
}
|
|
1991
2183
|
return instance.getLicensePayload(licenseId);
|
|
1992
2184
|
}
|
|
2185
|
+
/** Awaits settled callbacks for all registered FeatureCells. */
|
|
1993
2186
|
async function vaultAllSettled() {
|
|
1994
2187
|
if (!instance)
|
|
1995
2188
|
return;
|
|
1996
2189
|
await instance.awaitAllSettled();
|
|
1997
2190
|
}
|
|
2191
|
+
/**
|
|
2192
|
+
* Awaits the settled callback for a specific FeatureCell.
|
|
2193
|
+
*
|
|
2194
|
+
* @param key - FeatureCell key to await.
|
|
2195
|
+
*/
|
|
1998
2196
|
async function vaultSettled(key) {
|
|
1999
2197
|
if (!instance) {
|
|
2000
2198
|
throw new Error('[vault] Vault not initialized.');
|
|
2001
2199
|
}
|
|
2002
2200
|
await instance.awaitFeatureCellSettled(key);
|
|
2003
2201
|
}
|
|
2202
|
+
/**
|
|
2203
|
+
* Registers a settled callback for a FeatureCell in the global Vault.
|
|
2204
|
+
*
|
|
2205
|
+
* @param key - FeatureCell key.
|
|
2206
|
+
* @param vaultSettled - Async function invoked when the cell settles.
|
|
2207
|
+
*/
|
|
2004
2208
|
function registerVaultSettled(key, vaultSettled) {
|
|
2005
2209
|
if (!instance) {
|
|
2006
2210
|
throw new Error('[vault] Vault not initialized.');
|
|
@@ -2024,15 +2228,27 @@ function resetVaultForTests() {
|
|
|
2024
2228
|
instance?.resetForTesting();
|
|
2025
2229
|
instance = null;
|
|
2026
2230
|
}
|
|
2231
|
+
/** Clears the FeatureCell registration registry. */
|
|
2027
2232
|
function resetFeatureCellRegistry() {
|
|
2028
2233
|
instance?.resetFeatureCellRegistry();
|
|
2029
2234
|
}
|
|
2235
|
+
/**
|
|
2236
|
+
* Returns a read-only snapshot of the FeatureCell registry for test inspection.
|
|
2237
|
+
*
|
|
2238
|
+
* @returns A read-only copy of the registry.
|
|
2239
|
+
*/
|
|
2030
2240
|
function getVaultRegistryForTests() {
|
|
2031
2241
|
if (!instance) {
|
|
2032
2242
|
throw new Error('[vault] Vault not initialized.');
|
|
2033
2243
|
}
|
|
2034
2244
|
return instance.getRegistrySnapshot();
|
|
2035
2245
|
}
|
|
2246
|
+
/**
|
|
2247
|
+
* Checks whether a key is authorized by the Vault runtime.
|
|
2248
|
+
*
|
|
2249
|
+
* @param key - The key to check.
|
|
2250
|
+
* @returns True when the key is authorized or licensing is bypassed.
|
|
2251
|
+
*/
|
|
2036
2252
|
function isAuthorizedKey(key) {
|
|
2037
2253
|
if (!instance)
|
|
2038
2254
|
return false;
|
|
@@ -2040,11 +2256,21 @@ function isAuthorizedKey(key) {
|
|
|
2040
2256
|
return true;
|
|
2041
2257
|
return instance.isAuthorizedKey(key);
|
|
2042
2258
|
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Returns whether licensing checks are currently bypassed.
|
|
2261
|
+
*
|
|
2262
|
+
* @returns True when bypass licensing is active.
|
|
2263
|
+
*/
|
|
2043
2264
|
function isBypassLicensing() {
|
|
2044
2265
|
if (!instance)
|
|
2045
2266
|
return false;
|
|
2046
2267
|
return instance.isBypassLicensing();
|
|
2047
2268
|
}
|
|
2269
|
+
/**
|
|
2270
|
+
* Returns whether a Vault-level license has been registered.
|
|
2271
|
+
*
|
|
2272
|
+
* @returns True when a Vault license exists.
|
|
2273
|
+
*/
|
|
2048
2274
|
function hasVaultLicense() {
|
|
2049
2275
|
if (!instance)
|
|
2050
2276
|
return false;
|
|
@@ -2064,13 +2290,21 @@ class BehaviorInitializationClass {
|
|
|
2064
2290
|
#initialized = false;
|
|
2065
2291
|
/** FeatureCell identifier associated with this initializer. */
|
|
2066
2292
|
#cellKey;
|
|
2293
|
+
/** Live reference to the FeatureCell's mutable last-snapshot object. */
|
|
2294
|
+
#lastSnapshot;
|
|
2295
|
+
/** Subject used by the FeatureCell to emit state snapshots. */
|
|
2296
|
+
#state$;
|
|
2067
2297
|
/**
|
|
2068
2298
|
* Creates a new behavior initializer for a specific FeatureCell.
|
|
2069
2299
|
*
|
|
2070
2300
|
* @param cellKey - The unique FeatureCell key.
|
|
2301
|
+
* @param lastSnapshot - Live reference to the FeatureCell's mutable state snapshot.
|
|
2302
|
+
* @param state$ - Subject used to emit state snapshots.
|
|
2071
2303
|
*/
|
|
2072
|
-
constructor(cellKey) {
|
|
2304
|
+
constructor(cellKey, lastSnapshot, state$) {
|
|
2073
2305
|
this.#cellKey = cellKey;
|
|
2306
|
+
this.#lastSnapshot = lastSnapshot;
|
|
2307
|
+
this.#state$ = state$;
|
|
2074
2308
|
}
|
|
2075
2309
|
/**
|
|
2076
2310
|
* Instantiates and validates all behaviors declared for a FeatureCell.
|
|
@@ -2141,11 +2375,19 @@ class BehaviorInitializationClass {
|
|
|
2141
2375
|
}
|
|
2142
2376
|
let instance;
|
|
2143
2377
|
try {
|
|
2144
|
-
|
|
2378
|
+
let behaviorContract = {
|
|
2145
2379
|
featureCellKey: this.#cellKey,
|
|
2146
2380
|
behaviorConfig,
|
|
2147
2381
|
licensePayload
|
|
2148
|
-
}
|
|
2382
|
+
};
|
|
2383
|
+
if (meta.type === BehaviorTypes.TabSyncState) {
|
|
2384
|
+
behaviorContract = {
|
|
2385
|
+
...behaviorContract,
|
|
2386
|
+
lastSnapshot: this.#lastSnapshot,
|
|
2387
|
+
state$: this.#state$
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2390
|
+
instance = new BehaviorClass(behaviorKey, behaviorContract);
|
|
2149
2391
|
}
|
|
2150
2392
|
catch (error) {
|
|
2151
2393
|
isCritical = meta.critical;
|
|
@@ -2327,45 +2569,37 @@ const PublicKeys = {
|
|
|
2327
2569
|
*/
|
|
2328
2570
|
pro: `
|
|
2329
2571
|
-----BEGIN PUBLIC KEY-----
|
|
2572
|
+
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuXto+eRaFm9pObys/IEI ASwV1wgGvNGJsiyw/9hXsEd9mA76aQI1X9lpkZRKmBFovHdK2unPHFOPQM0k9vJo ieFMNXO9kmHn7UYZV98bDCcDTNURFHQ4SWlcAE/HEiNqcUb9LwotFbON7/mcthM8 QQQ4Lycdv+lm1uozQl8rl+i7FjfQzLaxJMuAkm9jFZK+ta6eoSy/lmXfhDem8RIo dE19aZWfY+LTXP9nn977XFah0z0S0D3NSvMv96gZsXTN2hTbFBl5dgDMAOW9R5OI wT6I+kGwrVqARXq2pTDHnZjqfO3a+rT4Lrb5/L58RjQ0EfA5puZ16EXGEUpOabqI KVT4Z/wv818P8eyat+LtTcy2G0zx/h0Fcz0QANzx3P9K7ezxeqdg4SsjkcNXRWZq PaJUhZHygN/Xuef9zfWwjuKobCBSdyyeXxF5XS0A0Y6NBmdhikyHc/YOY2iYupIt xiUvlHaq97B5wej3XcTmp4kmJUQyeQ8oD5Mj8Dmf69oa7vhI/ANNKWo9s8e7u7UX Dx74Eu3d8JBpACQ+Vvek6ZEGw+D0yCyLF6u/CaCw+cb2cBYAlM7jWZ5kpgsbQcWw YP2nbGV3OofcEspoEU704M4RW4v+nSRYrJbMEIJJ5Wuxk2/RuUgk/9uwgCHAvzXZ cmGomIf9dXZGoNhwT5uW1OECAwEAAQ==
|
|
2330
2573
|
-----END PUBLIC KEY-----
|
|
2331
2574
|
`,
|
|
2332
2575
|
/**
|
|
2333
2576
|
* the enterprise public key
|
|
2334
2577
|
*/
|
|
2335
2578
|
enterprise: `
|
|
2336
|
-
-----BEGIN PUBLIC KEY-----
|
|
2579
|
+
-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA6k4XHyV4WE6Bd/fizN4Q c3C37LtskNTJ1c3FVxcziygAFd+fotRfbLHctwtJJhuO6+Pv+c1SPjrPeJsWRw4M IN7QHcBQHPbQDW/Erd1XjA0OVNbxs3xLVjtgMuVcd2sKYPp4nJqIyz5WLMde7v1g 1k8knI+ISrym0h4GcjkNSaHK5QKKpK7n3dzOXrjo1P6h1uOVsGAHC/ErVMQNHrAu dKgY+SDVn87oPIrd2pJb5SotI6H8HzODM/CDsF58hk/eK4zApnrtDViVb1j3oNCk hdDOnN98VIgwcHzHYZOhPFM0TFwudpi57Yu/PJJztI7WbsjpxTyX3JPvwVeWJR+Z tt6NEQ5ZaoBghGgHGiuRbhKR5qoznwsMkfb2jUbpbgRTinmtEjFmpIYSnROCixjq W1neupzBDrNi+JfoVsTwiP8SbzxHXWksN0gLMfL235l1LDMS/IrI3RmhcRkhB/Pu vPuc+jhPkwpbXaM9vDkkPWK1dmRYHWo3atYCWoSdK2705woo19oT8Dxm9OXKT+nh HsdOI+k9asBCqe4kQHi3OJ4Raesa6bFWWxKFLeUNKSAt7clJKo7GhrovnHIIAbty gk7ULdwLIlpjwB5mVUBBCts5z9KznHo+pumNoeEA8FGqq374a+jEPOHWjsshA678 RDYeqeRbh2VNcy/OwlqH/MUCAwEAAQ==
|
|
2337
2580
|
-----END PUBLIC KEY-----
|
|
2338
2581
|
`,
|
|
2339
2582
|
/**
|
|
2340
2583
|
* the development public key
|
|
2341
2584
|
*/
|
|
2342
2585
|
development: `
|
|
2343
|
-
-----BEGIN PUBLIC KEY-----
|
|
2344
|
-
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6RDckQGY38NrlPq70YfI
|
|
2345
|
-
hIpBFmc9baNLS2HIeyKIU2m+Vvnt3AYcQJlQnzmO4ZLDOUkIP+VxAjaSCaUBeTOx
|
|
2346
|
-
KDYXAK8ds0xEur0cRV4Kn26jhLveLZVpfVzhkOjJ6I9ggLeh5JRt7kj3NPgKwnSm
|
|
2347
|
-
t5DxLh4JPyLfZdBDtW8tU2tCgCfEs2cArrVo/fg7Dei8FJ8w83PDhsKmX5UazSfj
|
|
2348
|
-
MC57gkTZ/cZmKBvVoIhnqaWVXcRRL+wZPArIsO801DRiMAhROyQYyC3EKpGLyUc1
|
|
2349
|
-
1VSKqdmQLIUE1pt151EnmStB+VOsMzCLZH4TyU7cd1Afs2Siqu947W2w4Qpm5+Y/
|
|
2350
|
-
FwIDAQAB
|
|
2586
|
+
-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsAFRjQalSEZkCDPrdBEf IMQpY7ujGf4pqjuFk86rkZENr7kJ00RjVJxuhcafgygdmxVAhKS+d1WtsSAw6c9m AawI+sSyhAClB+wrwfuCrxt/ZlLbNMiMH5SD1YvoRaHstkLpMGbWnbsLDI+cCpaL hGKk+5LoJLikhf9ipBkGX8VSAT0xTMk06iaYtEV85H9cMWtfx7seyBw2Mps/8S6f Rtp3tLlbNJIyh9+5XjtkTqYNRWJtFW1rv75K9GN9dPVXrEXUGojqeV13G+z2R3Sr QvmhESkyC1DviZBxaYnEhpWoijJQFJUQ1DGRi29ugktYzf36Otw9gyz9jGb5MLNE W+meR2LdnbTBy83QNtaS5lCzNJVo2ohwbD+djblNVegH/Dr0rK4IHEYSgjdxjErY 6xqykJpKJ025CTU4kyI3aaaYB+l2CQMAKVAh2y2rgGyJSJnMDTR44aBIZ8rtTu2r wazjBJ/RiMr0OOkfBqEQPKZ6qzSWtBDebvD0iUyRAP8SXSdDo1DcaJNamLLmjIxr 3KCcwgJt2oLcdZZHKG3WbjqmIdp7tq03O4gajKJHd5GmyLWtHXKqBwaijAx9aNqr qDPWj/Qg/8C9qpSBs7EUod3slV6UhO4yEnb7FdD/O0o8mRMU0rtJ0KQTarpEh2bY MKVsYxByiFeAjUJUWLSqIX8CAwEAAQ==
|
|
2351
2587
|
-----END PUBLIC KEY-----
|
|
2352
2588
|
`
|
|
2353
2589
|
};
|
|
2354
2590
|
|
|
2591
|
+
/** Whether license details have already been logged to the console. */
|
|
2592
|
+
let hasLoggedLicenseDetails = false;
|
|
2593
|
+
/** Resets the license details log flag for test isolation. */
|
|
2594
|
+
function resetLicenseDetailsLog() {
|
|
2595
|
+
if (!DevMode.active)
|
|
2596
|
+
return;
|
|
2597
|
+
hasLoggedLicenseDetails = false;
|
|
2598
|
+
}
|
|
2355
2599
|
/**
|
|
2356
|
-
* Static license verification utility
|
|
2357
|
-
* against tier-specific public keys. Tokens use a dot-separated
|
|
2358
|
-
*
|
|
2359
|
-
*
|
|
2360
|
-
* The verification process:
|
|
2361
|
-
* - Splits the token on `.` to extract the encoded payload and signature
|
|
2362
|
-
* - Decodes the payload from base64 to determine the license tier
|
|
2363
|
-
* - Rejects tokens with missing or unrecognized tier
|
|
2364
|
-
* - Loads the appropriate PEM-encoded public key for the tier
|
|
2365
|
-
* - Uses `crypto.subtle.verify()` (RSA-SHA256) to validate the signature
|
|
2366
|
-
* against the raw base64-encoded payload string
|
|
2367
|
-
*
|
|
2368
|
-
* Verification failures return `false` rather than throwing.
|
|
2600
|
+
* Static license verification utility that validates signed license tokens
|
|
2601
|
+
* against tier-specific RSA public keys. Tokens use a dot-separated
|
|
2602
|
+
* base64(payload).base64(signature) format verified via crypto.subtle.
|
|
2369
2603
|
*/
|
|
2370
2604
|
const VerifyLicenseToken = {
|
|
2371
2605
|
/**
|
|
@@ -2392,6 +2626,11 @@ const VerifyLicenseToken = {
|
|
|
2392
2626
|
if (!licenseType) {
|
|
2393
2627
|
return false;
|
|
2394
2628
|
}
|
|
2629
|
+
if (!DevMode.active && licenseType === 'development') {
|
|
2630
|
+
// eslint-disable-next-line
|
|
2631
|
+
console.error('[sdux-vault] Development license token rejected in production environment.');
|
|
2632
|
+
return false;
|
|
2633
|
+
}
|
|
2395
2634
|
const keyPem = PublicKeys[licenseType];
|
|
2396
2635
|
if (!keyPem)
|
|
2397
2636
|
return false;
|
|
@@ -2400,9 +2639,45 @@ const VerifyLicenseToken = {
|
|
|
2400
2639
|
name: 'RSASSA-PKCS1-v1_5',
|
|
2401
2640
|
hash: 'SHA-256'
|
|
2402
2641
|
}, key, signature, new TextEncoder().encode(encodedPayload));
|
|
2642
|
+
if (!hasLoggedLicenseDetails) {
|
|
2643
|
+
hasLoggedLicenseDetails = true;
|
|
2644
|
+
if (verified) {
|
|
2645
|
+
// eslint-disable-next-line
|
|
2646
|
+
console.info(`[sdux-vault] License verified — organization: "${payload.organization}", tier: "${payload.licenseType}"`);
|
|
2647
|
+
}
|
|
2648
|
+
else {
|
|
2649
|
+
// eslint-disable-next-line
|
|
2650
|
+
console.warn(`[sdux-vault] License signature invalid — organization: "${payload.organization}", tier: "${payload.licenseType}"`);
|
|
2651
|
+
}
|
|
2652
|
+
// eslint-disable-next-line
|
|
2653
|
+
console.info('[sdux-vault] License organization:', payload.organization);
|
|
2654
|
+
// eslint-disable-next-line
|
|
2655
|
+
console.info('[sdux-vault] License domain:', payload.domain);
|
|
2656
|
+
// eslint-disable-next-line
|
|
2657
|
+
console.info('[sdux-vault] License type:', payload.licenseType);
|
|
2658
|
+
// eslint-disable-next-line
|
|
2659
|
+
console.info('[sdux-vault] License issuedAt:', formatLicenseDate(payload.issuedAt));
|
|
2660
|
+
// eslint-disable-next-line
|
|
2661
|
+
console.info('[sdux-vault] License expires:', formatLicenseDate(payload.expires));
|
|
2662
|
+
}
|
|
2663
|
+
if (verified && payload.licenseType === 'enterprise' && typeof payload.expires === 'number') {
|
|
2664
|
+
const msUntilExpiry = payload.expires - Date.now();
|
|
2665
|
+
if (msUntilExpiry < 0) {
|
|
2666
|
+
// eslint-disable-next-line
|
|
2667
|
+
console.error(`[sdux-vault] Enterprise license expired — organization: "${payload.organization}", expired: ${formatLicenseDate(payload.expires)}`);
|
|
2668
|
+
return false;
|
|
2669
|
+
}
|
|
2670
|
+
const fifteenDaysMs = 15 * 24 * 60 * 60 * 1000;
|
|
2671
|
+
if (msUntilExpiry <= fifteenDaysMs) {
|
|
2672
|
+
// eslint-disable-next-line
|
|
2673
|
+
console.warn(`[sdux-vault] Enterprise license expiring soon — organization: "${payload.organization}", expires: ${formatLicenseDate(payload.expires)}`);
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2403
2676
|
return verified;
|
|
2404
2677
|
}
|
|
2405
|
-
catch {
|
|
2678
|
+
catch (error) {
|
|
2679
|
+
// eslint-disable-next-line
|
|
2680
|
+
console.error('[sdux-vault] License token verification failed:', error);
|
|
2406
2681
|
return false;
|
|
2407
2682
|
}
|
|
2408
2683
|
}
|
|
@@ -2445,18 +2720,69 @@ function str2ab(str) {
|
|
|
2445
2720
|
arr[i] = str.charCodeAt(i);
|
|
2446
2721
|
return buf;
|
|
2447
2722
|
}
|
|
2723
|
+
/**
|
|
2724
|
+
* Formats a license date value for display. Numeric timestamps are
|
|
2725
|
+
* formatted as locale-aware date strings; string values such as
|
|
2726
|
+
* `'forever'` are returned as-is.
|
|
2727
|
+
*
|
|
2728
|
+
* @param value - A Unix-epoch millisecond timestamp or a string literal.
|
|
2729
|
+
* @returns A human-readable date string.
|
|
2730
|
+
*/
|
|
2731
|
+
function formatLicenseDate(value) {
|
|
2732
|
+
if (typeof value === 'string')
|
|
2733
|
+
return value;
|
|
2734
|
+
return new Intl.DateTimeFormat('en-US', {
|
|
2735
|
+
month: '2-digit',
|
|
2736
|
+
day: '2-digit',
|
|
2737
|
+
year: 'numeric'
|
|
2738
|
+
}).format(new Date(value));
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
/**
|
|
2742
|
+
* Verifies a signed license payload string.
|
|
2743
|
+
*
|
|
2744
|
+
* @param licensePayload - The signed license token to verify.
|
|
2745
|
+
* @returns A promise resolving to true if the license is valid.
|
|
2746
|
+
*/
|
|
2747
|
+
async function verifyLicensePayload(licensePayload) {
|
|
2748
|
+
try {
|
|
2749
|
+
if (!licensePayload) {
|
|
2750
|
+
return false;
|
|
2751
|
+
}
|
|
2752
|
+
return await VerifyLicenseToken.verify(licensePayload);
|
|
2753
|
+
}
|
|
2754
|
+
catch {
|
|
2755
|
+
return false;
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2448
2758
|
|
|
2759
|
+
/**
|
|
2760
|
+
* Abstract base class providing license lifecycle management for behaviors
|
|
2761
|
+
* and controllers. Subclasses automatically request a license token during
|
|
2762
|
+
* construction when the static `needsLicense` flag is set.
|
|
2763
|
+
*/
|
|
2449
2764
|
class LicensingAbstract {
|
|
2765
|
+
/** Whether this class requires a valid license to operate. */
|
|
2450
2766
|
static needsLicense;
|
|
2767
|
+
/** Static key assigned by the VaultBehavior or VaultController decorator. */
|
|
2451
2768
|
static key;
|
|
2769
|
+
/** License token received from the licensing service. */
|
|
2452
2770
|
#licenseToken;
|
|
2771
|
+
/** Key of the FeatureCell this instance belongs to. */
|
|
2453
2772
|
#featureCellKey;
|
|
2773
|
+
/** Key identifying this behavior or controller. */
|
|
2454
2774
|
#key;
|
|
2775
|
+
/** Licensing service used to request and validate licenses. */
|
|
2455
2776
|
#licenseService;
|
|
2777
|
+
/**
|
|
2778
|
+
* Creates a new licensing-aware instance and requests a license if required.
|
|
2779
|
+
*
|
|
2780
|
+
* @param ctx - Context providing the FeatureCell key and optional license payload.
|
|
2781
|
+
*/
|
|
2456
2782
|
constructor(ctx) {
|
|
2457
2783
|
const ctor = this.constructor;
|
|
2458
2784
|
if (typeof ctor.key !== 'string' || !ctor.key.trim()) {
|
|
2459
|
-
throw new VaultLicenseError(`LicensingClass requires a static "key". Did you forget @VaultBehavior?`);
|
|
2785
|
+
throw new VaultLicenseError(`LicensingClass requires a static "key". Did you forget @VaultBehavior or @VaultController?`);
|
|
2460
2786
|
}
|
|
2461
2787
|
this.#licenseService = LicensingService();
|
|
2462
2788
|
this.#key = ctor.key;
|
|
@@ -2465,9 +2791,15 @@ class LicensingAbstract {
|
|
|
2465
2791
|
this.#requestLicense();
|
|
2466
2792
|
}
|
|
2467
2793
|
}
|
|
2794
|
+
/** Requests a license token from the licensing service. */
|
|
2468
2795
|
#requestLicense() {
|
|
2469
2796
|
this.#licenseToken = this.#licenseService.requestLicense(this.#featureCellKey, this.#key);
|
|
2470
2797
|
}
|
|
2798
|
+
/**
|
|
2799
|
+
* Validates the previously requested license token.
|
|
2800
|
+
*
|
|
2801
|
+
* @param valid - Whether the license should be marked as approved.
|
|
2802
|
+
*/
|
|
2471
2803
|
validateLicense(valid) {
|
|
2472
2804
|
if (!this.#licenseToken) {
|
|
2473
2805
|
throw new VaultLicenseError(`validateLicense() called but no license was requested for "${this.#featureCellKey}" and "${this.#key}".`);
|
|
@@ -2476,38 +2808,45 @@ class LicensingAbstract {
|
|
|
2476
2808
|
}
|
|
2477
2809
|
}
|
|
2478
2810
|
|
|
2811
|
+
/**
|
|
2812
|
+
* Core license behavior that validates the vault license payload on
|
|
2813
|
+
* construction. The behavior extends LicensingAbstract to integrate
|
|
2814
|
+
* with the licensing service and gates pipeline execution based on
|
|
2815
|
+
* the asynchronous verification result.
|
|
2816
|
+
*/
|
|
2479
2817
|
let withCoreLicenseBehavior = class withCoreLicenseBehavior extends LicensingAbstract {
|
|
2480
2818
|
behaviorCtx;
|
|
2819
|
+
/** Static behavior type assigned by the VaultBehavior decorator. */
|
|
2481
2820
|
static type;
|
|
2821
|
+
/** Static behavior key assigned by the VaultBehavior decorator. */
|
|
2482
2822
|
static key;
|
|
2823
|
+
/** Static critical flag assigned by the VaultBehavior decorator. */
|
|
2483
2824
|
static critical;
|
|
2825
|
+
/** Static license requirement flag assigned by the VaultBehavior decorator. */
|
|
2484
2826
|
static needsLicense;
|
|
2827
|
+
/** Instance behavior type for core license evaluation. */
|
|
2485
2828
|
type = BehaviorTypes.CoreLicense;
|
|
2829
|
+
/** Instance critical flag indicating this behavior is required. */
|
|
2486
2830
|
critical = true;
|
|
2831
|
+
/** Unique key identifying this behavior instance. */
|
|
2487
2832
|
key;
|
|
2833
|
+
/**
|
|
2834
|
+
* Verifies the license payload asynchronously and reports the result.
|
|
2835
|
+
*
|
|
2836
|
+
* @param key - Unique key for this behavior instance.
|
|
2837
|
+
* @param behaviorCtx - Behavior class context providing the license payload.
|
|
2838
|
+
*/
|
|
2488
2839
|
constructor(key, behaviorCtx) {
|
|
2489
2840
|
super(behaviorCtx);
|
|
2490
2841
|
this.behaviorCtx = behaviorCtx;
|
|
2491
2842
|
this.key = key;
|
|
2492
|
-
this
|
|
2493
|
-
}
|
|
2494
|
-
async #performLicenseValidation() {
|
|
2495
|
-
try {
|
|
2496
|
-
const payload = this.behaviorCtx.licensePayload;
|
|
2497
|
-
if (!payload?.token) {
|
|
2498
|
-
this.validateLicense(false);
|
|
2499
|
-
return;
|
|
2500
|
-
}
|
|
2501
|
-
const isValid = await VerifyLicenseToken.verify(payload.token);
|
|
2502
|
-
this.validateLicense(isValid);
|
|
2503
|
-
}
|
|
2504
|
-
catch {
|
|
2505
|
-
this.validateLicense(false);
|
|
2506
|
-
}
|
|
2843
|
+
verifyLicensePayload(this.behaviorCtx.licensePayload).then((valid) => this.validateLicense(valid));
|
|
2507
2844
|
}
|
|
2845
|
+
/** No-op destroy handler for this behavior. */
|
|
2508
2846
|
destroy() {
|
|
2509
2847
|
vaultWarn(`${this.key} - destroy noop`);
|
|
2510
2848
|
}
|
|
2849
|
+
/** No-op reset handler for this behavior. */
|
|
2511
2850
|
reset() {
|
|
2512
2851
|
vaultWarn(`${this.key} - reset noop`);
|
|
2513
2852
|
}
|
|
@@ -2522,32 +2861,56 @@ withCoreLicenseBehavior = __decorate([
|
|
|
2522
2861
|
})
|
|
2523
2862
|
], withCoreLicenseBehavior);
|
|
2524
2863
|
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2864
|
+
/**
|
|
2865
|
+
* Abstract base class that executes the FeatureCell behavior pipeline in
|
|
2866
|
+
* a deterministic stage order. The Orchestrator resolves incoming state,
|
|
2867
|
+
* runs operators, filters, reducers, tap callbacks, and persistence
|
|
2868
|
+
* stages before committing the final state snapshot.
|
|
2869
|
+
*/
|
|
2531
2870
|
class Orchestrator {
|
|
2871
|
+
/** Registered after-tap callback functions executed post-reduce. */
|
|
2532
2872
|
#afterTapCallbacks;
|
|
2873
|
+
/** Registered before-tap callback functions executed pre-reduce. */
|
|
2533
2874
|
#beforeTapCallbacks;
|
|
2875
|
+
/** Full set of instantiated behavior instances for this FeatureCell. */
|
|
2534
2876
|
#behaviors;
|
|
2877
|
+
/** Subset of behaviors eligible for stage-based pipeline execution. */
|
|
2535
2878
|
#stageBehaviors;
|
|
2879
|
+
/** Unique key identifying the FeatureCell that owns this orchestrator. */
|
|
2536
2880
|
cellKey;
|
|
2881
|
+
/** Decision engine used for controller vote evaluation. */
|
|
2537
2882
|
decisionEngine;
|
|
2883
|
+
/** Core error behavior responsible for normalizing pipeline errors. */
|
|
2538
2884
|
#coreErrorBehavior;
|
|
2885
|
+
/** Error callback behavior for dispatching errors to user callbacks. */
|
|
2539
2886
|
#coreErrorCallbackBehavior;
|
|
2887
|
+
/** Emit-state callback behavior for notifying state change listeners. */
|
|
2540
2888
|
#emitStateCallbackBehavior;
|
|
2889
|
+
/** User-provided emit-state callback functions. */
|
|
2541
2890
|
#emitStateCallbacks;
|
|
2891
|
+
/** Core state behavior managing snapshot commits and signal emission. */
|
|
2542
2892
|
#coreStateBehavior;
|
|
2893
|
+
/** Error transform behaviors for custom error shaping. */
|
|
2543
2894
|
#errorTransformBehaviors;
|
|
2895
|
+
/** User-provided error callback functions. */
|
|
2544
2896
|
#errorCallbacks;
|
|
2897
|
+
/** Global error service for broadcasting errors across FeatureCells. */
|
|
2545
2898
|
privateErrorService = VaultPrivateErrorService();
|
|
2899
|
+
/** User-provided filter functions applied during the filter stage. */
|
|
2546
2900
|
#filterFunctions = [];
|
|
2901
|
+
/** Initial state value or deferred factory for this FeatureCell. */
|
|
2547
2902
|
#initialState;
|
|
2903
|
+
/** Merge behavior responsible for combining partial and current state. */
|
|
2548
2904
|
#mergeBehavior;
|
|
2905
|
+
/** User-provided reducer functions applied during the reduce stage. */
|
|
2549
2906
|
#reducerFunctions;
|
|
2907
|
+
/** Vault monitor instance for tracing pipeline lifecycle events. */
|
|
2550
2908
|
vaultMonitor = VaultMonitor();
|
|
2909
|
+
/**
|
|
2910
|
+
* Initializes the orchestrator with configuration callbacks and initial state.
|
|
2911
|
+
*
|
|
2912
|
+
* @param config - Orchestrator configuration containing behaviors and callbacks.
|
|
2913
|
+
*/
|
|
2551
2914
|
constructor(config) {
|
|
2552
2915
|
this.#afterTapCallbacks = config.afterTapCallbacks ?? [];
|
|
2553
2916
|
this.#beforeTapCallbacks = config.beforeTapCallbacks ?? [];
|
|
@@ -2558,20 +2921,25 @@ class Orchestrator {
|
|
|
2558
2921
|
this.#initialState = config.initialState;
|
|
2559
2922
|
this.#reducerFunctions = config.reducerCallbacks ?? [];
|
|
2560
2923
|
}
|
|
2924
|
+
/** Registers all behaviors from the orchestrator configuration. */
|
|
2561
2925
|
initializeOrchestrator(config) {
|
|
2562
2926
|
config.behaviors = config.behaviors ?? [];
|
|
2563
2927
|
this.#registerBehaviors(config);
|
|
2564
2928
|
}
|
|
2565
2929
|
//#region Protected Methods
|
|
2930
|
+
/** Loads the initial state and runs the first pipeline cycle. */
|
|
2566
2931
|
async initializeFeatureCell(ctx) {
|
|
2567
2932
|
await this.#loadInitialState(ctx);
|
|
2568
2933
|
}
|
|
2934
|
+
/** Invokes destroy on all registered behaviors. */
|
|
2569
2935
|
destroyBehaviors(ctx) {
|
|
2570
2936
|
this.#destroyBehaviors(ctx);
|
|
2571
2937
|
}
|
|
2938
|
+
/** Invokes reset on all registered behaviors. */
|
|
2572
2939
|
resetBehaviors(ctx) {
|
|
2573
2940
|
this.#resetBehaviors(ctx);
|
|
2574
2941
|
}
|
|
2942
|
+
/** Routes the pipeline context to the appropriate replace or merge orchestration flow. */
|
|
2575
2943
|
async orchestrate(ctx, options) {
|
|
2576
2944
|
// no controllers → pipeline behaves exactly as before
|
|
2577
2945
|
if (ctx.operation === OperationTypes.Replace) {
|
|
@@ -2582,6 +2950,11 @@ class Orchestrator {
|
|
|
2582
2950
|
}
|
|
2583
2951
|
return;
|
|
2584
2952
|
}
|
|
2953
|
+
/**
|
|
2954
|
+
* Builds a controller context from the current behavior context.
|
|
2955
|
+
*
|
|
2956
|
+
* @returns The controller context snapshot.
|
|
2957
|
+
*/
|
|
2585
2958
|
buildControllerCtx(ctx) {
|
|
2586
2959
|
return {
|
|
2587
2960
|
traceId: ctx.traceId,
|
|
@@ -2591,6 +2964,11 @@ class Orchestrator {
|
|
|
2591
2964
|
operation: ctx.operation
|
|
2592
2965
|
};
|
|
2593
2966
|
}
|
|
2967
|
+
/**
|
|
2968
|
+
* Normalizes an incoming state input into a consistent shape.
|
|
2969
|
+
*
|
|
2970
|
+
* @returns The normalized state input value.
|
|
2971
|
+
*/
|
|
2594
2972
|
normalizeIncoming(incoming) {
|
|
2595
2973
|
if (!incoming)
|
|
2596
2974
|
return null;
|
|
@@ -2604,6 +2982,7 @@ class Orchestrator {
|
|
|
2604
2982
|
value: incoming
|
|
2605
2983
|
};
|
|
2606
2984
|
}
|
|
2985
|
+
/** Notifies the core state behavior of a controller abort or deny outcome. */
|
|
2607
2986
|
controllerOutcomeNotification(type, ctx) {
|
|
2608
2987
|
switch (type) {
|
|
2609
2988
|
case DecisionOutcomeTypes.Abort: {
|
|
@@ -2616,6 +2995,11 @@ class Orchestrator {
|
|
|
2616
2995
|
}
|
|
2617
2996
|
}
|
|
2618
2997
|
}
|
|
2998
|
+
/**
|
|
2999
|
+
* Prepares the incoming value and handles noop and clear-state short-circuits.
|
|
3000
|
+
*
|
|
3001
|
+
* @returns The normalized incoming value or undefined for short-circuit paths.
|
|
3002
|
+
*/
|
|
2619
3003
|
prepareIncoming(ctx, incoming, operation) {
|
|
2620
3004
|
ctx = this.#prepIncomingForOrchestration(ctx, incoming, operation);
|
|
2621
3005
|
const normalizedIncoming = this.#coreStateBehavior.preparePipelineIncoming(ctx);
|
|
@@ -2635,12 +3019,14 @@ class Orchestrator {
|
|
|
2635
3019
|
}
|
|
2636
3020
|
//#endregion
|
|
2637
3021
|
//#region Private Methods
|
|
3022
|
+
/** Attaches incoming, resolve type, and operation to the behavior context. */
|
|
2638
3023
|
#prepIncomingForOrchestration(ctx, incoming, operation) {
|
|
2639
3024
|
ctx.incoming = this.normalizeIncoming(incoming);
|
|
2640
3025
|
ctx.resolveType = this.#getResolveType(incoming);
|
|
2641
3026
|
ctx.operation = operation;
|
|
2642
3027
|
return ctx;
|
|
2643
3028
|
}
|
|
3029
|
+
/** Ensures at most one merge behavior is registered and adds it to the list. */
|
|
2644
3030
|
#addDefaultMergeBehavior(behaviors, config) {
|
|
2645
3031
|
const mergeBehaviors = config.behaviors.filter((behaviorClass) => {
|
|
2646
3032
|
return behaviorClass.type === BehaviorTypes.Merge;
|
|
@@ -2658,6 +3044,7 @@ class Orchestrator {
|
|
|
2658
3044
|
// ---------------------------------------------------------------------------
|
|
2659
3045
|
// NORMALIZATION
|
|
2660
3046
|
// ---------------------------------------------------------------------------
|
|
3047
|
+
/** Assembles the default behavior set from configuration and internal overrides. */
|
|
2661
3048
|
#defineDefaultBehaviors(config) {
|
|
2662
3049
|
let defaultBehaviors = config.defaultBehaviors ?? [];
|
|
2663
3050
|
defaultBehaviors = this.#determineCoreCallbackErrorBehavior(defaultBehaviors, config);
|
|
@@ -2666,18 +3053,21 @@ class Orchestrator {
|
|
|
2666
3053
|
defaultBehaviors = this.#addCoreLicenseBehavior(defaultBehaviors);
|
|
2667
3054
|
return defaultBehaviors;
|
|
2668
3055
|
}
|
|
3056
|
+
/** Removes the error callback behavior when no error callbacks are configured. */
|
|
2669
3057
|
#determineCoreCallbackErrorBehavior(behaviors, config) {
|
|
2670
3058
|
if (config?.errorCallbacks?.length === 0) {
|
|
2671
3059
|
return behaviors.filter((behavior) => behavior.type !== BehaviorTypes.CoreErrorCallback);
|
|
2672
3060
|
}
|
|
2673
3061
|
return behaviors;
|
|
2674
3062
|
}
|
|
3063
|
+
/** Removes the emit-state callback behavior when no emit-state callbacks are configured. */
|
|
2675
3064
|
#addCoreEmitStateCallbackBehavior(behaviors, config) {
|
|
2676
3065
|
if (config?.emitStateCallbacks?.length === 0) {
|
|
2677
3066
|
return behaviors.filter((behavior) => behavior.type !== BehaviorTypes.CoreEmitState);
|
|
2678
3067
|
}
|
|
2679
3068
|
return behaviors;
|
|
2680
3069
|
}
|
|
3070
|
+
/** Conditionally adds the core license behavior when a vault license is active. */
|
|
2681
3071
|
#addCoreLicenseBehavior(behaviors) {
|
|
2682
3072
|
behaviors = behaviors?.filter((behavior) => behavior.type !== BehaviorTypes.CoreLicense);
|
|
2683
3073
|
if (hasVaultLicense()) {
|
|
@@ -2685,6 +3075,7 @@ class Orchestrator {
|
|
|
2685
3075
|
}
|
|
2686
3076
|
return behaviors;
|
|
2687
3077
|
}
|
|
3078
|
+
/** Reports behavior metadata to the licensing service for license validation. */
|
|
2688
3079
|
#registerBehaviorsWithVault(behaviors) {
|
|
2689
3080
|
// eslint-disable-next-line
|
|
2690
3081
|
const behaviorMetadata = behaviors.map((behavior) => {
|
|
@@ -2701,6 +3092,7 @@ class Orchestrator {
|
|
|
2701
3092
|
behaviors: behaviorMetadata
|
|
2702
3093
|
});
|
|
2703
3094
|
}
|
|
3095
|
+
/** Initializes, filters, and registers all behaviors for this FeatureCell. */
|
|
2704
3096
|
#registerBehaviors(config) {
|
|
2705
3097
|
const defaultBehaviors = this.#defineDefaultBehaviors(config);
|
|
2706
3098
|
// Strip out any user-provided reducers; they are passed separately via `reducers`
|
|
@@ -2731,7 +3123,7 @@ class Orchestrator {
|
|
|
2731
3123
|
if (hasTabSync) {
|
|
2732
3124
|
allBehaviors = allBehaviors.filter((behavior) => behavior.type !== BehaviorTypes.CoreState);
|
|
2733
3125
|
}
|
|
2734
|
-
const behaviorInit = new BehaviorInitializationClass(this.cellKey);
|
|
3126
|
+
const behaviorInit = new BehaviorInitializationClass(this.cellKey, config.lastSnapshot, config.state$);
|
|
2735
3127
|
this.#registerBehaviorsWithVault(allBehaviors);
|
|
2736
3128
|
this.#behaviors = behaviorInit.initializeBehaviors(allBehaviors, config.behaviorConfigs);
|
|
2737
3129
|
this.#registerErrorBehavior();
|
|
@@ -2740,6 +3132,7 @@ class Orchestrator {
|
|
|
2740
3132
|
this.#registerStateBehavior();
|
|
2741
3133
|
behaviorInit.applyBehaviorExtensions(this.#behaviors, config.cell, this.vaultMonitor);
|
|
2742
3134
|
}
|
|
3135
|
+
/** Filters behaviors to only those eligible for stage-based pipeline execution. */
|
|
2743
3136
|
#registerStageBehaviors() {
|
|
2744
3137
|
// Remove merge behavior from the pipeline list
|
|
2745
3138
|
this.#stageBehaviors = this.#behaviors.filter((behavior) => !(behavior.type === BehaviorTypes.CoreState ||
|
|
@@ -2750,6 +3143,7 @@ class Orchestrator {
|
|
|
2750
3143
|
behavior.type === BehaviorTypes.CoreErrorCallback ||
|
|
2751
3144
|
behavior.type === BehaviorTypes.Merge));
|
|
2752
3145
|
}
|
|
3146
|
+
/** Extracts and registers the core state and emit-state callback behaviors. */
|
|
2753
3147
|
#registerStateBehavior() {
|
|
2754
3148
|
const tabSyncState = this.#behaviors.filter((behavior) => behavior.type === BehaviorTypes.TabSyncState);
|
|
2755
3149
|
const coreState = this.#behaviors.filter((behavior) => behavior.type === BehaviorTypes.CoreState);
|
|
@@ -2760,6 +3154,7 @@ class Orchestrator {
|
|
|
2760
3154
|
this.#coreStateBehavior = stateBehaviors[0] ?? null;
|
|
2761
3155
|
this.#emitStateCallbackBehavior = this.#behaviors.filter((behavior) => isCoreEmitStateCallbackBehavior(behavior))[0];
|
|
2762
3156
|
}
|
|
3157
|
+
/** Extracts and registers the core error, error callback, and error transform behaviors. */
|
|
2763
3158
|
#registerErrorBehavior() {
|
|
2764
3159
|
// Extract and remove error behavior
|
|
2765
3160
|
const coreErrors = this.#behaviors.filter((behavior) => behavior.type === BehaviorTypes.CoreError);
|
|
@@ -2770,11 +3165,17 @@ class Orchestrator {
|
|
|
2770
3165
|
this.#coreErrorCallbackBehavior = this.#behaviors.filter((behavior) => isCoreErrorCallbackBehavior(behavior))[0];
|
|
2771
3166
|
this.#errorTransformBehaviors = this.#behaviors.filter((behavior) => isErrorTransformBehavior(behavior));
|
|
2772
3167
|
}
|
|
3168
|
+
/** Extracts and registers the merge behavior from the behavior list. */
|
|
2773
3169
|
#registerMergeBehavior() {
|
|
2774
3170
|
// Extract and remove merge behavior
|
|
2775
3171
|
const merges = this.#behaviors.filter((behavior) => behavior.type === BehaviorTypes.Merge);
|
|
2776
3172
|
this.#mergeBehavior = merges[0] ?? null;
|
|
2777
3173
|
}
|
|
3174
|
+
/**
|
|
3175
|
+
* Runs a stepwise behavior and returns its pipeline signal.
|
|
3176
|
+
*
|
|
3177
|
+
* @returns A noop, clear-state, or continue signal.
|
|
3178
|
+
*/
|
|
2778
3179
|
async #runStepwise(behaviorType, ctx, current) {
|
|
2779
3180
|
const stepwise = await this.#runUpstreamStage(behaviorType, ctx, current);
|
|
2780
3181
|
if (isVaultClearState(stepwise)) {
|
|
@@ -2785,6 +3186,11 @@ class Orchestrator {
|
|
|
2785
3186
|
}
|
|
2786
3187
|
return VAULT_CONTINUE;
|
|
2787
3188
|
}
|
|
3189
|
+
/**
|
|
3190
|
+
* Runs the post-resolve pipeline stages and returns the final state data.
|
|
3191
|
+
*
|
|
3192
|
+
* @returns The pipeline data flow result or a sentinel signal.
|
|
3193
|
+
*/
|
|
2788
3194
|
async #finishPipeline(ctx, resolved) {
|
|
2789
3195
|
// Stage: operators
|
|
2790
3196
|
let pipelineDataFlow;
|
|
@@ -2825,6 +3231,7 @@ class Orchestrator {
|
|
|
2825
3231
|
// Commit the *cloned* pre-encrypted snapshot to signals
|
|
2826
3232
|
return stateData;
|
|
2827
3233
|
}
|
|
3234
|
+
/** Orchestrates a full replace pipeline cycle for the current attempt. */
|
|
2828
3235
|
async #orchestrateReplace(ctx) {
|
|
2829
3236
|
this.vaultMonitor.startReplace(this.cellKey, VAULT_ORCHESTRATOR, ctx);
|
|
2830
3237
|
await this.#safeAsync(async () => {
|
|
@@ -2842,6 +3249,7 @@ class Orchestrator {
|
|
|
2842
3249
|
return this.#handleFinalState(finalState, ctx);
|
|
2843
3250
|
}, ctx);
|
|
2844
3251
|
}
|
|
3252
|
+
/** Orchestrates a full merge pipeline cycle for the current attempt. */
|
|
2845
3253
|
async #orchestrateMerge(ctx, options) {
|
|
2846
3254
|
this.vaultMonitor.startMerge(this.cellKey, VAULT_ORCHESTRATOR, ctx);
|
|
2847
3255
|
await this.#safeAsync(async () => {
|
|
@@ -2866,6 +3274,11 @@ class Orchestrator {
|
|
|
2866
3274
|
return await this.#handleFinalState(finalState, ctx);
|
|
2867
3275
|
}, ctx);
|
|
2868
3276
|
}
|
|
3277
|
+
/**
|
|
3278
|
+
* Emits monitor events for the final pipeline state and returns the result.
|
|
3279
|
+
*
|
|
3280
|
+
* @returns The final state value passed through.
|
|
3281
|
+
*/
|
|
2869
3282
|
async #handleFinalState(finalState, ctx) {
|
|
2870
3283
|
let payload;
|
|
2871
3284
|
if (isSignalStop(finalState)) {
|
|
@@ -2885,6 +3298,7 @@ class Orchestrator {
|
|
|
2885
3298
|
}
|
|
2886
3299
|
return finalState;
|
|
2887
3300
|
}
|
|
3301
|
+
/** Wraps an async pipeline function with error handling and state finalization. */
|
|
2888
3302
|
async #safeAsync(fn, ctx) {
|
|
2889
3303
|
try {
|
|
2890
3304
|
const result = await fn();
|
|
@@ -2904,6 +3318,11 @@ class Orchestrator {
|
|
|
2904
3318
|
await this.decisionEngine?.notifyFailure(this.buildControllerCtx(ctx), pipelineError);
|
|
2905
3319
|
}
|
|
2906
3320
|
}
|
|
3321
|
+
/**
|
|
3322
|
+
* Iterates through behaviors matching the given stage and applies each one.
|
|
3323
|
+
*
|
|
3324
|
+
* @returns The accumulated pipeline value after all stage behaviors execute.
|
|
3325
|
+
*/
|
|
2907
3326
|
async #runUpstreamStage(stage, ctx, current) {
|
|
2908
3327
|
let stageBehaviors;
|
|
2909
3328
|
if (stage === BehaviorTypes.Resolve) {
|
|
@@ -3000,6 +3419,11 @@ class Orchestrator {
|
|
|
3000
3419
|
}
|
|
3001
3420
|
return current;
|
|
3002
3421
|
}
|
|
3422
|
+
/**
|
|
3423
|
+
* Runs interceptor behaviors and returns a stop signal if any interceptor halts the pipeline.
|
|
3424
|
+
*
|
|
3425
|
+
* @returns Undefined to continue or VAULT_STOP to halt.
|
|
3426
|
+
*/
|
|
3003
3427
|
async #runInterceptorStage(ctx) {
|
|
3004
3428
|
const interceptorBehaviors = this.#stageBehaviors.filter((behavior) => behavior.type === BehaviorTypes.Interceptor);
|
|
3005
3429
|
for (const behavior of interceptorBehaviors) {
|
|
@@ -3020,10 +3444,16 @@ class Orchestrator {
|
|
|
3020
3444
|
}
|
|
3021
3445
|
return;
|
|
3022
3446
|
}
|
|
3447
|
+
/** Returns whether any operator behaviors are registered in the pipeline. */
|
|
3023
3448
|
#containsOperators() {
|
|
3024
3449
|
const operatorBehaviors = this.#stageBehaviors.filter((behavior) => behavior.type === BehaviorTypes.Operator);
|
|
3025
3450
|
return operatorBehaviors.length > 0;
|
|
3026
3451
|
}
|
|
3452
|
+
/**
|
|
3453
|
+
* Runs operator behaviors in sequence and returns the transformed value.
|
|
3454
|
+
*
|
|
3455
|
+
* @returns The pipeline value after all operators execute or undefined on noop.
|
|
3456
|
+
*/
|
|
3027
3457
|
async #runOperatorStage(ctx, current) {
|
|
3028
3458
|
const operatorBehaviors = this.#stageBehaviors.filter((behavior) => behavior.type === BehaviorTypes.Operator);
|
|
3029
3459
|
for (const behavior of operatorBehaviors) {
|
|
@@ -3046,6 +3476,11 @@ class Orchestrator {
|
|
|
3046
3476
|
}
|
|
3047
3477
|
return current;
|
|
3048
3478
|
}
|
|
3479
|
+
/**
|
|
3480
|
+
* Runs encrypt and persist behaviors in sequence for state persistence.
|
|
3481
|
+
*
|
|
3482
|
+
* @returns The pipeline persist value after all stage behaviors execute.
|
|
3483
|
+
*/
|
|
3049
3484
|
async #runPersistStage(stage, ctx, current) {
|
|
3050
3485
|
let stageBehaviors;
|
|
3051
3486
|
stageBehaviors = this.#stageBehaviors.filter((behavior) => behavior.type === stage);
|
|
@@ -3080,6 +3515,7 @@ class Orchestrator {
|
|
|
3080
3515
|
}
|
|
3081
3516
|
return current;
|
|
3082
3517
|
}
|
|
3518
|
+
/** Invokes destroy on each registered behavior with monitor tracing. */
|
|
3083
3519
|
#destroyBehaviors(ctx) {
|
|
3084
3520
|
for (const behavior of this.#behaviors) {
|
|
3085
3521
|
this.vaultMonitor.startDestroy(this.cellKey, behavior.key, ctx);
|
|
@@ -3093,6 +3529,7 @@ class Orchestrator {
|
|
|
3093
3529
|
}
|
|
3094
3530
|
}
|
|
3095
3531
|
}
|
|
3532
|
+
/** Invokes reset on each registered behavior with monitor tracing. */
|
|
3096
3533
|
#resetBehaviors(ctx) {
|
|
3097
3534
|
for (const behavior of this.#behaviors) {
|
|
3098
3535
|
this.vaultMonitor.startReset(this.cellKey, behavior.key, ctx);
|
|
@@ -3106,6 +3543,7 @@ class Orchestrator {
|
|
|
3106
3543
|
}
|
|
3107
3544
|
}
|
|
3108
3545
|
}
|
|
3546
|
+
/** Runs emit-state callbacks with an isolated snapshot clone. */
|
|
3109
3547
|
async #runStateBehaviors(ctx) {
|
|
3110
3548
|
if (this.#emitStateCallbacks?.length > 0) {
|
|
3111
3549
|
const lastSnapshotClone = isolateValue(ctx.lastSnapshot);
|
|
@@ -3117,15 +3555,9 @@ class Orchestrator {
|
|
|
3117
3555
|
}
|
|
3118
3556
|
}
|
|
3119
3557
|
/**
|
|
3120
|
-
* Runs
|
|
3121
|
-
*
|
|
3122
|
-
* - Starts from a normalized ResourceError produced by resourceError().
|
|
3123
|
-
* - Each behavior gets (rawError, currentResourceError, ctx).
|
|
3124
|
-
* - If a behavior returns:
|
|
3125
|
-
* • ResourceError → becomes the next currentResourceError
|
|
3126
|
-
* • VAULT_NOOP → previous ResourceError is kept
|
|
3558
|
+
* Runs error normalization, transforms, state commit, and callbacks in sequence.
|
|
3127
3559
|
*
|
|
3128
|
-
*
|
|
3560
|
+
* @returns The final normalized vault error shape.
|
|
3129
3561
|
*/
|
|
3130
3562
|
async #runErrorBehaviors(rawError, ctx) {
|
|
3131
3563
|
let current;
|
|
@@ -3206,6 +3638,11 @@ class Orchestrator {
|
|
|
3206
3638
|
vaultDebug(`${this.cellKey} #runErrorBehaviors completed with final ResourceError: ${JSON.stringify(current)}`);
|
|
3207
3639
|
return current;
|
|
3208
3640
|
}
|
|
3641
|
+
/**
|
|
3642
|
+
* Determines the resolve type for the incoming state input.
|
|
3643
|
+
*
|
|
3644
|
+
* @returns The resolve type matching the incoming value shape.
|
|
3645
|
+
*/
|
|
3209
3646
|
#getResolveType(incoming) {
|
|
3210
3647
|
if (isHttpResourceRef(incoming)) {
|
|
3211
3648
|
return ResolveTypes.HttpResource;
|
|
@@ -3223,6 +3660,7 @@ class Orchestrator {
|
|
|
3223
3660
|
return ResolveTypes.Value;
|
|
3224
3661
|
}
|
|
3225
3662
|
}
|
|
3663
|
+
/** Loads the initial state from persistence, configuration, or a deferred factory. */
|
|
3226
3664
|
async #loadInitialState(ctx) {
|
|
3227
3665
|
const incoming = {
|
|
3228
3666
|
value: undefined,
|
|
@@ -3257,9 +3695,19 @@ class Orchestrator {
|
|
|
3257
3695
|
this.decisionEngine?.notifySuccess(this.buildControllerCtx(ctx));
|
|
3258
3696
|
}
|
|
3259
3697
|
}
|
|
3698
|
+
/**
|
|
3699
|
+
* Returns persist behaviors registered for this FeatureCell.
|
|
3700
|
+
*
|
|
3701
|
+
* @returns Array of persist behavior instances.
|
|
3702
|
+
*/
|
|
3260
3703
|
#getPersistedBehaviors() {
|
|
3261
3704
|
return this.#stageBehaviors.filter((behavior) => behavior.type === BehaviorTypes.Persist);
|
|
3262
3705
|
}
|
|
3706
|
+
/**
|
|
3707
|
+
* Loads persisted state and decrypts it if encryption behaviors are registered.
|
|
3708
|
+
*
|
|
3709
|
+
* @returns The loaded and optionally decrypted state or undefined on failure.
|
|
3710
|
+
*/
|
|
3263
3711
|
async #loadInitialPersistedState(ctx, persistBehaviors) {
|
|
3264
3712
|
let loaded = undefined;
|
|
3265
3713
|
for (const behavior of persistBehaviors) {
|
|
@@ -3308,6 +3756,7 @@ class Orchestrator {
|
|
|
3308
3756
|
}
|
|
3309
3757
|
}
|
|
3310
3758
|
|
|
3759
|
+
/** Enumeration of conductor license evaluation statuses. */
|
|
3311
3760
|
const ConductorLicenseStatusTypes = {
|
|
3312
3761
|
Pending: 'pending',
|
|
3313
3762
|
Approved: 'approved',
|
|
@@ -3464,20 +3913,32 @@ class ControllerInitializationClass {
|
|
|
3464
3913
|
}
|
|
3465
3914
|
}
|
|
3466
3915
|
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3916
|
+
/**
|
|
3917
|
+
* Coordinates pipeline execution, controller arbitration, and license
|
|
3918
|
+
* gating for a single FeatureCell. The Conductor serializes pipeline
|
|
3919
|
+
* attempts through a FIFO queue and delegates vote resolution to the
|
|
3920
|
+
* DecisionEngine.
|
|
3921
|
+
*/
|
|
3473
3922
|
class Conductor extends Orchestrator {
|
|
3923
|
+
/** FIFO queue of pending pipeline attempts awaiting controller evaluation. */
|
|
3474
3924
|
#attemptQueue = [];
|
|
3925
|
+
/** Instantiated controller instances for this FeatureCell. */
|
|
3475
3926
|
#controllers = [];
|
|
3927
|
+
/** Subject emitting controller lifecycle events to the decision engine. */
|
|
3476
3928
|
#events$ = new Subject();
|
|
3929
|
+
/** Whether the conductor is currently processing an attempt. */
|
|
3477
3930
|
#processing = false;
|
|
3931
|
+
/** Whether the conductor is in a deny state for test flushing. */
|
|
3478
3932
|
#isDenyStateForTesting = false;
|
|
3933
|
+
/** Current license evaluation status for this conductor. */
|
|
3479
3934
|
#conductorLicenseStatus = ConductorLicenseStatusTypes.Pending;
|
|
3935
|
+
/** Subject signaling when the conductor has settled for DevMode testing. */
|
|
3480
3936
|
#settled$ = new Subject();
|
|
3937
|
+
/**
|
|
3938
|
+
* Creates a new Conductor and initializes controllers and licensing.
|
|
3939
|
+
*
|
|
3940
|
+
* @param config - Configuration describing behaviors, controllers, and callbacks.
|
|
3941
|
+
*/
|
|
3481
3942
|
constructor(config) {
|
|
3482
3943
|
super(config);
|
|
3483
3944
|
LicensingService().describeFeature({
|
|
@@ -3499,6 +3960,11 @@ class Conductor extends Orchestrator {
|
|
|
3499
3960
|
this.vaultMonitor.conductorLicenseAttempt(this.cellKey, `${this.cellKey}::license`);
|
|
3500
3961
|
this.initializeOrchestrator(config);
|
|
3501
3962
|
}
|
|
3963
|
+
/**
|
|
3964
|
+
* Enqueues an initialization attempt for the FeatureCell pipeline.
|
|
3965
|
+
*
|
|
3966
|
+
* @param ctx - Behavior context for the initialization cycle.
|
|
3967
|
+
*/
|
|
3502
3968
|
initialize(ctx) {
|
|
3503
3969
|
const initCtx = this.#buildPipelineCtx(ctx, OperationTypes.Initialize, undefined);
|
|
3504
3970
|
this.#enqueueAttempt({
|
|
@@ -3508,6 +3974,14 @@ class Conductor extends Orchestrator {
|
|
|
3508
3974
|
});
|
|
3509
3975
|
}
|
|
3510
3976
|
//#region public method
|
|
3977
|
+
/**
|
|
3978
|
+
* Enqueues a pipeline attempt for a merge or replace operation.
|
|
3979
|
+
*
|
|
3980
|
+
* @param ctx - Behavior context for the current pipeline cycle.
|
|
3981
|
+
* @param incoming - Incoming state input value.
|
|
3982
|
+
* @param operation - Pipeline operation type.
|
|
3983
|
+
* @param options - Optional merge or replace options.
|
|
3984
|
+
*/
|
|
3511
3985
|
conduct(ctx, incoming, operation, options) {
|
|
3512
3986
|
const behaviorCtx = this.#buildPipelineCtx(ctx, operation, options);
|
|
3513
3987
|
const preparedIncoming = this.prepareIncoming(behaviorCtx, incoming, operation);
|
|
@@ -3550,6 +4024,7 @@ class Conductor extends Orchestrator {
|
|
|
3550
4024
|
}
|
|
3551
4025
|
//#endregion
|
|
3552
4026
|
//#region Private Methods
|
|
4027
|
+
/** Routes a pipeline attempt to the appropriate orchestrator method. */
|
|
3553
4028
|
async #processEvent(ctx, options) {
|
|
3554
4029
|
if (ctx.operation === OperationTypes.Initialize) {
|
|
3555
4030
|
await this.initializeFeatureCell(ctx);
|
|
@@ -3562,6 +4037,7 @@ class Conductor extends Orchestrator {
|
|
|
3562
4037
|
this.vaultMonitor.runtimeError(this.cellKey, VAULT_CONDUCTOR, ctx, new Error(`Unknown operation type: "${ctx.operation}"`));
|
|
3563
4038
|
this.#completeCurrentAttempt(ctx);
|
|
3564
4039
|
}
|
|
4040
|
+
/** Schedules a microtask to signal a deny-state completion. */
|
|
3565
4041
|
#enqueueMicrotask() {
|
|
3566
4042
|
queueMicrotask(() => {
|
|
3567
4043
|
this.#denyAttemptCompleted();
|
|
@@ -3593,6 +4069,7 @@ class Conductor extends Orchestrator {
|
|
|
3593
4069
|
this.#enqueueMicrotask();
|
|
3594
4070
|
}
|
|
3595
4071
|
}
|
|
4072
|
+
/** Finalizes the current attempt and advances the queue in a microtask. */
|
|
3596
4073
|
#completeCurrentAttempt(ctx) {
|
|
3597
4074
|
const head = this.#attemptQueue[0];
|
|
3598
4075
|
/* istanbul ignore next */
|
|
@@ -3647,6 +4124,7 @@ class Conductor extends Orchestrator {
|
|
|
3647
4124
|
this.#processQueue();
|
|
3648
4125
|
});
|
|
3649
4126
|
}
|
|
4127
|
+
/** Resets the processing flag and emits a restart-attempt monitor event. */
|
|
3650
4128
|
#resetProcessingState(ctx, outcome) {
|
|
3651
4129
|
this.vaultMonitor.restartControllerAttempt(this.cellKey, ctx.traceId, ctx, outcome);
|
|
3652
4130
|
this.#processing = false;
|
|
@@ -3743,6 +4221,7 @@ class Conductor extends Orchestrator {
|
|
|
3743
4221
|
this.#processQueue();
|
|
3744
4222
|
}
|
|
3745
4223
|
}
|
|
4224
|
+
/** Registers the decision engine and subscribes to controller lifecycle events. */
|
|
3746
4225
|
#registerDecisionEngine() {
|
|
3747
4226
|
this.decisionEngine = new DecisionEngine(this.#controllers, this.#events$);
|
|
3748
4227
|
this.#events$.subscribe({
|
|
@@ -3751,6 +4230,8 @@ class Conductor extends Orchestrator {
|
|
|
3751
4230
|
this.vaultMonitor.conductorLicenseDenied(this.cellKey, event.traceId);
|
|
3752
4231
|
this.#conductorLicenseStatus = ConductorLicenseStatusTypes.Denied;
|
|
3753
4232
|
const error = new Error(`${this.cellKey} Conductor Decision Engine: The FeatureCell received a "License Denied". Pipeline is disabled.`);
|
|
4233
|
+
// eslint-disable-next-line
|
|
4234
|
+
console.error(`[vault] ${error.message}`);
|
|
3754
4235
|
vaultDebug(error.message);
|
|
3755
4236
|
this.privateErrorService.setError(createVaultError(error, this.cellKey));
|
|
3756
4237
|
this.#attemptQueue.length = 0;
|
|
@@ -3808,6 +4289,7 @@ class Conductor extends Orchestrator {
|
|
|
3808
4289
|
}
|
|
3809
4290
|
});
|
|
3810
4291
|
}
|
|
4292
|
+
/** Builds an isolated pipeline context for a single attempt. */
|
|
3811
4293
|
#buildPipelineCtx(ctx, operation, options) {
|
|
3812
4294
|
const traceId = assignTraceId();
|
|
3813
4295
|
return {
|
|
@@ -3826,10 +4308,12 @@ class Conductor extends Orchestrator {
|
|
|
3826
4308
|
incoming: undefined
|
|
3827
4309
|
};
|
|
3828
4310
|
}
|
|
4311
|
+
/** Clears the attempt queue and resets the processing flag. */
|
|
3829
4312
|
#resetConductor() {
|
|
3830
4313
|
this.#attemptQueue.length = 0;
|
|
3831
4314
|
this.#processing = false;
|
|
3832
4315
|
}
|
|
4316
|
+
/** Invokes destroy on all registered controllers. */
|
|
3833
4317
|
#destroyControllers(ctx) {
|
|
3834
4318
|
for (const controller of this.#controllers) {
|
|
3835
4319
|
this.vaultMonitor.startDestroy(this.cellKey, controller.key, ctx);
|
|
@@ -3843,6 +4327,7 @@ class Conductor extends Orchestrator {
|
|
|
3843
4327
|
}
|
|
3844
4328
|
}
|
|
3845
4329
|
}
|
|
4330
|
+
/** Invokes reset on all registered controllers. */
|
|
3846
4331
|
#resetControllers(ctx) {
|
|
3847
4332
|
for (const controller of this.#controllers) {
|
|
3848
4333
|
this.vaultMonitor.startReset(this.cellKey, controller.key, ctx);
|
|
@@ -3856,6 +4341,7 @@ class Conductor extends Orchestrator {
|
|
|
3856
4341
|
}
|
|
3857
4342
|
}
|
|
3858
4343
|
}
|
|
4344
|
+
/** Adds the default or user-supplied error controller to the controller list. */
|
|
3859
4345
|
#addDefaultErrorController(controllers, config) {
|
|
3860
4346
|
const errorControllers = config.controllers.filter((controllerClass) => {
|
|
3861
4347
|
return controllerClass.type === ControllerTypes.Error;
|
|
@@ -3871,6 +4357,7 @@ class Conductor extends Orchestrator {
|
|
|
3871
4357
|
controllers.unshift(withCoreErrorController);
|
|
3872
4358
|
}
|
|
3873
4359
|
}
|
|
4360
|
+
/** Removes reserved controller types from the user-supplied list. */
|
|
3874
4361
|
#filterRestrictedControllers(controllers) {
|
|
3875
4362
|
return controllers.filter((controller) => {
|
|
3876
4363
|
if (controller.type === ControllerTypes.License || controller.type === ControllerTypes.CoreAbstain) {
|
|
@@ -3883,6 +4370,7 @@ class Conductor extends Orchestrator {
|
|
|
3883
4370
|
// ---------------------------------------------------------------------------
|
|
3884
4371
|
// INTERNAL SCHEDULING
|
|
3885
4372
|
// ---------------------------------------------------------------------------
|
|
4373
|
+
/** Initializes all controllers and registers the decision engine. */
|
|
3886
4374
|
#registerControllers(config) {
|
|
3887
4375
|
config.controllers = config.controllers ?? [];
|
|
3888
4376
|
const allControllers = this.#filterRestrictedControllers(config.controllers);
|
|
@@ -3923,12 +4411,14 @@ class Conductor extends Orchestrator {
|
|
|
3923
4411
|
this.vaultMonitor.endControllerVote(this.cellKey, pending.controllerCtx.traceId, pending.controllerCtx, decision);
|
|
3924
4412
|
}), map((decision) => decision.outcome));
|
|
3925
4413
|
}
|
|
4414
|
+
/** Signals a deny-state settlement for DevMode testing. */
|
|
3926
4415
|
#denyAttemptCompleted() {
|
|
3927
4416
|
if (!DevMode.active)
|
|
3928
4417
|
return;
|
|
3929
4418
|
this.#settled$.next();
|
|
3930
4419
|
}
|
|
3931
4420
|
// ADD
|
|
4421
|
+
/** Signals queue settlement when all attempts have been processed. */
|
|
3932
4422
|
#attemptCompleted() {
|
|
3933
4423
|
if (!DevMode.active || this.#attemptQueue.length > 0)
|
|
3934
4424
|
return;
|
|
@@ -3936,6 +4426,7 @@ class Conductor extends Orchestrator {
|
|
|
3936
4426
|
this.#settled$.next();
|
|
3937
4427
|
});
|
|
3938
4428
|
}
|
|
4429
|
+
/** Returns a promise that resolves when the conductor has settled. */
|
|
3939
4430
|
#conductorSettled() {
|
|
3940
4431
|
return firstValueFrom(this.#settled$);
|
|
3941
4432
|
}
|
|
@@ -3987,27 +4478,46 @@ function featureCellValidation(descriptor, behaviors = []) {
|
|
|
3987
4478
|
}
|
|
3988
4479
|
}
|
|
3989
4480
|
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
4481
|
+
/**
|
|
4482
|
+
* Abstract builder that assembles the fluent configuration API and
|
|
4483
|
+
* lifecycle wiring for a FeatureCell. Subclasses call setup() to obtain
|
|
4484
|
+
* a CellBuilderContract, then build the final FeatureCellShape from it.
|
|
4485
|
+
* The builder manages conductor creation, initialization guards, and
|
|
4486
|
+
* state operation dispatch.
|
|
4487
|
+
*/
|
|
3996
4488
|
class FeatureCellBuilder {
|
|
3997
4489
|
featureCellConfiguration;
|
|
3998
4490
|
defaultBehaviors;
|
|
3999
4491
|
behaviors;
|
|
4000
4492
|
controllers;
|
|
4493
|
+
/** Whether the cell encountered a critical failure and is permanently unusable. */
|
|
4001
4494
|
#cellCorrupt = false;
|
|
4495
|
+
/** Conductor instance managing pipeline execution for this cell. */
|
|
4002
4496
|
#conductor;
|
|
4497
|
+
/** Whether the cell has been initialized via the builder. */
|
|
4003
4498
|
#initialized = false;
|
|
4499
|
+
/** Vault monitor instance for tracing lifecycle events. */
|
|
4004
4500
|
#vaultMonitor = VaultMonitor();
|
|
4501
|
+
/** Reference to the constructed FeatureCellBaseShape. */
|
|
4005
4502
|
cell;
|
|
4503
|
+
/** Unique key identifying this FeatureCell. */
|
|
4006
4504
|
cellKey;
|
|
4505
|
+
/** Shared behavior context for pipeline execution. */
|
|
4007
4506
|
ctx;
|
|
4507
|
+
/** Subject signaling cell destruction. */
|
|
4008
4508
|
destroyed$ = new Subject();
|
|
4509
|
+
/** Subject signaling cell reset. */
|
|
4009
4510
|
reset$ = new Subject();
|
|
4511
|
+
/** Subject emitting state snapshots to subscribers. */
|
|
4010
4512
|
state$ = new Subject();
|
|
4513
|
+
/**
|
|
4514
|
+
* Creates the builder and initializes the shared behavior context.
|
|
4515
|
+
*
|
|
4516
|
+
* @param featureCellConfiguration - FeatureCell descriptor configuration.
|
|
4517
|
+
* @param defaultBehaviors - Default behavior classes provided by the framework.
|
|
4518
|
+
* @param behaviors - User-supplied behavior classes.
|
|
4519
|
+
* @param controllers - User-supplied controller classes.
|
|
4520
|
+
*/
|
|
4011
4521
|
constructor(
|
|
4012
4522
|
// kept for parity with original API, even if not used directly
|
|
4013
4523
|
featureCellConfiguration, defaultBehaviors, behaviors, controllers) {
|
|
@@ -4018,6 +4528,11 @@ class FeatureCellBuilder {
|
|
|
4018
4528
|
this.cellKey = this.featureCellConfiguration.key;
|
|
4019
4529
|
this.ctx = this.#buildCtx();
|
|
4020
4530
|
}
|
|
4531
|
+
/**
|
|
4532
|
+
* Builds the initial behavior context with locked snapshot reference.
|
|
4533
|
+
*
|
|
4534
|
+
* @returns The constructed behavior context.
|
|
4535
|
+
*/
|
|
4021
4536
|
#buildCtx() {
|
|
4022
4537
|
const destroyed$ = this.destroyed$.asObservable();
|
|
4023
4538
|
const state$ = this.state$;
|
|
@@ -4055,6 +4570,7 @@ class FeatureCellBuilder {
|
|
|
4055
4570
|
// ---------------------------------------------------------------------------
|
|
4056
4571
|
// INTERNAL LIFECYCLE
|
|
4057
4572
|
// ---------------------------------------------------------------------------
|
|
4573
|
+
/** Resets the conductor and emits a reset signal. */
|
|
4058
4574
|
reset() {
|
|
4059
4575
|
this.#vaultMonitor.startReset(this.cellKey, VAULT_FEATURE_CELL, this.ctx);
|
|
4060
4576
|
vaultWarn(`${VAULT_FEATURE_CELL}: reset`);
|
|
@@ -4063,6 +4579,7 @@ class FeatureCellBuilder {
|
|
|
4063
4579
|
this.#conductor?.reset(this.ctx);
|
|
4064
4580
|
this.#vaultMonitor.endReset(this.cellKey, VAULT_FEATURE_CELL, this.ctx);
|
|
4065
4581
|
}
|
|
4582
|
+
/** Destroys the conductor, completes all subjects, and emits destruction signals. */
|
|
4066
4583
|
destroy() {
|
|
4067
4584
|
this.#vaultMonitor.startDestroy(this.cellKey, VAULT_FEATURE_CELL, this.ctx);
|
|
4068
4585
|
vaultWarn(`${VAULT_FEATURE_CELL}: destroy`);
|
|
@@ -4074,6 +4591,7 @@ class FeatureCellBuilder {
|
|
|
4074
4591
|
this.state$.complete();
|
|
4075
4592
|
this.#vaultMonitor.endDestroy(this.cellKey, VAULT_FEATURE_CELL, this.ctx);
|
|
4076
4593
|
}
|
|
4594
|
+
/** Throws if the cell is corrupt or has not been initialized. */
|
|
4077
4595
|
#ensureInitialized() {
|
|
4078
4596
|
if (this.#cellCorrupt) {
|
|
4079
4597
|
const errorMessage = `[vault] FeatureCell "${this.featureCellConfiguration.key}" encountered a critical initialization failure and is now in a corrupted state. Further use is blocked.`;
|
|
@@ -4086,6 +4604,7 @@ class FeatureCellBuilder {
|
|
|
4086
4604
|
throw new Error(errorMessage);
|
|
4087
4605
|
}
|
|
4088
4606
|
}
|
|
4607
|
+
/** Creates the conductor, validates the cell, and starts the initialization lifecycle. */
|
|
4089
4608
|
#initialize(ctx) {
|
|
4090
4609
|
if (this.#initialized) {
|
|
4091
4610
|
const errorMessage = `[vault] FeatureCell "${this.featureCellConfiguration.key}" already initialized.`;
|
|
@@ -4113,8 +4632,10 @@ class FeatureCellBuilder {
|
|
|
4113
4632
|
filterCallbacks: ctx.filterFunctions,
|
|
4114
4633
|
initialState: ctx.hydrate || this.featureCellConfiguration.initialState,
|
|
4115
4634
|
interceptors: ctx.interceptors,
|
|
4635
|
+
lastSnapshot: this.ctx.lastSnapshot,
|
|
4116
4636
|
operators: ctx.operators,
|
|
4117
|
-
reducerCallbacks: ctx.reducerFunctions
|
|
4637
|
+
reducerCallbacks: ctx.reducerFunctions,
|
|
4638
|
+
state$: this.state$
|
|
4118
4639
|
});
|
|
4119
4640
|
this.#conductor.initialize(this.ctx);
|
|
4120
4641
|
if (DevMode.active) {
|
|
@@ -4136,11 +4657,17 @@ class FeatureCellBuilder {
|
|
|
4136
4657
|
throw err;
|
|
4137
4658
|
}
|
|
4138
4659
|
}
|
|
4660
|
+
/** Marks the cell as corrupt, logs a runtime error, and throws. */
|
|
4139
4661
|
#handleCorruptionError(message) {
|
|
4140
4662
|
this.#cellCorrupt = true;
|
|
4141
4663
|
this.#vaultMonitor.runtimeError(this.cellKey, VAULT_FEATURE_CELL, this.ctx, message);
|
|
4142
4664
|
throw new Error(message);
|
|
4143
4665
|
}
|
|
4666
|
+
/**
|
|
4667
|
+
* Constructs the fluent CellBuilderContract with pre-initialization guards.
|
|
4668
|
+
*
|
|
4669
|
+
* @returns The builder contract exposing configuration and initialize methods.
|
|
4670
|
+
*/
|
|
4144
4671
|
setup() {
|
|
4145
4672
|
const afterTapCallbacks = [];
|
|
4146
4673
|
const beforeTapCallbacks = [];
|
|
@@ -4253,26 +4780,41 @@ class FeatureCellBuilder {
|
|
|
4253
4780
|
// ---------------------------------------------------------------------------
|
|
4254
4781
|
// STATE OPERATIONS
|
|
4255
4782
|
// ---------------------------------------------------------------------------
|
|
4783
|
+
/** Dispatches a merge operation through the conductor pipeline. */
|
|
4256
4784
|
mergeState(incoming, options) {
|
|
4257
4785
|
this.#ensureInitialized();
|
|
4258
4786
|
return this.#conductor.conduct(this.ctx, incoming, OperationTypes.Merge, options);
|
|
4259
4787
|
}
|
|
4788
|
+
/** Dispatches a replace operation through the conductor pipeline. */
|
|
4260
4789
|
replaceState(incoming, options) {
|
|
4261
4790
|
this.#ensureInitialized();
|
|
4262
4791
|
return this.#conductor.conduct(this.ctx, incoming, OperationTypes.Replace, options);
|
|
4263
4792
|
}
|
|
4264
4793
|
}
|
|
4265
4794
|
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4795
|
+
/**
|
|
4796
|
+
* Concrete FeatureCell factory that extends the builder to produce a
|
|
4797
|
+
* fully configured FeatureCellShape instance. The class wires fluent
|
|
4798
|
+
* API extensions from behaviors and controllers onto the resulting
|
|
4799
|
+
* cell and attaches non-enumerable context and key properties.
|
|
4800
|
+
*/
|
|
4272
4801
|
class FeatureCellClass extends FeatureCellBuilder {
|
|
4802
|
+
/**
|
|
4803
|
+
* Creates a new FeatureCellClass with the provided descriptor and behavior/controller lists.
|
|
4804
|
+
*
|
|
4805
|
+
* @param descriptor - FeatureCell configuration descriptor.
|
|
4806
|
+
* @param defaultBehaviors - Default behavior classes provided by the framework.
|
|
4807
|
+
* @param behaviors - User-supplied behavior classes.
|
|
4808
|
+
* @param controllers - User-supplied controller classes.
|
|
4809
|
+
*/
|
|
4273
4810
|
constructor(descriptor, defaultBehaviors, behaviors, controllers) {
|
|
4274
4811
|
super(descriptor, defaultBehaviors, behaviors, controllers);
|
|
4275
4812
|
}
|
|
4813
|
+
/**
|
|
4814
|
+
* Assembles and returns a fully configured FeatureCellShape with fluent API extensions.
|
|
4815
|
+
*
|
|
4816
|
+
* @returns The constructed FeatureCellShape instance.
|
|
4817
|
+
*/
|
|
4276
4818
|
build() {
|
|
4277
4819
|
const builder = this.setup();
|
|
4278
4820
|
const ctx = this.ctx;
|
|
@@ -4403,12 +4945,6 @@ function resetFeatureCellToken() {
|
|
|
4403
4945
|
featureCellTokenRequested.clear();
|
|
4404
4946
|
}
|
|
4405
4947
|
|
|
4406
|
-
// --- AI Model File Path (DO NOT DELETE) ---
|
|
4407
|
-
// FilePath: lib > public-api.ts
|
|
4408
|
-
// Updated: 2026-03-30 15:42
|
|
4409
|
-
// Generated by pathcomment [tab] (see .vscode/typescript.code-snippets) or
|
|
4410
|
-
// cmd+alt+j (see .vscode/keybindings.json)
|
|
4411
|
-
// --- END AI MODEL FILE PATH ---
|
|
4412
4948
|
/*
|
|
4413
4949
|
* This is for version support in dev mode and tracking in the devtools
|
|
4414
4950
|
*/
|
|
@@ -4417,5 +4953,5 @@ function resetFeatureCellToken() {
|
|
|
4417
4953
|
* Generated bundle index. Do not edit.
|
|
4418
4954
|
*/
|
|
4419
4955
|
|
|
4420
|
-
export { Conductor, FeatureCellClass, LicensingAbstract, VAULT_LICENSE_ID, VaultCore, VerifyLicenseToken, createFeatureCellToken, getFeatureCellToken, getLicensePayload, getVaultRegistryForTests, hasVaultLicense, isAuthorizedKey, isBypassLicensing, isPipelineTerminal, registerFeatureCell, registerVaultSettled, resetFeatureCellRegistry, resetVaultForTests, vaultAllSettled, vaultSettled };
|
|
4956
|
+
export { Conductor, FeatureCellClass, LicensingAbstract, VAULT_LICENSE_ID, VaultCore, VerifyLicenseToken, createFeatureCellToken, getFeatureCellToken, getLicensePayload, getVaultRegistryForTests, hasVaultLicense, isAuthorizedKey, isBypassLicensing, isPipelineTerminal, registerFeatureCell, registerVaultSettled, resetFeatureCellRegistry, resetVaultForTests, vaultAllSettled, vaultSettled, verifyLicensePayload };
|
|
4421
4957
|
//# sourceMappingURL=sdux-vault-engine.mjs.map
|