@sdux-vault/engine 0.0.9

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.
@@ -0,0 +1,4069 @@
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, validateBehaviorKey, BehaviorTypes, VAULT_STOP, isVaultNoop, VaultPrivateErrorService, isHttpResourceRef, isFunction, isStateInputShape, isVaultClearState, VAULT_CLEAR_STATE, VAULT_NOOP, VAULT_CONTINUE, isVaultContinue, isolateValue, isUndefined, isDefined, isNullish, ResolveTypes, isPromise, VaultUsagePromiseError, CONTROLLER_META, validateControllerKey, VaultLicenseError } from '@sdux-vault/shared';
2
+ import { of, map, catchError, forkJoin, Subject, ReplaySubject, isObservable, firstValueFrom, tap } from 'rxjs';
3
+ import { __decorate } from 'tslib';
4
+ import { filter } from 'rxjs/operators';
5
+ import { EventBus } from '@sdux-vault/devtools';
6
+
7
+ // --- AI Model File Path (DO NOT DELETE) ---
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 ---
13
+ const SDUX_PACKAGE = '@sdux-vault/engine';
14
+ const SDUX_VERSION = '0.0.9';
15
+ registerVersion(SDUX_PACKAGE, SDUX_VERSION);
16
+
17
+ /**
18
+ * Canonical identifier used to label the Conductor execution context.
19
+ */
20
+ const VAULT_CONDUCTOR = 'vault-conductor';
21
+
22
+ var withCoreAbstainController_1;
23
+ /**
24
+ * Core abstain controller responsible for participating in controller vote resolution.
25
+ * This controller observes incoming controller messages and emits a neutral vote without modifying pipeline flow.
26
+ *
27
+ * --RelatedStart--
28
+ * ControllerMessageShape
29
+ * ControllerVote
30
+ * ControllerVotes
31
+ * --RelatedEnd--
32
+ */
33
+ let withCoreAbstainController = class withCoreAbstainController {
34
+ static { withCoreAbstainController_1 = this; }
35
+ controllerCtx;
36
+ /**
37
+ * Static controller type identifier used by the orchestrator.
38
+ */
39
+ static type;
40
+ /**
41
+ * Static controller key assigned by the decorator.
42
+ */
43
+ static key;
44
+ /**
45
+ * Indicates whether this controller is critical.
46
+ */
47
+ static critical;
48
+ /**
49
+ * Controller type identifier used for pipeline classification.
50
+ */
51
+ type = withCoreAbstainController_1.type;
52
+ /**
53
+ * Indicates whether this controller instance is critical.
54
+ */
55
+ critical = withCoreAbstainController_1.critical;
56
+ /**
57
+ * Unique controller key identifying this instance.
58
+ */
59
+ key;
60
+ #isInitialized = false;
61
+ #initializationFailed = false;
62
+ /**
63
+ * Creates a new core conductor controller instance.
64
+ *
65
+ * @param key Unique controller identifier supplied by the factory.
66
+ * @param controllerCtx Controller context supplying execution metadata.
67
+ */
68
+ constructor(key, controllerCtx) {
69
+ this.controllerCtx = controllerCtx;
70
+ this.key = key;
71
+ }
72
+ /**
73
+ * Handles an incoming controller message and emits a controller vote.
74
+ *
75
+ * @param msg Controller message supplied by the orchestrator.
76
+ * @returns Observable emitting a controller vote or no value.
77
+ */
78
+ handleMessage(msg) {
79
+ vaultDebug(`${this.key} handleMessage received "${msg.type}" for trace "${msg.traceId}".`);
80
+ switch (msg.type) {
81
+ case ControllerMessageTypes.Attempt: {
82
+ const { ctx } = msg;
83
+ if (this.#initializationFailed) {
84
+ return of(ControllerVotes.Abort);
85
+ }
86
+ if (ctx.operation === OperationTypes.Initialize) {
87
+ return of(ControllerVotes.Abstain);
88
+ }
89
+ return of(this.#isInitialized ? ControllerVotes.Abstain : ControllerVotes.Deny);
90
+ }
91
+ case ControllerMessageTypes.Finalize: {
92
+ this.#isInitialized = true;
93
+ return of();
94
+ }
95
+ case ControllerMessageTypes.Success: {
96
+ this.#isInitialized = true;
97
+ return of();
98
+ }
99
+ case ControllerMessageTypes.Failure: {
100
+ if (msg.ctx.operation === OperationTypes.Initialize) {
101
+ this.#initializationFailed = true;
102
+ }
103
+ return of();
104
+ }
105
+ default: {
106
+ return of(ControllerVotes.Abstain);
107
+ }
108
+ }
109
+ }
110
+ /**
111
+ * Lifecycle hook invoked when the controller instance is destroyed.
112
+ */
113
+ destroy() {
114
+ vaultWarn(`${this.key} - destroy noop`);
115
+ }
116
+ /**
117
+ * Lifecycle hook invoked when the controller instance is reset.
118
+ */
119
+ reset() {
120
+ vaultWarn(`${this.key} - reset noop`);
121
+ }
122
+ };
123
+ withCoreAbstainController = withCoreAbstainController_1 = __decorate([
124
+ VaultController({
125
+ type: ControllerTypes.CoreAbstain,
126
+ key: defineControllerKey('Policy', 'CoreAbstain'),
127
+ critical: false
128
+ })
129
+ ], withCoreAbstainController);
130
+
131
+ var withCoreErrorController_1;
132
+ let withCoreErrorController = class withCoreErrorController {
133
+ static { withCoreErrorController_1 = this; }
134
+ controllerCtx;
135
+ /**
136
+ * Static controller type identifier used by the orchestrator.
137
+ */
138
+ static type;
139
+ /**
140
+ * Static controller key assigned by the decorator.
141
+ */
142
+ static key;
143
+ /**
144
+ * Indicates whether this controller is critical.
145
+ */
146
+ static critical;
147
+ /**
148
+ * Controller type identifier used for pipeline classification.
149
+ */
150
+ type = withCoreErrorController_1.type;
151
+ /**
152
+ * Indicates whether this controller instance is critical.
153
+ */
154
+ critical = withCoreErrorController_1.critical;
155
+ /**
156
+ * Unique controller key identifying this instance.
157
+ */
158
+ key;
159
+ ctx;
160
+ /**
161
+ * Creates a new core conductor controller instance.
162
+ *
163
+ * @param key Unique controller identifier supplied by the factory.
164
+ * @param controllerCtx Controller context supplying execution metadata.
165
+ */
166
+ constructor(key, controllerCtx) {
167
+ this.controllerCtx = controllerCtx;
168
+ this.key = key;
169
+ this.ctx = controllerCtx;
170
+ }
171
+ /**
172
+ * Handles an incoming controller message and emits a controller vote.
173
+ *
174
+ * @param msg Controller message supplied by the orchestrator.
175
+ * @returns Observable emitting a controller vote or no value.
176
+ */
177
+ handleMessage(msg) {
178
+ vaultDebug(`${this.key} handleMessage received "${msg.type}" for trace "${msg.traceId}".`);
179
+ switch (msg.type) {
180
+ case ControllerMessageTypes.Failure: {
181
+ vaultDebug(`${this.key} ABORT — default failure handler for trace "${msg.traceId}"`);
182
+ this.ctx.requestAbort(msg.traceId);
183
+ return of();
184
+ }
185
+ default: {
186
+ return of(ControllerVotes.Abstain);
187
+ }
188
+ }
189
+ }
190
+ /**
191
+ * Lifecycle hook invoked when the controller instance is destroyed.
192
+ */
193
+ destroy() {
194
+ vaultWarn(`${this.key} - destroy noop`);
195
+ }
196
+ /**
197
+ * Lifecycle hook invoked when the controller instance is reset.
198
+ */
199
+ reset() {
200
+ vaultWarn(`${this.key} - reset noop`);
201
+ }
202
+ };
203
+ withCoreErrorController = withCoreErrorController_1 = __decorate([
204
+ VaultController({
205
+ type: ControllerTypes.Error,
206
+ key: defineControllerKey('Policy', 'CoreError'),
207
+ critical: false
208
+ })
209
+ ], withCoreErrorController);
210
+
211
+ const LicenseEventTypes = {
212
+ RequireLicense: 'requireLicense',
213
+ ValidateLicense: 'validateLicense',
214
+ LicenseStatus: 'licenseStatus',
215
+ DescribeFeature: 'describe-feature',
216
+ DescribeBehaviors: 'describe-behaviors',
217
+ DescribeControllers: 'describe-controllers'
218
+ };
219
+
220
+ //eslint-disable-next-line
221
+ let singleton = null;
222
+ function InitializeLicensingService(events$, validation$) {
223
+ if (singleton)
224
+ return;
225
+ singleton = new LicensingServiceInstance(events$, validation$);
226
+ }
227
+ function LicensingService() {
228
+ if (!singleton) {
229
+ throw new Error('[vault] LicensingService not initialized.');
230
+ }
231
+ return singleton;
232
+ }
233
+ class LicensingServiceInstance {
234
+ events$;
235
+ validation$;
236
+ constructor(events$, validation$) {
237
+ this.events$ = events$;
238
+ this.validation$ = validation$;
239
+ }
240
+ describeFeature(event) {
241
+ event.type = LicenseEventTypes.DescribeFeature;
242
+ this.events$.next(event);
243
+ }
244
+ describeBehaviors(event) {
245
+ event.type = LicenseEventTypes.DescribeBehaviors;
246
+ this.events$.next(event);
247
+ }
248
+ describeControllers(event) {
249
+ event.type = LicenseEventTypes.DescribeControllers;
250
+ this.events$.next(event);
251
+ }
252
+ requestLicense(featureCellKey, key) {
253
+ if (!key) {
254
+ throw new Error('[vault] Cannot register controller license without a key.');
255
+ }
256
+ const licenseToken = this.#generateLicenseToken();
257
+ this.events$.next({
258
+ featureCellKey,
259
+ key,
260
+ licenseToken,
261
+ type: LicenseEventTypes.RequireLicense
262
+ });
263
+ return licenseToken;
264
+ }
265
+ validateLicense(featureCellKey, key, licenseToken, valid) {
266
+ this.events$.next({
267
+ featureCellKey,
268
+ key,
269
+ licenseToken,
270
+ type: LicenseEventTypes.ValidateLicense,
271
+ valid
272
+ });
273
+ }
274
+ #generateLicenseToken() {
275
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
276
+ const randomPart = (length) => Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
277
+ return `${randomPart(5)}-${randomPart(5)}`;
278
+ }
279
+ getLicenseValidation$() {
280
+ return this.validation$;
281
+ }
282
+ }
283
+ const resetLicensingServiceForTests = () => {
284
+ if (!DevMode.active)
285
+ return;
286
+ singleton = null;
287
+ };
288
+
289
+ var withCoreLicenseController_1;
290
+ let withCoreLicenseController = class withCoreLicenseController {
291
+ static { withCoreLicenseController_1 = this; }
292
+ controllerCtx;
293
+ static type;
294
+ static key;
295
+ static critical;
296
+ type = withCoreLicenseController_1.type;
297
+ critical = withCoreLicenseController_1.critical;
298
+ key;
299
+ #licenseApproved = null;
300
+ #sub;
301
+ constructor(key, controllerCtx) {
302
+ this.controllerCtx = controllerCtx;
303
+ this.key = key;
304
+ const featureCellKey = controllerCtx.featureCellKey;
305
+ this.#sub = LicensingService()
306
+ .getLicenseValidation$()
307
+ .pipe(filter((licenseEvent) => licenseEvent.featureCellKey === featureCellKey))
308
+ .subscribe((event) => {
309
+ this.#licenseApproved = event.approved;
310
+ this.#sub?.unsubscribe();
311
+ const traceId = `${featureCellKey}::license`;
312
+ if (event.approved) {
313
+ controllerCtx?.licenseApproved?.(traceId);
314
+ }
315
+ else {
316
+ controllerCtx?.licenseDenied?.(traceId);
317
+ }
318
+ });
319
+ }
320
+ handleMessage(msg) {
321
+ vaultDebug(`${this.key} received "${msg.type}" for trace "${msg.traceId}".`);
322
+ switch (msg.type) {
323
+ case ControllerMessageTypes.Attempt: {
324
+ if (this.#licenseApproved) {
325
+ return of(ControllerVotes.Abstain);
326
+ }
327
+ else if (this.#licenseApproved === null) {
328
+ return of(ControllerVotes.Deny);
329
+ }
330
+ // abort
331
+ return of(ControllerVotes.Abort);
332
+ }
333
+ default:
334
+ return of();
335
+ }
336
+ }
337
+ destroy() {
338
+ this.#sub?.unsubscribe();
339
+ vaultWarn(`${this.key} - destroy unsubscribe`);
340
+ }
341
+ reset() {
342
+ vaultWarn(`${this.key} - reset noop`);
343
+ }
344
+ };
345
+ withCoreLicenseController = withCoreLicenseController_1 = __decorate([
346
+ VaultController({
347
+ type: ControllerTypes.License,
348
+ key: defineControllerKey('Policy', 'CoreLicense'),
349
+ critical: true
350
+ })
351
+ ], withCoreLicenseController);
352
+
353
+ /**
354
+ * Pure decision engine responsible for aggregating controller votes.
355
+ *
356
+ * This class evaluates controller responses deterministically by normalizing
357
+ * votes, applying priority rules, and producing a single decision outcome
358
+ * without maintaining internal state or execution history.
359
+ */
360
+ class Arbitrator {
361
+ /**
362
+ * Evaluates an ATTEMPT message across all provided controllers.
363
+ *
364
+ * Each controller vote is normalized and aggregated using deterministic
365
+ * priority rules to produce exactly one decision outcome.
366
+ *
367
+ * @param controllers - The set of controllers participating in arbitration.
368
+ * @param msg - The ATTEMPT message being evaluated.
369
+ * @returns An observable emitting a single controller decision.
370
+ */
371
+ evaluateAttempt(controllers, msg) {
372
+ // No controllers → treat as unanimous abstain → arbitrate
373
+ if (controllers.length === 0) {
374
+ return of(this.arbitrate(msg.traceId, [ControllerVotes.Abstain]));
375
+ }
376
+ try {
377
+ const votes$ = controllers.map((controller) => controller.handleMessage(msg).pipe(map((v) => v ?? ControllerVotes.Abstain), catchError((err) => {
378
+ vaultWarn('[vault:arbitrator] controller threw during attempt:', err);
379
+ return of(ControllerVotes.Deny);
380
+ })));
381
+ return forkJoin(votes$).pipe(map((votes) => this.arbitrate(msg.traceId, votes)));
382
+ }
383
+ catch {
384
+ return of(this.arbitrate(msg.traceId, [ControllerVotes.Deny]));
385
+ }
386
+ }
387
+ /**
388
+ * Notifies controllers of non-attempt lifecycle messages.
389
+ *
390
+ * These messages are forwarded to controllers for observation only and
391
+ * do not participate in arbitration or decision-making.
392
+ *
393
+ * @param controllers - The controllers to notify.
394
+ * @param msg - The lifecycle message being dispatched.
395
+ * @returns An observable that completes when all notifications finish.
396
+ */
397
+ notify(controllers, msg) {
398
+ if (controllers.length === 0) {
399
+ return of(void 0);
400
+ }
401
+ try {
402
+ const streams$ = controllers.map((controller) => controller.handleMessage(msg).pipe(map(() => void 0), catchError((err) => {
403
+ vaultWarn('[vault:arbitrator] controller threw during notify:', err);
404
+ return of(void 0);
405
+ })));
406
+ return forkJoin(streams$).pipe(map(() => void 0));
407
+ }
408
+ catch {
409
+ return of(void 0);
410
+ }
411
+ }
412
+ /**
413
+ * Applies priority rules to a set of controller votes.
414
+ *
415
+ * Votes are evaluated in descending priority order to determine the
416
+ * final controller decision for a given trace.
417
+ *
418
+ * @param traceId - The trace identifier associated with the decision.
419
+ * @param votes - The normalized controller votes.
420
+ * @returns The resolved controller decision.
421
+ */
422
+ arbitrate(traceId, votes) {
423
+ const hasAbort = votes.includes(ControllerVotes.Abort);
424
+ if (hasAbort) {
425
+ return { traceId, outcome: DecisionOutcomeTypes.Abort };
426
+ }
427
+ const hasDeny = votes.includes(ControllerVotes.Deny);
428
+ if (hasDeny) {
429
+ return { traceId, outcome: DecisionOutcomeTypes.Deny };
430
+ }
431
+ const allAbstain = votes.every((v) => v === ControllerVotes.Abstain);
432
+ if (allAbstain) {
433
+ return { traceId, outcome: DecisionOutcomeTypes.Abstain };
434
+ }
435
+ vaultError(`Unknown controller vote detected`, { traceId, votes });
436
+ return { traceId, outcome: DecisionOutcomeTypes.Deny };
437
+ }
438
+ }
439
+
440
+ // --- AI Model File Path ---
441
+ // projects > engine > src > lib > types > monitor > vault-monitor-event-category.type.ts
442
+ const VaultMonitorEventCategoryType = {
443
+ Boundary: 'boundary',
444
+ State: 'state',
445
+ Error: 'error'
446
+ };
447
+
448
+ // --- AI Model File Path ---
449
+ // projects > engine > src > lib > types > monitor > vault-monitor-field-rule.type.ts
450
+ const FieldRuleTypes = {
451
+ Never: 'never',
452
+ Optional: 'optional',
453
+ Required: 'required'
454
+ };
455
+
456
+ // --- AI Model File Path (DO NOT DELETE) ---
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 ---
462
+ const CATEGORY_FIELD_POLICY_CONSTANT = {
463
+ [VaultMonitorEventCategoryType.Boundary]: {
464
+ state: FieldRuleTypes.Never,
465
+ payload: FieldRuleTypes.Optional,
466
+ error: FieldRuleTypes.Never
467
+ },
468
+ [VaultMonitorEventCategoryType.State]: {
469
+ state: FieldRuleTypes.Required,
470
+ payload: FieldRuleTypes.Optional,
471
+ error: FieldRuleTypes.Never
472
+ },
473
+ [VaultMonitorEventCategoryType.Error]: {
474
+ state: FieldRuleTypes.Required,
475
+ payload: FieldRuleTypes.Optional,
476
+ error: FieldRuleTypes.Required
477
+ }
478
+ };
479
+
480
+ // --- AI Model File Path (DO NOT DELETE) ---
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 ---
486
+ const EVENT_POLICY_CONSTANT = {
487
+ // ─────────────────────────────────────────────
488
+ // STATE
489
+ // ─────────────────────────────────────────────
490
+ 'stage:end:core-state': { category: VaultMonitorEventCategoryType.State },
491
+ 'stage:end:core-emit-state': { category: VaultMonitorEventCategoryType.State },
492
+ 'lifecycle:end:merge': { category: VaultMonitorEventCategoryType.State },
493
+ 'lifecycle:end:replace': { category: VaultMonitorEventCategoryType.State },
494
+ 'stage:end:compute-merge': { category: VaultMonitorEventCategoryType.State },
495
+ 'stage:end:reducer': { category: VaultMonitorEventCategoryType.State },
496
+ 'stage:end:resolve': { category: VaultMonitorEventCategoryType.State },
497
+ // ─────────────────────────────────────────────
498
+ // ERROR
499
+ // ─────────────────────────────────────────────
500
+ 'lifecycle:notification:failure': { category: VaultMonitorEventCategoryType.Error },
501
+ 'lifecycle:notification:runtime-error': { category: VaultMonitorEventCategoryType.Error },
502
+ 'lifecycle:notification:warn': { category: VaultMonitorEventCategoryType.Error },
503
+ 'lifecycle:notification:fatal': { category: VaultMonitorEventCategoryType.Error },
504
+ // ─────────────────────────────────────────────
505
+ // BOUNDARY
506
+ // ─────────────────────────────────────────────
507
+ 'conductor:start:abort': { category: VaultMonitorEventCategoryType.Boundary },
508
+ 'conductor:start:deny': { category: VaultMonitorEventCategoryType.Boundary },
509
+ 'conductor:start:revote': { category: VaultMonitorEventCategoryType.Boundary },
510
+ 'controller:end:vote': { category: VaultMonitorEventCategoryType.Boundary },
511
+ // Conductor
512
+ 'conductor:start:license-approved': { category: VaultMonitorEventCategoryType.Boundary },
513
+ 'conductor:start:license-attempt': { category: VaultMonitorEventCategoryType.Boundary },
514
+ // Controller
515
+ 'controller:end:attempt': { category: VaultMonitorEventCategoryType.Boundary },
516
+ 'controller:notification:finalize': { category: VaultMonitorEventCategoryType.Boundary },
517
+ 'controller:notification:success': { category: VaultMonitorEventCategoryType.Boundary },
518
+ 'controller:restart:restart-controller-attempt': {
519
+ category: VaultMonitorEventCategoryType.Boundary
520
+ },
521
+ 'controller:start:attempt': { category: VaultMonitorEventCategoryType.Boundary },
522
+ 'controller:start:vote': { category: VaultMonitorEventCategoryType.Boundary },
523
+ // Lifecycle (end)
524
+ 'lifecycle:end:initialized': { category: VaultMonitorEventCategoryType.Boundary },
525
+ // Lifecycle (start)
526
+ 'lifecycle:start:core-callback-error': {
527
+ category: VaultMonitorEventCategoryType.Boundary
528
+ },
529
+ 'lifecycle:start:core-error': { category: VaultMonitorEventCategoryType.Boundary },
530
+ 'lifecycle:start:core-state': { category: VaultMonitorEventCategoryType.Boundary },
531
+ 'lifecycle:start:global-error': { category: VaultMonitorEventCategoryType.Boundary },
532
+ 'lifecycle:start:initialized': { category: VaultMonitorEventCategoryType.Boundary },
533
+ 'lifecycle:start:merge': { category: VaultMonitorEventCategoryType.Boundary },
534
+ 'lifecycle:start:replace': { category: VaultMonitorEventCategoryType.Boundary },
535
+ 'lifecycle:start:error-transform': { category: VaultMonitorEventCategoryType.Boundary },
536
+ 'lifecycle:end:error-transform': { category: VaultMonitorEventCategoryType.Boundary },
537
+ 'lifecycle:end:core-callback-error': { category: VaultMonitorEventCategoryType.Boundary },
538
+ 'lifecycle:end:core-error': { category: VaultMonitorEventCategoryType.Boundary },
539
+ 'lifecycle:end:global-error': { category: VaultMonitorEventCategoryType.Boundary },
540
+ // Stage (end)
541
+ 'stage:end:after-tap': { category: VaultMonitorEventCategoryType.Boundary },
542
+ 'stage:end:before-tap': { category: VaultMonitorEventCategoryType.Boundary },
543
+ 'stage:end:encrypt': { category: VaultMonitorEventCategoryType.Boundary },
544
+ 'stage:end:filter': { category: VaultMonitorEventCategoryType.Boundary },
545
+ 'stage:end:load-persist': { category: VaultMonitorEventCategoryType.Boundary },
546
+ 'stage:end:operator': { category: VaultMonitorEventCategoryType.Boundary },
547
+ 'stage:end:persist': { category: VaultMonitorEventCategoryType.Boundary },
548
+ // Stage (start)
549
+ 'stage:start:after-tap': { category: VaultMonitorEventCategoryType.Boundary },
550
+ 'stage:start:before-tap': { category: VaultMonitorEventCategoryType.Boundary },
551
+ 'stage:start:compute-merge': { category: VaultMonitorEventCategoryType.Boundary },
552
+ 'stage:start:encrypt': { category: VaultMonitorEventCategoryType.Boundary },
553
+ 'stage:start:filter': { category: VaultMonitorEventCategoryType.Boundary },
554
+ 'stage:start:load-persist': { category: VaultMonitorEventCategoryType.Boundary },
555
+ 'stage:start:operator': { category: VaultMonitorEventCategoryType.Boundary },
556
+ 'stage:start:persist': { category: VaultMonitorEventCategoryType.Boundary },
557
+ 'stage:start:reducer': { category: VaultMonitorEventCategoryType.Boundary },
558
+ 'stage:start:resolve': { category: VaultMonitorEventCategoryType.Boundary }
559
+ };
560
+
561
+ /**
562
+ * Holds the singleton VaultMonitorSharedState instance.
563
+ */
564
+ let instance$2 = null;
565
+ /**
566
+ * 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
+ *
573
+ * @returns The global VaultMonitorSharedState instance.
574
+ */
575
+ function VaultMonitorSharedState() {
576
+ if (!instance$2) {
577
+ instance$2 = new VaultMonitorSharedStateInstance();
578
+ }
579
+ return instance$2;
580
+ }
581
+ class VaultMonitorSharedStateInstance {
582
+ /**
583
+ * Optional global insight definition that overrides per-cell insight
584
+ * configuration for all monitor instances. When set, all monitors use
585
+ * this definition when determining which event fields to emit.
586
+ */
587
+ globalInsightOverride = null;
588
+ /**
589
+ * Registry of FeatureCell insight configurations keyed by cell ID.
590
+ *
591
+ * Each entry indicates whether insight is enabled and contains the
592
+ * resolved list of {@link InsightConfig} values associated with
593
+ * that FeatureCell.
594
+ */
595
+ cellRegistry = new Map();
596
+ }
597
+
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
+ /**
605
+ * Abstract helper base class used by both `VaultMonitor` and
606
+ * `VaultQueueMonitor`.
607
+ *
608
+ * This class centralizes access to shared monitor state and DevMode
609
+ * utilities. All mutable state is delegated to `VaultMonitorSharedState`,
610
+ * ensuring that every monitor instance operates on the same global registry
611
+ * and global insight override.
612
+ *
613
+ * No pipeline behavior is implemented directly here; this class provides
614
+ * shared accessors, helpers, and common metadata required by monitor
615
+ * implementations.
616
+ */
617
+ class VaultMonitorHelper {
618
+ /**
619
+ * Shared global monitor state. All cell registry information and
620
+ * global insight overrides are pulled from this injected source.
621
+ */
622
+ shared = VaultMonitorSharedState();
623
+ /**
624
+ * Behavior key used for DevTools/Telemetry classification in
625
+ * Vault diagnostics. Identifies monitor-related behavior for
626
+ * analytics, devtools, and inspector utilities.
627
+ */
628
+ key = defineBehaviorKey('DevTools', 'Telemetry');
629
+ /**
630
+ * Retrieves the globally-applied insight override, if any.
631
+ *
632
+ * @returns The active global `InsightDefinition`, or `null` if
633
+ * no override is applied.
634
+ */
635
+ get globalInsightOverride() {
636
+ return this.shared.globalInsightOverride;
637
+ }
638
+ /**
639
+ * Applies a global insight override. This value is shared across all
640
+ * monitors, allowing system-wide inspection behavior injection.
641
+ *
642
+ * @param def - The insight metadata to set as the global override.
643
+ */
644
+ set globalInsightOverride(def) {
645
+ this.shared.globalInsightOverride = def;
646
+ }
647
+ /**
648
+ * Provides access to the shared FeatureCell registry. This registry
649
+ * tracks monitor metadata about every registered cell, including
650
+ * whether it provides insights and the definitions associated with it.
651
+ *
652
+ * @returns The shared cell registry map.
653
+ */
654
+ get cellRegistry() {
655
+ return this.shared.cellRegistry;
656
+ }
657
+ /**
658
+ * Registers a FeatureCell into the shared monitor registry.
659
+ * Each entry stores insight awareness and any provided definitions.
660
+ *
661
+ * @param cellKey - The unique FeatureCell identifier.
662
+ * @param insight - Optional `InsightDefinition` describing cell-level
663
+ * insight capabilities.
664
+ */
665
+ registerCell(cellKey, insight) {
666
+ const hasInsight = !!insight;
667
+ this.cellRegistry.set(cellKey, {
668
+ hasInsight,
669
+ insights: hasInsight ? [insight] : []
670
+ });
671
+ }
672
+ /**
673
+ * Applies a global insight override shared across all monitors.
674
+ * When set, this overrides cell-specific insight metadata for
675
+ * system-wide inspection behavior.
676
+ *
677
+ * @param definition - The global insight definition to activate.
678
+ */
679
+ activateGlobalInsights(definition) {
680
+ this.globalInsightOverride = definition;
681
+ }
682
+ isChromeDevTools(cell) {
683
+ return cell === DEVTOOLS_LOGGING_KEY_CONSTANT || cell === DEVTOOLS_AGGREGATE_KEY_CONSTANT;
684
+ }
685
+ applyPolicy(event, insight) {
686
+ const category = EVENT_POLICY_CONSTANT[event.name]?.category ?? VaultMonitorEventCategoryType.Boundary;
687
+ const policy = CATEGORY_FIELD_POLICY_CONSTANT[category];
688
+ const allowState = !!insight?.wantsState;
689
+ const allowPayload = !!insight?.wantsPayload;
690
+ const allowErrors = !!insight?.wantsErrors;
691
+ if (!event.source) {
692
+ delete event.source;
693
+ }
694
+ // STATE
695
+ if (!allowState || policy.state === FieldRuleTypes.Never) {
696
+ delete event.state;
697
+ }
698
+ // PAYLOAD
699
+ if (!allowPayload ||
700
+ policy.payload === FieldRuleTypes.Never ||
701
+ (policy.payload === FieldRuleTypes.Optional && event.payload === undefined)) {
702
+ delete event.payload;
703
+ }
704
+ // ERROR
705
+ if (!allowErrors || policy.error === FieldRuleTypes.Never) {
706
+ delete event.error;
707
+ }
708
+ else if (policy.error === FieldRuleTypes.Required) {
709
+ // Optional payload allowed on Error category
710
+ if (!allowPayload || event.payload === undefined) {
711
+ delete event.payload;
712
+ }
713
+ }
714
+ return event;
715
+ }
716
+ }
717
+
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
+ /**
725
+ * Holds the singleton VaultMonitor instance.
726
+ */
727
+ let instance$1 = null;
728
+ /**
729
+ * Returns the global VaultMonitor singleton instance.
730
+ * This function ensures a single monitor instance is created and reused across the runtime.
731
+ *
732
+ * --RelatedStart--
733
+ * VaultMonitorContract
734
+ * --RelatedEnd--
735
+ *
736
+ * @returns The global VaultMonitor instance.
737
+ */
738
+ function VaultMonitor() {
739
+ if (!instance$1) {
740
+ instance$1 = new VaultMonitorInstance();
741
+ }
742
+ return instance$1;
743
+ }
744
+ /**
745
+ * Implements the VaultMonitorContract using shared helper functionality.
746
+ * This class provides the concrete singleton implementation backing the VaultMonitor entry point.
747
+ *
748
+ * --RelatedStart--
749
+ * VaultMonitorContract
750
+ * --RelatedEnd--
751
+ */
752
+ class VaultMonitorInstance extends VaultMonitorHelper {
753
+ /**
754
+ * Event bus used to emit structured pipeline events to DevTools.
755
+ * This bus is consumed by the ngSDuX Chrome extension and internal
756
+ * development utilities.
757
+ */
758
+ #eventBus = EventBus();
759
+ /**
760
+ * Creates a new VaultMonitor instance and exposes it globally for
761
+ * DevTools integration. The monitor is attached to `window.sdux`
762
+ * so that browser extensions can observe all emitted events.
763
+ */
764
+ constructor() {
765
+ super();
766
+ // This is for the chrome plugin
767
+ window.sdux ??= {};
768
+ window.sdux.vaultMonitorInstance = this;
769
+ }
770
+ /**
771
+ * 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
+ *
775
+ * @typeParam T - State value type.
776
+ * @param ctx - Immutable behavior context provided by the orchestrator.
777
+ * @returns A standardized StateSnapshot.
778
+ */
779
+ #toSnapshot(ctx) {
780
+ // const src: any = ctx.incoming ?? ctx.state ?? {};
781
+ const src = ctx?.snapshot ?? ctx?.state ?? {};
782
+ return {
783
+ isLoading: src.isLoading ?? false,
784
+ value: src.value ?? undefined,
785
+ error: src.error ?? null,
786
+ hasValue: src.hasValue ?? !!src.value
787
+ };
788
+ }
789
+ /** Emits a startAfterTap event. */
790
+ startAfterTap(cell, behaviorKey, ctx) {
791
+ const name = 'after-tap';
792
+ this.#emitEventV2StageStart({ cell, behaviorKey, name, ctx });
793
+ }
794
+ /** Emits an endAfterTap event with optional payload. */
795
+ endAfterTap(cell, behaviorKey, ctx, payload) {
796
+ const name = 'after-tap';
797
+ this.#emitEventV2StageEnd({ cell, behaviorKey, name, ctx, payload });
798
+ }
799
+ /** Emits a startBeforeTap event. */
800
+ startBeforeTap(cell, behaviorKey, ctx) {
801
+ const name = 'before-tap';
802
+ this.#emitEventV2StageStart({ cell, behaviorKey, name, ctx });
803
+ }
804
+ /** Emits an endBeforeTap event with optional payload. */
805
+ endBeforeTap(cell, behaviorKey, ctx, payload) {
806
+ const name = 'before-tap';
807
+ this.#emitEventV2StageEnd({ cell, behaviorKey, name, ctx, payload });
808
+ }
809
+ /** Emits a startClearPersist event. */
810
+ startClearPersist(cell, behaviorKey, ctx) {
811
+ const name = 'clear-persist';
812
+ this.#emitEventV2LifecycleStart({ cell, behaviorKey, name, ctx });
813
+ }
814
+ /** Emits an endClearPersist event. */
815
+ endClearPersist(cell, behaviorKey, ctx) {
816
+ const name = 'clear-persist';
817
+ this.#emitEventV2LifecycleEnd({ cell, behaviorKey, name, ctx });
818
+ }
819
+ /** Emits a startComputeMerge event. */
820
+ startComputeMerge(cell, behaviorKey, ctx) {
821
+ const name = 'compute-merge';
822
+ this.#emitEventV2StageStart({ cell, behaviorKey, name, ctx });
823
+ }
824
+ /** Emits an endComputeMerge event. */
825
+ endComputeMerge(cell, behaviorKey, ctx) {
826
+ const name = 'compute-merge';
827
+ this.#emitEventV2StageEnd({ cell, behaviorKey, name, ctx });
828
+ }
829
+ /** Emits a notifyConductorDeny event. */
830
+ notifyConductorDeny(cell, behaviorKey, ctx) {
831
+ const name = 'deny';
832
+ this.#emitEventV2ConductorNotification({ cell, behaviorKey, name, ctx });
833
+ }
834
+ /** Emits a conductorCrashed event. */
835
+ conductorCrashed(cell, behaviorKey, ctx, error) {
836
+ const errorMessage = createVaultError(error, cell);
837
+ const name = 'fatal';
838
+ vaultError(cell, behaviorKey, errorMessage);
839
+ this.#emitEventV2LifecycleNotification({
840
+ cell,
841
+ behaviorKey,
842
+ name,
843
+ ctx,
844
+ payload: {
845
+ message: 'This has proven to be untested code in unit tests. So you win some type of prize. Please create a github issues and share your amazing gift to bring down a systm.'
846
+ },
847
+ error: errorMessage
848
+ });
849
+ }
850
+ /** Emits a conductorRevote event. */
851
+ conductorRevote(cell, behaviorKey, ctx) {
852
+ const name = 'revote';
853
+ this.#emitEventV2LifecycleNotification({ cell, behaviorKey, name, ctx });
854
+ }
855
+ /** Emits a conductorAbort event. */
856
+ conductorAbort(cell, behaviorKey, ctx) {
857
+ const name = 'abort';
858
+ this.#emitEventV2LifecycleNotification({ cell, behaviorKey, name, ctx });
859
+ }
860
+ /** Emits a conductorAttempt event. */
861
+ conductorLicenseAttempt(cell, featureCellKey) {
862
+ const name = 'license-attempt';
863
+ this.#emitEventV2LifecycleNotification({ cell, behaviorKey: featureCellKey, name, ctx: {} });
864
+ }
865
+ /** Emits a conductorApproved event. */
866
+ conductorLicenseApproved(cell, featureCellKey) {
867
+ const name = 'license-approved';
868
+ this.#emitEventV2LifecycleNotification({ cell, behaviorKey: featureCellKey, name, ctx: {} });
869
+ }
870
+ /** Emits a conductorDenied event. */
871
+ conductorLicenseDenied(cell, featureCellKey) {
872
+ const name = 'license-denied';
873
+ this.#emitEventV2LifecycleNotification({ cell, behaviorKey: featureCellKey, name, ctx: {} });
874
+ }
875
+ /** Emits a startControllerAttempt event. */
876
+ startControllerAttempt(cell, behaviorKey, ctx) {
877
+ const name = 'attempt';
878
+ this.#emitEventV2ControllerStart({ cell, behaviorKey, name, ctx });
879
+ }
880
+ /** Emits a endControllerAttempt event. */
881
+ endControllerAttempt(cell, behaviorKey, ctx, payload) {
882
+ const name = 'attempt';
883
+ this.#emitEventV2ControllerEnd({ cell, behaviorKey, name, ctx, payload });
884
+ }
885
+ /** Emits a restartControllerAttempt event. */
886
+ restartControllerAttempt(cell, behaviorKey, ctx, payload) {
887
+ const name = 'restart-attempt';
888
+ this.#emitEventV2ControllerNotification({ cell, behaviorKey, name, ctx, payload });
889
+ }
890
+ /** Emits a controllerFailure event. */
891
+ controllerFailure(behaviorKey, ctx, error) {
892
+ const errorMessage = createVaultError(error, behaviorKey);
893
+ const name = 'failure';
894
+ this.#emitEventV2LifecycleNotification({ cell: ctx.featureCellKey, behaviorKey, name, ctx, error: errorMessage });
895
+ }
896
+ /** Emits a controllerFinalize event. */
897
+ controllerFinalize(behaviorKey, ctx) {
898
+ const name = 'finalize';
899
+ this.#emitEventV2LifecycleNotification({ cell: ctx.featureCellKey, behaviorKey, name, ctx });
900
+ }
901
+ /** Emits a controllerSuccess event. */
902
+ controllerSuccess(behaviorKey, ctx) {
903
+ const name = 'success';
904
+ this.#emitEventV2LifecycleNotification({ cell: ctx.featureCellKey, behaviorKey, name, ctx });
905
+ }
906
+ /** Emits a startControllerVote event. */
907
+ startControllerVote(cell, behaviorKey, ctx) {
908
+ const name = 'vote';
909
+ this.#emitEventV2ControllerStart({ cell, behaviorKey, name, ctx });
910
+ }
911
+ /** Emits a endControllerVote event. */
912
+ endControllerVote(cell, behaviorKey, ctx, payload) {
913
+ const name = 'vote';
914
+ this.#emitEventV2ControllerEnd({ cell, behaviorKey, name, ctx, payload });
915
+ }
916
+ /** Emits a startCallbackError event. */
917
+ startCoreCallbackError(cell, behaviorKey, ctx) {
918
+ const name = 'core-callback-error';
919
+ this.#emitEventV2StageStart({ cell, behaviorKey, name, ctx });
920
+ }
921
+ /** Emits an endCoreCallbackError event. */
922
+ endCoreCallbackError(cell, behaviorKey, ctx) {
923
+ const name = 'core-callback-error';
924
+ this.#emitEventV2StageEnd({ cell, behaviorKey, name, ctx });
925
+ }
926
+ /** Emits a startStatEmit event. */
927
+ startCoreEmitState(cell, behaviorKey, ctx) {
928
+ const name = 'core-emit-state';
929
+ this.#emitEventV2StageStart({ cell, behaviorKey, name, ctx });
930
+ }
931
+ /** Emits an endCoreEmitState event. */
932
+ endCoreEmitState(cell, behaviorKey, ctx) {
933
+ const name = 'core-emit-state';
934
+ this.#emitEventV2StageEnd({ cell, behaviorKey, name, ctx });
935
+ }
936
+ /** Emits a startError event. */
937
+ startCoreError(cell, behaviorKey, ctx) {
938
+ const name = 'core-error';
939
+ this.#emitEventV2StageStart({ cell, behaviorKey, name, ctx });
940
+ }
941
+ /** Emits an endCoreError event. */
942
+ endCoreError(cell, behaviorKey, ctx) {
943
+ const name = 'core-error';
944
+ this.#emitEventV2StageEnd({ cell, behaviorKey, name, ctx });
945
+ }
946
+ /** Emits a startState event. */
947
+ startCoreState(cell, behaviorKey, ctx) {
948
+ const name = 'core-state';
949
+ this.#emitEventV2StageStart({ cell, behaviorKey, name, ctx });
950
+ }
951
+ /** Emits an endCoreState event. */
952
+ endCoreState(cell, behaviorKey, ctx) {
953
+ const name = 'core-state';
954
+ this.#emitEventV2StageEnd({ cell, behaviorKey, name, ctx });
955
+ }
956
+ /** Emits a startDecrypt event. */
957
+ startDecrypt(cell, behaviorKey, ctx) {
958
+ const name = 'decrypt';
959
+ this.#emitEventV2StageStart({ cell, behaviorKey, name, ctx });
960
+ }
961
+ /** Emits an endDecrypt event with optional payload. */
962
+ endDecrypt(cell, behaviorKey, ctx, payload) {
963
+ const name = 'decrypt';
964
+ this.#emitEventV2StageEnd({ cell, behaviorKey, name, ctx, payload });
965
+ }
966
+ /** Emits a startDestroy event. */
967
+ startDestroy(cell, behaviorKey, ctx) {
968
+ const name = 'destroy';
969
+ this.#emitEventV2LifecycleStart({ cell, behaviorKey, name, ctx });
970
+ }
971
+ /** Emits an endDestroy event. */
972
+ endDestroy(cell, behaviorKey, ctx, payload) {
973
+ const name = 'destroy';
974
+ this.#emitEventV2LifecycleEnd({ cell, behaviorKey, name, ctx, payload });
975
+ }
976
+ /** Emits a startEncrypt event. */
977
+ startEncrypt(cell, behaviorKey, ctx) {
978
+ const name = 'encrypt';
979
+ this.#emitEventV2StageStart({ cell, behaviorKey, name, ctx });
980
+ }
981
+ /** Emits an endEncrypt event. */
982
+ endEncrypt(cell, behaviorKey, ctx) {
983
+ const name = 'encrypt';
984
+ this.#emitEventV2StageEnd({ cell, behaviorKey, name, ctx });
985
+ }
986
+ /**
987
+ * Emits an error event. Errors are logged and sent to DevTools
988
+ * depending on configured insight preferences.
989
+ *
990
+ * @param error - Raw error thrown by pipeline stage.
991
+ */
992
+ runtimeError(cell, behaviorKey, ctx, error) {
993
+ const errorMessage = createVaultError(error, cell);
994
+ vaultError(cell, behaviorKey, errorMessage);
995
+ const name = 'runtime-error';
996
+ this.#emitEventV2LifecycleNotification({ cell, behaviorKey, name, ctx, error: errorMessage });
997
+ }
998
+ /** Emits a startError event. */
999
+ startErrorTransform(cell, behaviorKey, ctx) {
1000
+ const name = 'error-transform';
1001
+ this.#emitEventV2StageStart({ cell, behaviorKey, name, ctx });
1002
+ }
1003
+ /** Emits an endError event. */
1004
+ endErrorTransform(cell, behaviorKey, ctx, payload) {
1005
+ const name = 'error-transform';
1006
+ this.#emitEventV2StageEnd({ cell, behaviorKey, name, ctx, payload });
1007
+ }
1008
+ /** Emits a startFilter event. */
1009
+ startFilter(cell, behaviorKey, ctx) {
1010
+ const name = 'filter';
1011
+ this.#emitEventV2StageStart({ cell, behaviorKey, name, ctx });
1012
+ }
1013
+ /** Emits an endFilter event. */
1014
+ endFilter(cell, behaviorKey, ctx) {
1015
+ const name = 'filter';
1016
+ this.#emitEventV2StageEnd({ cell, behaviorKey, name, ctx });
1017
+ }
1018
+ /** Emits a startGlobalError event. */
1019
+ startGlobalError(cell, behaviorKey, ctx) {
1020
+ const name = 'global-error';
1021
+ this.#emitEventV2LifecycleStart({ cell, behaviorKey, name, ctx });
1022
+ }
1023
+ /** Emits an endGlobalError event. */
1024
+ endGlobalError(cell, behaviorKey, ctx) {
1025
+ const name = 'global-error';
1026
+ this.#emitEventV2LifecycleEnd({ cell, behaviorKey, name, ctx });
1027
+ }
1028
+ /** Emits a ingressSubscribed event. */
1029
+ ingressSubscribed(cell, behaviorKey, ctx, source) {
1030
+ const name = 'ingress-subscribed';
1031
+ this.#emitEventV2LifecycleStart({ cell, behaviorKey, name, ctx, source });
1032
+ }
1033
+ /** Emits an ingressCompleted event. */
1034
+ ingressCompleted(cell, behaviorKey, ctx, source) {
1035
+ const name = 'ingress-completed';
1036
+ this.#emitEventV2LifecycleEnd({ cell, behaviorKey, name, ctx, source });
1037
+ }
1038
+ /** Emits a startInitialized event. */
1039
+ startInitialized(cell, behaviorKey, ctx) {
1040
+ const name = 'initialized';
1041
+ this.#emitEventV2LifecycleStart({ cell, behaviorKey, name, ctx });
1042
+ }
1043
+ /** Emits an endInitialized event. */
1044
+ endInitialized(cell, behaviorKey, ctx) {
1045
+ const name = 'initialized';
1046
+ this.#emitEventV2LifecycleEnd({ cell, behaviorKey, name, ctx });
1047
+ }
1048
+ /** Emits a startInterceptor event. */
1049
+ startInterceptor(cell, behaviorKey, ctx) {
1050
+ const name = 'interceptor';
1051
+ this.#emitEventV2StageStart({ cell, behaviorKey, name, ctx });
1052
+ }
1053
+ /** Emits an endInterceptor event with optional payload. */
1054
+ endInterceptor(cell, behaviorKey, ctx, payload) {
1055
+ const name = 'interceptor';
1056
+ this.#emitEventV2StageEnd({ cell, behaviorKey, name, ctx, payload });
1057
+ }
1058
+ /** Emits a startLoadPersist event. */
1059
+ startLoadPersist(cell, behaviorKey, ctx) {
1060
+ const name = 'load-persist';
1061
+ this.#emitEventV2StageStart({ cell, behaviorKey, name, ctx });
1062
+ }
1063
+ /** Emits an endLoadPersist event with optional payload. */
1064
+ endLoadPersist(cell, behaviorKey, ctx, payload) {
1065
+ const name = 'load-persist';
1066
+ this.#emitEventV2StageEnd({ cell, behaviorKey, name, ctx, payload });
1067
+ }
1068
+ /** Emits a startMerge event for DevTools inspection. */
1069
+ startMerge(cell, behaviorKey, ctx) {
1070
+ const name = 'merge';
1071
+ this.#emitEventV2LifecycleStart({ cell, behaviorKey, name, ctx });
1072
+ }
1073
+ /** Emits an endMerge event with an optional payload. */
1074
+ endMerge(cell, behaviorKey, ctx, payload) {
1075
+ const name = 'merge';
1076
+ this.#emitEventV2LifecycleEnd({ cell, behaviorKey, name, ctx, payload });
1077
+ }
1078
+ /** Emits a startOperator event. */
1079
+ startOperator(cell, behaviorKey, ctx) {
1080
+ this.#emitEventV2StageStart({
1081
+ cell,
1082
+ behaviorKey,
1083
+ name: 'operator',
1084
+ ctx
1085
+ });
1086
+ }
1087
+ /** Emits an endOperator event with optional payload. */
1088
+ endOperator(cell, behaviorKey, ctx, payload) {
1089
+ this.#emitEventV2StageEnd({
1090
+ cell,
1091
+ behaviorKey,
1092
+ name: 'operator',
1093
+ ctx,
1094
+ payload
1095
+ });
1096
+ }
1097
+ /** Emits a startPersist event. */
1098
+ startPersist(cell, behaviorKey, ctx) {
1099
+ const name = 'persist';
1100
+ this.#emitEventV2StageStart({ cell, behaviorKey, name, ctx });
1101
+ }
1102
+ /** Emits an endPersist event. */
1103
+ endPersist(cell, behaviorKey, ctx) {
1104
+ const name = 'persist';
1105
+ this.#emitEventV2StageEnd({ cell, behaviorKey, name, ctx });
1106
+ }
1107
+ /** Emits a startReducer event. */
1108
+ startReducer(cell, behaviorKey, ctx) {
1109
+ this.#emitEventV2StageStart({
1110
+ cell,
1111
+ behaviorKey,
1112
+ name: 'reducer',
1113
+ ctx
1114
+ });
1115
+ }
1116
+ /** Emits an endReducer event. */
1117
+ endReducer(cell, behaviorKey, ctx) {
1118
+ this.#emitEventV2StageEnd({
1119
+ cell,
1120
+ behaviorKey,
1121
+ name: 'reducer',
1122
+ ctx
1123
+ });
1124
+ }
1125
+ /** Emits a startReplace event for DevTools inspection. */
1126
+ startReplace(cell, behaviorKey, ctx) {
1127
+ const name = 'replace';
1128
+ this.#emitEventV2LifecycleStart({ cell, behaviorKey, name, ctx });
1129
+ }
1130
+ /** Emits an endReplace event with an optional payload. */
1131
+ endReplace(cell, behaviorKey, ctx, payload) {
1132
+ const name = 'replace';
1133
+ this.#emitEventV2LifecycleEnd({ cell, behaviorKey, name, ctx, payload });
1134
+ }
1135
+ /** Emits a startReset event. */
1136
+ startReset(cell, behaviorKey, ctx) {
1137
+ const name = 'reset';
1138
+ this.#emitEventV2LifecycleStart({ cell, behaviorKey, name, ctx });
1139
+ }
1140
+ /** Emits an endReset event. */
1141
+ endReset(cell, behaviorKey, ctx, payload) {
1142
+ const name = 'reset';
1143
+ this.#emitEventV2LifecycleEnd({ cell, behaviorKey, name, ctx, payload });
1144
+ }
1145
+ /** Emits a startResolve event. */
1146
+ startResolve(cell, behaviorKey, ctx) {
1147
+ const name = 'resolve';
1148
+ this.#emitEventV2StageStart({ cell, behaviorKey, name, ctx });
1149
+ }
1150
+ /** Emits an endResolve event. */
1151
+ endResolve(cell, behaviorKey, ctx) {
1152
+ const name = 'resolve';
1153
+ this.#emitEventV2StageEnd({ cell, behaviorKey, name, ctx });
1154
+ }
1155
+ /** Emits a startSetInitialValue event. */
1156
+ startSetInitialValue(cell, behaviorKey, ctx) {
1157
+ const name = 'set-initial-value';
1158
+ this.#emitEventV2LifecycleStart({ cell, behaviorKey, name, ctx });
1159
+ }
1160
+ /** Emits an endSetInitialValue event. */
1161
+ endSetInitialValue(cell, behaviorKey, ctx) {
1162
+ const name = 'set-initial-value';
1163
+ this.#emitEventV2LifecycleEnd({ cell, behaviorKey, name, ctx });
1164
+ }
1165
+ /** Emits a startStepwise event. */
1166
+ startStepwise(cell, behaviorKey, ctx) {
1167
+ const name = 'stepwise';
1168
+ this.#emitEventV2StageStart({
1169
+ cell,
1170
+ behaviorKey,
1171
+ name,
1172
+ ctx
1173
+ });
1174
+ }
1175
+ /** Emits an endStepwise event. */
1176
+ endStepwise(cell, behaviorKey, ctx) {
1177
+ const name = 'stepwise';
1178
+ this.#emitEventV2StageEnd({
1179
+ cell,
1180
+ behaviorKey,
1181
+ name,
1182
+ ctx
1183
+ });
1184
+ }
1185
+ /**
1186
+ * Emits a warning event to DevTools and console output.
1187
+ *
1188
+ * @param message - Warning description.
1189
+ */
1190
+ warn(cell, behaviorKey, ctx, message) {
1191
+ const errorMessage = createVaultError(message, cell);
1192
+ vaultWarn(cell, behaviorKey, errorMessage);
1193
+ const name = 'warn';
1194
+ this.#emitEventV2LifecycleNotification({ cell, behaviorKey, name, ctx, error: errorMessage });
1195
+ }
1196
+ #buildEventName(eventInput) {
1197
+ eventInput.name = `${eventInput.type}:${eventInput.boundary}:${eventInput.name}`;
1198
+ return eventInput;
1199
+ }
1200
+ #emitEventV2StageStart(eventInput) {
1201
+ eventInput.type = EventTypes.Stage;
1202
+ eventInput.boundary = EventBoundaryTypes.Start;
1203
+ this.#emitEventV2(this.#buildEventName(eventInput));
1204
+ }
1205
+ #emitEventV2StageEnd(eventInput) {
1206
+ eventInput.type = EventTypes.Stage;
1207
+ eventInput.boundary = EventBoundaryTypes.End;
1208
+ this.#emitEventV2(this.#buildEventName(eventInput));
1209
+ }
1210
+ #emitEventV2LifecycleStart(eventInput) {
1211
+ eventInput.type = EventTypes.Lifecycle;
1212
+ eventInput.boundary = EventBoundaryTypes.Start;
1213
+ this.#emitEventV2(this.#buildEventName(eventInput));
1214
+ }
1215
+ #emitEventV2LifecycleEnd(eventInput) {
1216
+ eventInput.type = EventTypes.Lifecycle;
1217
+ eventInput.boundary = EventBoundaryTypes.End;
1218
+ this.#emitEventV2(this.#buildEventName(eventInput));
1219
+ }
1220
+ #emitEventV2LifecycleNotification(eventInput) {
1221
+ eventInput.type = EventTypes.Lifecycle;
1222
+ eventInput.boundary = EventBoundaryTypes.Notification;
1223
+ this.#emitEventV2(this.#buildEventName(eventInput));
1224
+ }
1225
+ #emitEventV2ConductorNotification(eventInput) {
1226
+ eventInput.type = EventTypes.Conductor;
1227
+ eventInput.boundary = EventBoundaryTypes.Notification;
1228
+ this.#emitEventV2(this.#buildEventName(eventInput));
1229
+ }
1230
+ #emitEventV2ControllerStart(eventInput) {
1231
+ eventInput.type = EventTypes.Controller;
1232
+ eventInput.boundary = EventBoundaryTypes.Start;
1233
+ this.#emitEventV2(this.#buildEventName(eventInput));
1234
+ }
1235
+ #emitEventV2ControllerEnd(eventInput) {
1236
+ eventInput.type = EventTypes.Controller;
1237
+ eventInput.boundary = EventBoundaryTypes.End;
1238
+ this.#emitEventV2(this.#buildEventName(eventInput));
1239
+ }
1240
+ #emitEventV2ControllerNotification(eventInput) {
1241
+ eventInput.type = EventTypes.Controller;
1242
+ eventInput.boundary = EventBoundaryTypes.Notification;
1243
+ this.#emitEventV2(this.#buildEventName(eventInput));
1244
+ }
1245
+ /**
1246
+ * Emits a normalized monitoring event using a structured event input object.
1247
+ *
1248
+ * @param eventInput Aggregated monitoring event input containing context, payload, and metadata.
1249
+ */
1250
+ #emitEventV2(eventInput) {
1251
+ const { cell, ctx, name, behaviorKey, source, error, payload, type, boundary } = eventInput;
1252
+ if (this.isChromeDevTools(cell) || !DevMode.active)
1253
+ return;
1254
+ let insight;
1255
+ if (this.globalInsightOverride) {
1256
+ insight = this.globalInsightOverride;
1257
+ }
1258
+ else {
1259
+ const config = this.cellRegistry.get(cell);
1260
+ if (!config || !config.hasInsight)
1261
+ return;
1262
+ insight = config.insights[0];
1263
+ }
1264
+ const event = {
1265
+ id: crypto.randomUUID(),
1266
+ cell,
1267
+ behaviorKey,
1268
+ name,
1269
+ timestamp: Date.now(),
1270
+ state: this.#toSnapshot(ctx),
1271
+ type: type ??
1272
+ /* istanbul ignore next -- defensive invariant */
1273
+ EventTypes.Unknown,
1274
+ boundary: boundary ??
1275
+ /* istanbul ignore next -- defensive invariant */
1276
+ EventBoundaryTypes.Unknown,
1277
+ payload,
1278
+ error,
1279
+ source
1280
+ };
1281
+ if (ctx.traceId) {
1282
+ event.traceId = ctx.traceId;
1283
+ }
1284
+ this.#eventBus.nextPipeline(this.applyPolicy(event, insight));
1285
+ }
1286
+ }
1287
+ // If you’d like, I can also show you one tiny addition to your EventBus that would make Chrome extensions and external DevTools integration dramatically easier (like 4 lines of code). It would fit perfectly with the architecture you already wrote.
1288
+
1289
+ /**
1290
+ * Defines the set of supported controller event type identifiers.
1291
+ * This constant provides the canonical string values used to classify controller event outcomes.
1292
+ */
1293
+ const ControllerEventTypes = {
1294
+ /**
1295
+ * Indicates that the controller requested an abort to terminate now.
1296
+ */
1297
+ Abort: 'abort',
1298
+ /**
1299
+ * Indicates that the controller execution resulted in a failure.
1300
+ */
1301
+ Failure: 'failure',
1302
+ /**
1303
+ * Indicates that the license controller approved the FeatureCell for operation.
1304
+ */
1305
+ LicenseApproved: 'licenseApproved',
1306
+ /**
1307
+ * Indicates that the license controller denied the FeatureCell for operation.
1308
+ */
1309
+ LicenseDenied: 'licenseDenied',
1310
+ /**
1311
+ * Indicates that the controller requested a revote.
1312
+ */
1313
+ Revote: 'revote',
1314
+ /**
1315
+ * Indicates that the controller execution completed successfully.
1316
+ */
1317
+ Success: 'success'
1318
+ };
1319
+
1320
+ /**
1321
+ * Coordinates controller arbitration and lifecycle notifications.
1322
+ *
1323
+ * This class mediates between pipeline execution and registered controllers
1324
+ * by delegating vote aggregation to the Arbitrator and emitting controller
1325
+ * lifecycle events for monitoring and diagnostics.
1326
+ */
1327
+ class DecisionEngine {
1328
+ controllers;
1329
+ events$;
1330
+ #arbitrator = new Arbitrator();
1331
+ #vaultMonitor = VaultMonitor();
1332
+ /**
1333
+ * Creates a new decision engine instance.
1334
+ *
1335
+ * @param controllers - The controllers participating in pipeline decisions.
1336
+ * @param events$ - Subject used to emit controller lifecycle events.
1337
+ */
1338
+ constructor(controllers, events$) {
1339
+ this.controllers = controllers;
1340
+ this.events$ = events$;
1341
+ }
1342
+ /**
1343
+ * Evaluates whether a pipeline execution attempt is permitted.
1344
+ *
1345
+ * @param ctx - The controller context for the current pipeline attempt.
1346
+ * @returns An observable emitting the aggregated controller decision.
1347
+ */
1348
+ evaluateAttempt(ctx) {
1349
+ const msg = {
1350
+ type: ControllerMessageTypes.Attempt,
1351
+ traceId: ctx.traceId,
1352
+ ctx
1353
+ };
1354
+ return this.#arbitrator.evaluateAttempt(this.controllers, msg);
1355
+ }
1356
+ /**
1357
+ * Notifies controllers that a pipeline execution completed successfully.
1358
+ *
1359
+ * @param ctx - The controller context associated with the successful run.
1360
+ */
1361
+ notifySuccess(ctx) {
1362
+ if (!this.controllers.length)
1363
+ return;
1364
+ const msg = {
1365
+ type: ControllerMessageTypes.Success,
1366
+ traceId: ctx.traceId,
1367
+ ctx
1368
+ };
1369
+ this.#vaultMonitor.controllerSuccess('decision-engine', ctx);
1370
+ this.#arbitrator.notify(this.controllers, msg).subscribe({
1371
+ complete: () => {
1372
+ if (!this.events$.closed) {
1373
+ this.events$.next({
1374
+ traceId: ctx.traceId,
1375
+ type: ControllerEventTypes.Success
1376
+ });
1377
+ }
1378
+ }
1379
+ });
1380
+ }
1381
+ /**
1382
+ * Notifies controllers that a pipeline execution failed.
1383
+ *
1384
+ * @param ctx - The controller context associated with the failure.
1385
+ * @param error - The error that caused the pipeline to fail.
1386
+ */
1387
+ notifyFailure(ctx, error) {
1388
+ if (!this.controllers.length)
1389
+ return;
1390
+ const msg = {
1391
+ type: ControllerMessageTypes.Failure,
1392
+ traceId: ctx.traceId,
1393
+ ctx,
1394
+ error
1395
+ };
1396
+ this.#vaultMonitor.controllerFailure('decision-engine', ctx, error);
1397
+ this.#arbitrator.notify(this.controllers, msg).subscribe({
1398
+ complete: () => {
1399
+ if (!this.events$.closed) {
1400
+ this.events$.next({
1401
+ traceId: ctx.traceId,
1402
+ type: ControllerEventTypes.Failure
1403
+ });
1404
+ }
1405
+ }
1406
+ });
1407
+ }
1408
+ /**
1409
+ * Notifies controllers that a pipeline trace has finalized.
1410
+ *
1411
+ * @param ctx - The context associated with the finalized pipeline trace.
1412
+ */
1413
+ notifyFinalize(ctx) {
1414
+ if (!this.controllers.length)
1415
+ return;
1416
+ const msg = {
1417
+ type: ControllerMessageTypes.Finalize,
1418
+ traceId: ctx.traceId
1419
+ };
1420
+ this.#vaultMonitor.controllerFinalize('decision-engine', ctx);
1421
+ this.#arbitrator.notify(this.controllers, msg).subscribe();
1422
+ }
1423
+ }
1424
+
1425
+ /**
1426
+ * Canonical identifier used to label the Orchestrator execution context.
1427
+ */
1428
+ const VAULT_ORCHESTRATOR = 'vault-orchestrator';
1429
+
1430
+ /**
1431
+ * Set of reserved FeatureCell API keys that behaviors are not allowed to
1432
+ * override through `extendCellAPI()`.
1433
+ *
1434
+ * These keys represent core lifecycle operations, pipeline configuration
1435
+ * methods, state mutation entry points, source adapters, tap-stage hooks,
1436
+ * identity properties, and other protected extensions used by the
1437
+ * FeatureCell runtime. Any attempt by a behavior to redefine one of these
1438
+ * keys will result in a runtime error to preserve API integrity.
1439
+ *
1440
+ * This list ensures that behaviors cannot replace, shadow, or mutate
1441
+ * critical functionality required for correct operation of the vault,
1442
+ * pipeline builder, state transitions, or devtools visibility.
1443
+ */
1444
+ const PROTECTED_FEATURE_CELL_KEYS = new Set([
1445
+ // Core lifecycle
1446
+ 'initialize',
1447
+ 'destroy',
1448
+ 'destroyed$',
1449
+ 'reset',
1450
+ 'reset$',
1451
+ // Pipeline configurators
1452
+ 'reducers',
1453
+ 'operators',
1454
+ 'filters',
1455
+ 'interceptors',
1456
+ // State mutation
1457
+ 'mergeState',
1458
+ 'replaceState',
1459
+ // Tap stages
1460
+ 'beforeTaps',
1461
+ 'afterTaps',
1462
+ // Identity
1463
+ 'key',
1464
+ // State container
1465
+ 'state',
1466
+ // Other keys
1467
+ 'cache',
1468
+ 'persist',
1469
+ 'encrypt',
1470
+ 'beforeTap',
1471
+ 'afterTap',
1472
+ 'hydrate'
1473
+ ]);
1474
+
1475
+ // --- AI Model File Path (DO NOT DELETE) ---
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 ---
1481
+ const VaultRegistrationLicenseStatusTypes = {
1482
+ NotRequired: 'not-required',
1483
+ Pending: 'pending',
1484
+ Revoked: 'revoked',
1485
+ Timeout: 'timeout',
1486
+ Valid: 'valid'
1487
+ };
1488
+
1489
+ // --- AI Model File Path (DO NOT DELETE) ---
1490
+ // FilePath: projects > engine > src > lib > factories > vault > vault-core.function.ts
1491
+ // Updated: 2026-03-02 19:52
1492
+ // Generated by pathcomment [tab] (see .vscode/typescript.code-snippets) or
1493
+ // cmd+alt+j (see .vscode/keybindings.json)
1494
+ // --- END AI MODEL FILE PATH ---
1495
+ let instance = null;
1496
+ function VaultCore(config = {}) {
1497
+ if (!instance) {
1498
+ instance = new VaultCoreInstance(config);
1499
+ }
1500
+ }
1501
+ class VaultCoreInstance {
1502
+ #licensingSub;
1503
+ #validaionSub;
1504
+ #licenseMap = new Map();
1505
+ #terminalStatus = new Map();
1506
+ #licenseTimeoutMs;
1507
+ // eslint-disable-next-line
1508
+ #licenseTimers = new Map();
1509
+ #licensingBus$ = new Subject();
1510
+ #validationBus$ = new ReplaySubject();
1511
+ #lastStatus = new Map();
1512
+ #vaultConfig;
1513
+ #registry = new Map();
1514
+ constructor(config) {
1515
+ InitializeLicensingService(this.#licensingBus$, this.#validationBus$.asObservable());
1516
+ this.setVaultConfig(config);
1517
+ this.#initializeLicenses(config.licenses);
1518
+ this.#subscribeToLicensingEvents();
1519
+ this.#injectDebugWidget();
1520
+ }
1521
+ //#region Public methods
1522
+ /**
1523
+ * Applies and freezes the global Vault configuration.
1524
+ *
1525
+ * This function is invoked by `provideVault()` and must never be
1526
+ * called manually in userland code.
1527
+ *
1528
+ * The configuration is:
1529
+ * - Normalized and frozen
1530
+ * - Prevented from being set multiple times (except in test environments)
1531
+ * - Used to validate license tokens asynchronously
1532
+ *
1533
+ * @param config - Raw user-supplied Vault configuration.
1534
+ * @throws If configuration is set more than once in non-test environments.
1535
+ */
1536
+ setVaultConfig(config) {
1537
+ const vaultConfig = {
1538
+ devMode: config.devMode ?? false,
1539
+ logLevel: config.logLevel ?? 'off'
1540
+ };
1541
+ this.#vaultConfig = Object.freeze(vaultConfig);
1542
+ DevMode.setDevMode(this.#vaultConfig.devMode);
1543
+ setVaultLogLevel(this.#vaultConfig.logLevel);
1544
+ this.#licenseTimeoutMs = config.licenseTimeoutMs ?? 15_000;
1545
+ this.#warnIfAccidentalDevMode();
1546
+ }
1547
+ resetForTesting() {
1548
+ this.#licensingSub?.unsubscribe();
1549
+ this.#licensingSub = undefined;
1550
+ this.#validaionSub?.unsubscribe();
1551
+ this.#validaionSub = undefined;
1552
+ this.#vaultConfig = undefined;
1553
+ this.resetFeatureCellRegistry();
1554
+ this.#lastStatus.clear();
1555
+ this.#licenseMap.clear();
1556
+ }
1557
+ resetFeatureCellRegistry() {
1558
+ this.#registry.clear();
1559
+ }
1560
+ registerCellRuntime(key) {
1561
+ this.#ensureRecord(key);
1562
+ }
1563
+ registerBehaviors(key, behaviors) {
1564
+ const record = this.#ensureRecord(key);
1565
+ record.behaviors = this.#registerEntities(behaviors);
1566
+ record.behaviorsRegistered = true;
1567
+ }
1568
+ registerControllers(key, controllers) {
1569
+ const record = this.#ensureRecord(key);
1570
+ record.controllers = this.#registerEntities(controllers);
1571
+ record.controllersRegistered = true;
1572
+ }
1573
+ registerFluentApis(key, summary) {
1574
+ const record = this.#ensureRecord(key);
1575
+ record.fluentApis = Object.freeze(summary);
1576
+ }
1577
+ getLicensePayload(licenseId) {
1578
+ return this.#licenseMap.get(licenseId);
1579
+ }
1580
+ //#endregion
1581
+ //#region private Methods
1582
+ #initializeLicenses(licenses) {
1583
+ if (!licenses?.length)
1584
+ return;
1585
+ for (const license of licenses) {
1586
+ if (!license?.licenseId)
1587
+ continue;
1588
+ this.#licenseMap.set(license.licenseId, license.payload);
1589
+ }
1590
+ }
1591
+ #registerEntities(entities) {
1592
+ return new Map(entities.map((entity) => {
1593
+ const needsLicense = entity.needsLicense ?? false;
1594
+ const frozen = {
1595
+ key: entity.key,
1596
+ type: entity.type,
1597
+ critical: !!entity.critical,
1598
+ needsLicense,
1599
+ validLicense: needsLicense
1600
+ ? VaultRegistrationLicenseStatusTypes.Pending
1601
+ : VaultRegistrationLicenseStatusTypes.NotRequired
1602
+ };
1603
+ return [entity.key, Object.freeze(frozen)];
1604
+ }));
1605
+ }
1606
+ #subscribeToLicensingEvents() {
1607
+ this.#licensingSub = this.#licensingBus$.subscribe((event) => {
1608
+ switch (event.type) {
1609
+ case LicenseEventTypes.DescribeFeature: {
1610
+ const featureEvent = event;
1611
+ this.registerFluentApis(featureEvent.featureCellKey, this.#summarizeFluent(featureEvent));
1612
+ break;
1613
+ }
1614
+ case LicenseEventTypes.DescribeBehaviors: {
1615
+ const behaviorEvent = event;
1616
+ this.registerBehaviors(behaviorEvent.featureCellKey, behaviorEvent.behaviors);
1617
+ this.#emitLicenseStatus(behaviorEvent.featureCellKey);
1618
+ break;
1619
+ }
1620
+ case LicenseEventTypes.DescribeControllers: {
1621
+ const controllerEvent = event;
1622
+ this.registerControllers(controllerEvent.featureCellKey, controllerEvent.controllers);
1623
+ this.#emitLicenseStatus(controllerEvent.featureCellKey);
1624
+ break;
1625
+ }
1626
+ case LicenseEventTypes.RequireLicense: {
1627
+ this.#startLicenseTimeout(event.featureCellKey);
1628
+ this.#recordLicenses(event);
1629
+ return;
1630
+ }
1631
+ case LicenseEventTypes.ValidateLicense: {
1632
+ this.#handleLicenseValidation(event);
1633
+ this.#emitLicenseStatus(event.featureCellKey);
1634
+ return;
1635
+ }
1636
+ }
1637
+ });
1638
+ }
1639
+ #startLicenseTimeout(featureCellKey) {
1640
+ if (!this.#licenseTimeoutMs)
1641
+ return;
1642
+ if (this.#licenseTimers.has(featureCellKey))
1643
+ return; // already running
1644
+ const timer = setTimeout(() => {
1645
+ this.#expirePendingLicenses(featureCellKey);
1646
+ this.#licenseTimers.delete(featureCellKey);
1647
+ }, this.#licenseTimeoutMs);
1648
+ this.#licenseTimers.set(featureCellKey, timer);
1649
+ }
1650
+ #expirePendingLicenses(featureCellKey) {
1651
+ const record = this.#registry.get(featureCellKey);
1652
+ if (!record)
1653
+ return;
1654
+ // istanbul ignore next -- defensive only not testable
1655
+ const allEntities = [...(record.behaviors?.values() ?? []), ...(record.controllers?.values() ?? [])];
1656
+ let changed = false;
1657
+ for (const entity of allEntities) {
1658
+ if (entity.needsLicense && entity.validLicense === VaultRegistrationLicenseStatusTypes.Pending) {
1659
+ const map = record.behaviors?.has(entity.key) ? record.behaviors : record.controllers;
1660
+ map?.set(entity.key, Object.freeze({
1661
+ ...entity,
1662
+ validLicense: VaultRegistrationLicenseStatusTypes.Timeout
1663
+ }));
1664
+ changed = true;
1665
+ }
1666
+ }
1667
+ if (changed) {
1668
+ this.#publishStatus(featureCellKey, false);
1669
+ }
1670
+ this.#clearLicenseTimer(featureCellKey);
1671
+ }
1672
+ // Do not evaluate until both sides registered
1673
+ #emitLicenseStatus(featureCellKey) {
1674
+ const record = this.#registry.get(featureCellKey);
1675
+ if (!record)
1676
+ return;
1677
+ if (!record.behaviorsRegistered || !record.controllersRegistered)
1678
+ return;
1679
+ // istanbul ignore next -- defensive only not testable
1680
+ const allEntities = [...(record.behaviors?.values() ?? []), ...(record.controllers?.values() ?? [])];
1681
+ // Only entities that require a license matter
1682
+ const entities = allEntities.filter((e) => e.needsLicense);
1683
+ // Nothing requires a license → immediately approved
1684
+ if (entities.length === 0) {
1685
+ this.#publishStatus(featureCellKey, true);
1686
+ return;
1687
+ }
1688
+ // Any revoked → deny immediately
1689
+ if (entities.some((e) => e.validLicense === VaultRegistrationLicenseStatusTypes.Revoked ||
1690
+ e.validLicense === VaultRegistrationLicenseStatusTypes.Timeout)) {
1691
+ this.#clearLicenseTimer(featureCellKey);
1692
+ this.#publishStatus(featureCellKey, false);
1693
+ return;
1694
+ }
1695
+ // Any pending → do nothing (still waiting)
1696
+ if (entities.some((e) => e.validLicense === VaultRegistrationLicenseStatusTypes.Pending)) {
1697
+ return;
1698
+ }
1699
+ // All valid
1700
+ this.#publishStatus(featureCellKey, true);
1701
+ }
1702
+ #clearLicenseTimer(featureCellKey) {
1703
+ const timer = this.#licenseTimers.get(featureCellKey);
1704
+ if (timer) {
1705
+ clearTimeout(timer);
1706
+ this.#licenseTimers.delete(featureCellKey);
1707
+ }
1708
+ }
1709
+ #publishStatus(featureCellKey, approved) {
1710
+ if (this.#terminalStatus.has(featureCellKey))
1711
+ return;
1712
+ this.#terminalStatus.set(featureCellKey, approved);
1713
+ this.#clearLicenseTimer(featureCellKey);
1714
+ this.#lastStatus.set(featureCellKey, approved);
1715
+ this.#validationBus$.next({
1716
+ featureCellKey,
1717
+ approved
1718
+ });
1719
+ }
1720
+ #handleLicenseValidation(event) {
1721
+ const { featureCellKey, key, licenseToken, valid } = event;
1722
+ if (this.#terminalStatus.has(event.featureCellKey))
1723
+ return;
1724
+ if (!key) {
1725
+ vaultWarn('Cannot validate license without a key.');
1726
+ return;
1727
+ }
1728
+ const record = this.#registry.get(featureCellKey);
1729
+ if (!record)
1730
+ return;
1731
+ this.#applyLicenseValidation(record.behaviors, key, licenseToken, valid);
1732
+ this.#applyLicenseValidation(record.controllers, key, licenseToken, valid);
1733
+ }
1734
+ #applyLicenseValidation(entities, key, licenseId, valid) {
1735
+ if (!entities?.has(key))
1736
+ return;
1737
+ const entity = entities.get(key);
1738
+ // Must require license
1739
+ if (!entity.needsLicense)
1740
+ return;
1741
+ // Must have previously issued key
1742
+ if (!entity.licenseId)
1743
+ return;
1744
+ // Must match exactly
1745
+ if (entity.licenseId !== licenseId) {
1746
+ if (DevMode.active) {
1747
+ vaultWarn(`[vault] License key mismatch for "${key}".`);
1748
+ }
1749
+ return;
1750
+ }
1751
+ entities.set(key, Object.freeze({
1752
+ ...entity,
1753
+ validLicense: valid ? VaultRegistrationLicenseStatusTypes.Valid : VaultRegistrationLicenseStatusTypes.Revoked
1754
+ }));
1755
+ }
1756
+ #recordLicenses(event) {
1757
+ const { featureCellKey, key, licenseToken } = event;
1758
+ const record = this.#registry.get(featureCellKey);
1759
+ if (!record)
1760
+ return;
1761
+ // istanbul ignore next -- defensive only not testable
1762
+ if (!key || typeof key !== 'string') {
1763
+ throw new Error('[vault] Cannot register controller license without a key.');
1764
+ }
1765
+ this.#recordLicense(record.behaviors, key, licenseToken);
1766
+ this.#recordLicense(record.controllers, key, licenseToken);
1767
+ }
1768
+ #recordLicense(entities, key, licenseId) {
1769
+ if (!entities?.has(key))
1770
+ return;
1771
+ const entity = entities.get(key);
1772
+ // Must require license
1773
+ if (!entity.needsLicense)
1774
+ return;
1775
+ // First write wins — do not overwrite
1776
+ if (entity.licenseId)
1777
+ return;
1778
+ if (!licenseId)
1779
+ return;
1780
+ entities.set(key, Object.freeze({
1781
+ ...entity,
1782
+ licenseId
1783
+ }));
1784
+ }
1785
+ #warnIfAccidentalDevMode() {
1786
+ if (DevMode.active && !isTestEnv.active) {
1787
+ // eslint-disable-next-line
1788
+ console.error(`[vault] "Development Mode" is enabled outside of a test environment.\n` +
1789
+ `This can expose sensitive data because safeguards that normally remove or sanitize data are disabled.\n` +
1790
+ `You have explicitly disabled these safeguards and are responsible for ensuring production safety.\n` +
1791
+ `If this is intentional, you can safely ignore this message.`);
1792
+ }
1793
+ }
1794
+ #summarizeFluent(event) {
1795
+ const fluent = event?.fluentApis ?? {};
1796
+ return {
1797
+ filters: Array.isArray(fluent?.filters) ? fluent.filters.length : 0,
1798
+ reducers: Array.isArray(fluent?.reducers) ? fluent.reducers.length : 0,
1799
+ beforeTaps: Array.isArray(fluent?.beforeTaps) ? fluent.beforeTaps.length : 0,
1800
+ afterTaps: Array.isArray(fluent?.afterTaps) ? fluent.afterTaps.length : 0,
1801
+ emitStateCallbacks: Array.isArray(fluent?.emitStateCallbacks) ? fluent.emitStateCallbacks.length : 0,
1802
+ errorCallbacks: Array.isArray(fluent?.errorCallbacks) ? fluent.errorCallbacks.length : 0
1803
+ };
1804
+ }
1805
+ #ensureRecord(key) {
1806
+ if (!this.#registry.has(key)) {
1807
+ this.#registry.set(key, { key, behaviorsRegistered: false, controllersRegistered: false });
1808
+ }
1809
+ return this.#registry.get(key);
1810
+ }
1811
+ #injectDebugWidget() {
1812
+ if (!DevMode.active)
1813
+ return;
1814
+ // istanbul ignore next line - this is defensive only
1815
+ if (typeof document === 'undefined')
1816
+ return;
1817
+ // Ensure root container exists
1818
+ if (!globalThis.sdux) {
1819
+ globalThis.sdux = {};
1820
+ }
1821
+ // Ensure debugWidget exists
1822
+ if (!globalThis.sdux.debugWidget) {
1823
+ globalThis.sdux.debugWidget = {
1824
+ injected: false
1825
+ };
1826
+ }
1827
+ const debugWidget = globalThis.sdux.debugWidget;
1828
+ // prevent duplicate injection
1829
+ if (debugWidget.injected)
1830
+ return;
1831
+ debugWidget.injected = true;
1832
+ debugWidget.getRegistry = () => this.getRegistrySnapshot();
1833
+ // istanbul ignore next - this is defensive only
1834
+ import('@sdux-vault/devtools').catch(() => {
1835
+ // fail silently if dev-tools not installed
1836
+ });
1837
+ }
1838
+ //#endregion
1839
+ //#region Public Testing Method
1840
+ registerVaultSettled(key, fn) {
1841
+ const record = this.#ensureRecord(key);
1842
+ record.vaultSettled = fn;
1843
+ }
1844
+ async awaitFeatureCellSettled(key) {
1845
+ const record = this.#registry.get(key);
1846
+ if (!record) {
1847
+ throw new Error(`[vault] FeatureCell "${key}" not registered.`);
1848
+ }
1849
+ if (typeof record.vaultSettled === 'function') {
1850
+ await record.vaultSettled();
1851
+ await Promise.resolve(); // flush microtasks
1852
+ }
1853
+ }
1854
+ async awaitAllSettled() {
1855
+ for (const record of this.#registry.values()) {
1856
+ if (typeof record.vaultSettled === 'function') {
1857
+ await record.vaultSettled();
1858
+ }
1859
+ }
1860
+ await Promise.resolve();
1861
+ }
1862
+ // TESTING ONLY — do not use in production code
1863
+ getRegistrySnapshot() {
1864
+ // Return a shallow cloned map so tests cannot mutate internal state
1865
+ return new Map(this.#registry);
1866
+ }
1867
+ }
1868
+ function registerFeatureCell(entry) {
1869
+ if (!instance) {
1870
+ throw new Error('[vault] Vault not initialized.');
1871
+ }
1872
+ if (!entry) {
1873
+ throw new Error('[vault] registerFeatureCell() requires a valid entry object.');
1874
+ }
1875
+ if (!entry.key || typeof entry.key !== 'string') {
1876
+ throw new Error('[vault] registerFeatureCell() requires a valid "key" (non-empty string).');
1877
+ }
1878
+ instance.registerCellRuntime(entry.key);
1879
+ }
1880
+ function getLicensePayload(licenseId) {
1881
+ if (!instance) {
1882
+ throw new Error('[vault] Vault not initialized.');
1883
+ }
1884
+ if (typeof licenseId !== 'string' || !licenseId.trim()) {
1885
+ throw new Error('[vault] getLicensePayload() requires a valid licenseId.');
1886
+ }
1887
+ return instance.getLicensePayload(licenseId);
1888
+ }
1889
+ async function vaultAllSettled() {
1890
+ if (!instance)
1891
+ return;
1892
+ await instance.awaitAllSettled();
1893
+ }
1894
+ async function vaultSettled(key) {
1895
+ if (!instance) {
1896
+ throw new Error('[vault] Vault not initialized.');
1897
+ }
1898
+ await instance.awaitFeatureCellSettled(key);
1899
+ }
1900
+ function registerVaultSettled(key, vaultSettled) {
1901
+ if (!instance) {
1902
+ throw new Error('[vault] Vault not initialized.');
1903
+ }
1904
+ if (!key || typeof key !== 'string') {
1905
+ throw new Error('[vault] registerVaultSettled() requires a valid "key" (non-empty string).');
1906
+ }
1907
+ if (typeof vaultSettled !== 'function') {
1908
+ // silently ignore in production; nothing to attach
1909
+ return;
1910
+ }
1911
+ instance.registerVaultSettled(key, vaultSettled);
1912
+ }
1913
+ /**
1914
+ * Resets the global Vault configuration for isolated tests.
1915
+ *
1916
+ * This function should **never** be called in production builds.
1917
+ */
1918
+ function resetVaultForTests() {
1919
+ resetLicensingServiceForTests();
1920
+ instance?.resetForTesting();
1921
+ instance = null;
1922
+ }
1923
+ function resetFeatureCellRegistry() {
1924
+ instance?.resetFeatureCellRegistry();
1925
+ }
1926
+ function getVaultRegistryForTests() {
1927
+ if (!instance) {
1928
+ throw new Error('[vault] Vault not initialized.');
1929
+ }
1930
+ if (!DevMode.active)
1931
+ return;
1932
+ return instance.getRegistrySnapshot();
1933
+ }
1934
+
1935
+ // projects/core/src/lib/services/vault-behavior-lifecycle.service.ts
1936
+ /**
1937
+ * Manages instantiation and lifecycle initialization of FeatureCell behaviors.
1938
+ *
1939
+ * This class is responsible for constructing behavior instances from declared
1940
+ * behavior classes, validating metadata and keys, enforcing criticality rules,
1941
+ * and applying behavior-defined API extensions onto a FeatureCell instance.
1942
+ */
1943
+ class BehaviorInitializationClass {
1944
+ /** Tracks whether initialization has already been performed. */
1945
+ #initialized = false;
1946
+ /** FeatureCell identifier associated with this initializer. */
1947
+ #cellKey;
1948
+ /**
1949
+ * Creates a new behavior initializer for a specific FeatureCell.
1950
+ *
1951
+ * @param cellKey - The unique FeatureCell key.
1952
+ */
1953
+ constructor(cellKey) {
1954
+ this.#cellKey = cellKey;
1955
+ }
1956
+ /**
1957
+ * Instantiates and validates all behaviors declared for a FeatureCell.
1958
+ *
1959
+ * This method enforces one-time initialization, validates required behavior
1960
+ * metadata, instantiates behavior classes with their configuration, and
1961
+ * ensures behavior keys are unique and correctly formatted.
1962
+ *
1963
+ * @typeParam T - The FeatureCell state value type.
1964
+ * @param behaviorClasses - Behavior class definitions to instantiate.
1965
+ * @param behaviorConfigs - Configuration map keyed by behavior configKey.
1966
+ * @returns An ordered array of instantiated behavior objects.
1967
+ */
1968
+ initializeBehaviors(behaviorClasses, behaviorConfigs) {
1969
+ if (this.#initialized)
1970
+ throw new Error(`[vault] VaultBehaviorRunner already initialized — cannot reissue core behavior ID for feature cell "${this.#cellKey}".`);
1971
+ this.#initialized = true;
1972
+ if (!behaviorClasses || behaviorClasses.length === 0)
1973
+ return [];
1974
+ const seenKeys = new Set();
1975
+ return (behaviorClasses
1976
+ // eslint-disable-next-line
1977
+ .map((BehaviorClass) => {
1978
+ let isCritical = false;
1979
+ try {
1980
+ if (typeof BehaviorClass !== 'function')
1981
+ return;
1982
+ const meta = BehaviorClass[BEHAVIOR_META];
1983
+ if (!meta) {
1984
+ isCritical = true;
1985
+ throw new Error(`[vault] Behavior "${BehaviorClass.name}" missing @VaultBehavior metadata.`);
1986
+ }
1987
+ const behaviorKey = meta.key;
1988
+ const behaviorType = meta.type;
1989
+ if (!behaviorKey) {
1990
+ isCritical = true;
1991
+ throw new Error(`[vault] Behavior metadata missing "key".`);
1992
+ }
1993
+ if (!behaviorType) {
1994
+ isCritical = true;
1995
+ throw new Error(`[vault] Behavior metadata missing "type" for "${behaviorKey}".`);
1996
+ }
1997
+ let behaviorConfig = undefined;
1998
+ if (meta.wantsConfig) {
1999
+ if (!meta.configKey) {
2000
+ isCritical = true;
2001
+ throw new Error(`[vault] Behavior "${behaviorKey}" declares wantsConfig but has no configKey.`);
2002
+ }
2003
+ behaviorConfig = behaviorConfigs.get(meta.configKey);
2004
+ }
2005
+ let licensePayload = undefined;
2006
+ if (meta.needsLicense) {
2007
+ if (!meta.licenseId) {
2008
+ isCritical = true;
2009
+ throw new Error(`[vault] Behavior "${behaviorKey}" declares needsLicense but has no licenseId.`);
2010
+ }
2011
+ licensePayload = getLicensePayload(meta.licenseId);
2012
+ if (licensePayload === undefined) {
2013
+ isCritical = true;
2014
+ throw new Error(`[vault] License "${meta.licenseId}" required by behavior "${behaviorKey}" is not registered in Vault config.`);
2015
+ }
2016
+ }
2017
+ let instance;
2018
+ try {
2019
+ instance = new BehaviorClass(behaviorKey, {
2020
+ featureCellKey: this.#cellKey,
2021
+ behaviorConfig,
2022
+ licensePayload
2023
+ });
2024
+ }
2025
+ catch (error) {
2026
+ isCritical = meta.critical;
2027
+ throw error;
2028
+ }
2029
+ if (!instance.key) {
2030
+ isCritical = true;
2031
+ throw new Error(`[vault] Behavior missing key for type "${behaviorType}". Every behavior must define a unique "key".`);
2032
+ }
2033
+ if (!validateBehaviorKey(instance.key)) {
2034
+ isCritical = true;
2035
+ throw new Error(`[vault] Behavior key "${instance.key}" not valid format for "${behaviorType}" behavior.`);
2036
+ }
2037
+ if (instance.key && seenKeys.has(instance.key)) {
2038
+ vaultWarn(`[vault] Skipping duplicate behavior with key "${instance.key}"`);
2039
+ return null;
2040
+ }
2041
+ if (instance.key)
2042
+ seenKeys.add(instance.key);
2043
+ return instance;
2044
+ }
2045
+ catch (err) {
2046
+ if (isCritical) {
2047
+ throw err;
2048
+ }
2049
+ // eslint-disable-next-line
2050
+ vaultWarn(`[vault] Non-critical behavior initialization failed: ${err?.message}`);
2051
+ return null;
2052
+ }
2053
+ })
2054
+ .filter((behavior) => !!behavior));
2055
+ }
2056
+ /**
2057
+ * Applies behavior-defined API extensions to a FeatureCell instance.
2058
+ *
2059
+ * Each behavior may expose extension functions that are attached to the
2060
+ * FeatureCell. Extensions are validated to prevent overriding protected
2061
+ * core APIs or other behavior extensions without explicit permission.
2062
+ *
2063
+ * @typeParam T - The FeatureCell state value type.
2064
+ * @param cell - The FeatureCell instance to extend.
2065
+ * @param vaultMonitor - Monitor used for extension execution diagnostics.
2066
+ */
2067
+ applyBehaviorExtensions(behaviors, cell, vaultMonitor) {
2068
+ for (const behavior of behaviors) {
2069
+ const FeatureCellExtensionContext = {
2070
+ featureCellKey: cell.key,
2071
+ destroyed$: cell.destroyed$,
2072
+ reset$: cell.reset$,
2073
+ mergeState: cell.mergeState,
2074
+ replaceState: cell.replaceState,
2075
+ state$: cell.state$,
2076
+ vaultMonitor
2077
+ };
2078
+ const extensions = behavior.extendCellAPI?.(FeatureCellExtensionContext);
2079
+ if (!extensions || typeof extensions !== 'object')
2080
+ continue;
2081
+ for (const [key, fn] of Object.entries(extensions)) {
2082
+ const alreadyDefined = cell[key] !== undefined;
2083
+ const canOverride =
2084
+ // eslint-disable-next-line
2085
+ Array.isArray(behavior.allowOverride) && behavior.allowOverride.includes(key);
2086
+ if (PROTECTED_FEATURE_CELL_KEYS.has(key)) {
2087
+ throw new Error(`[vault] Behavior "${behavior.key}" attempted to overwrite core FeatureCell method "${key}".`);
2088
+ }
2089
+ if (alreadyDefined && !canOverride) {
2090
+ throw new Error(`[vault] Behavior "${behavior.key}" attempted to redefine method "${key}" already provided by another behavior.`);
2091
+ }
2092
+ if (alreadyDefined && canOverride) {
2093
+ vaultWarn(`[vault] Behavior "${behavior.key}" is overriding method "${key}" (explicitly allowed).`);
2094
+ // eslint-disable-next-line
2095
+ delete cell[key];
2096
+ }
2097
+ Object.defineProperty(cell, key, {
2098
+ // eslint-disable-next-line
2099
+ value: (...args) => {
2100
+ try {
2101
+ if (typeof fn !== 'function')
2102
+ return;
2103
+ return fn(...args);
2104
+ }
2105
+ catch (err) {
2106
+ vaultError(`[vault] Behavior extension "${key}" threw an error:`, err);
2107
+ throw err;
2108
+ }
2109
+ },
2110
+ enumerable: false,
2111
+ writable: false,
2112
+ configurable: true
2113
+ });
2114
+ }
2115
+ }
2116
+ }
2117
+ }
2118
+
2119
+ /**
2120
+ * Type guard that identifies error transform behaviors.
2121
+ *
2122
+ * This function determines whether a behavior participates in the
2123
+ * error transformation stage of the pipeline based on its declared
2124
+ * behavior type.
2125
+ *
2126
+ * @typeParam T - The FeatureCell state value type.
2127
+ * @param behavior - A behavior instance to inspect.
2128
+ * @returns `true` when the behavior is an error transform behavior.
2129
+ */
2130
+ const isErrorTransformBehavior = (behavior) => {
2131
+ return behavior.type === BehaviorTypes.ErrorTransform;
2132
+ };
2133
+ /**
2134
+ * Type guard that identifies the core error callback behavior.
2135
+ *
2136
+ * This function is used to detect behaviors responsible for invoking
2137
+ * legacy or observational error callbacks during pipeline execution.
2138
+ *
2139
+ * @typeParam T - The FeatureCell state value type.
2140
+ * @param behavior - A behavior instance to inspect.
2141
+ * @returns `true` when the behavior is the core error callback behavior.
2142
+ */
2143
+ const isCoreErrorCallbackBehavior = (behavior) => {
2144
+ return behavior.type === BehaviorTypes.CoreErrorCallback;
2145
+ };
2146
+ /**
2147
+ * Type guard that identifies the core emitState behavior.
2148
+ *
2149
+ * This function detects the behavior responsible for emitting finalized
2150
+ * state snapshots to observers after pipeline execution.
2151
+ *
2152
+ * @typeParam T - The FeatureCell state value type.
2153
+ * @param behavior - A behavior instance to inspect.
2154
+ * @returns `true` when the behavior is the core emitState behavior.
2155
+ */
2156
+ const isCoreEmitStateCallbackBehavior = (behavior) => {
2157
+ return behavior.type === BehaviorTypes.CoreEmitState;
2158
+ };
2159
+ /**
2160
+ * Determines whether a final pipeline value represents a STOP signal.
2161
+ *
2162
+ * A STOP signal halts the pipeline immediately without committing state
2163
+ * and is typically produced by interceptor behaviors.
2164
+ *
2165
+ * @typeParam T - The FeatureCell state value type.
2166
+ * @param current - The final pipeline result value.
2167
+ * @returns `true` when the value is the `VAULT_STOP` sentinel.
2168
+ */
2169
+ const isSignalStop = (current) => {
2170
+ return current === VAULT_STOP;
2171
+ };
2172
+ /**
2173
+ * Generates a compact uppercase trace identifier.
2174
+ *
2175
+ * The identifier is derived from `crypto.randomUUID()` when available,
2176
+ * with a fallback to a randomized string for environments where WebCrypto
2177
+ * is unavailable.
2178
+ *
2179
+ * @returns A five-character uppercase trace identifier.
2180
+ */
2181
+ const assignTraceId = () => {
2182
+ return crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2, 7);
2183
+ };
2184
+ /**
2185
+ * Determines whether a pipeline result is terminal.
2186
+ *
2187
+ * Terminal values indicate that the pipeline must halt without applying
2188
+ * a new state snapshot. This includes both no-op and stop signals.
2189
+ *
2190
+ * @typeParam T - The FeatureCell state value type.
2191
+ * @param current - The final pipeline result value.
2192
+ * @returns `true` when the pipeline should not apply state.
2193
+ */
2194
+ const isPipelineTerminal = (current) => isVaultNoop(current) || isSignalStop(current);
2195
+
2196
+ // --- AI Model File Path (DO NOT DELETE) ---
2197
+ // FilePath: projects > engine > src > lib > orchestrator > orchestrator.ts
2198
+ // Updated: 2026-03-10 03:56
2199
+ // Generated by pathcomment [tab] (see .vscode/typescript.code-snippets) or
2200
+ // cmd+alt+j (see .vscode/keybindings.json)
2201
+ // --- END AI MODEL FILE PATH ---
2202
+ class Orchestrator {
2203
+ #afterTapCallbacks;
2204
+ #beforeTapCallbacks;
2205
+ #behaviors;
2206
+ #stageBehaviors;
2207
+ cellKey;
2208
+ decisionEngine;
2209
+ #coreErrorBehavior;
2210
+ #coreErrorCallbackBehavior;
2211
+ #emitStateCallbackBehavior;
2212
+ #emitStateCallbacks;
2213
+ #coreStateBehavior;
2214
+ #errorTransformBehaviors;
2215
+ #errorCallbacks;
2216
+ privateErrorService = VaultPrivateErrorService();
2217
+ #filterFunctions = [];
2218
+ #initialState;
2219
+ #mergeBehavior;
2220
+ #reducerFunctions;
2221
+ vaultMonitor = VaultMonitor();
2222
+ constructor(config) {
2223
+ this.#afterTapCallbacks = config.afterTapCallbacks ?? [];
2224
+ this.#beforeTapCallbacks = config.beforeTapCallbacks ?? [];
2225
+ this.cellKey = config.cell?.key;
2226
+ this.#emitStateCallbacks = config.emitStateCallbacks ?? [];
2227
+ this.#errorCallbacks = config.errorCallbacks ?? [];
2228
+ this.#filterFunctions = config.filterCallbacks ?? [];
2229
+ this.#initialState = config.initialState;
2230
+ this.#reducerFunctions = config.reducerCallbacks ?? [];
2231
+ }
2232
+ initializeOrchestrator(config) {
2233
+ config.behaviors = config.behaviors ?? [];
2234
+ this.#registerBehaviors(config);
2235
+ }
2236
+ //#region Protected Methods
2237
+ async initializeFeatureCell(ctx) {
2238
+ await this.#loadInitialState(ctx);
2239
+ }
2240
+ destroyBehaviors(ctx) {
2241
+ this.#destroyBehaviors(ctx);
2242
+ }
2243
+ resetBehaviors(ctx) {
2244
+ this.#resetBehaviors(ctx);
2245
+ }
2246
+ preparingIncoming(ctx) {
2247
+ const incoming = this.#coreStateBehavior.preparePipelineIncoming(ctx);
2248
+ if (isVaultNoop(incoming)) {
2249
+ this.#runStateBehaviors(ctx);
2250
+ }
2251
+ return incoming;
2252
+ }
2253
+ async orchestrate(ctx, options) {
2254
+ // no controllers → pipeline behaves exactly as before
2255
+ if (ctx.operation === OperationTypes.Replace) {
2256
+ await this.#orchestrateReplace(ctx);
2257
+ }
2258
+ else {
2259
+ await this.#orchestrateMerge(ctx, options);
2260
+ }
2261
+ return;
2262
+ }
2263
+ buildControllerCtx(ctx) {
2264
+ return {
2265
+ traceId: ctx.traceId,
2266
+ featureCellKey: ctx.featureCellKey,
2267
+ snapshot: ctx.lastSnapshot,
2268
+ incoming: ctx.incoming,
2269
+ operation: ctx.operation
2270
+ };
2271
+ }
2272
+ normalizeIncoming(incoming) {
2273
+ if (!incoming)
2274
+ return null;
2275
+ if (isHttpResourceRef(incoming) || isObservable(incoming) || isFunction(incoming) || isFunction(incoming)) {
2276
+ return incoming;
2277
+ }
2278
+ if (isStateInputShape(incoming)) {
2279
+ return incoming;
2280
+ }
2281
+ return {
2282
+ value: incoming
2283
+ };
2284
+ }
2285
+ controllerOutcomeNotification(type, ctx) {
2286
+ switch (type) {
2287
+ case DecisionOutcomeTypes.Abort: {
2288
+ this.#coreStateBehavior.finalizeControllerAbort(ctx);
2289
+ break;
2290
+ }
2291
+ case DecisionOutcomeTypes.Deny: {
2292
+ this.#coreStateBehavior.finalizeControllerDeny(ctx);
2293
+ break;
2294
+ }
2295
+ }
2296
+ }
2297
+ prepIncomingForOrchestration(ctx, incoming, operation) {
2298
+ ctx.incoming = this.normalizeIncoming(incoming);
2299
+ ctx.resolveType = this.#getResolveType(incoming);
2300
+ ctx.operation = operation;
2301
+ return ctx;
2302
+ }
2303
+ //#endregion
2304
+ //#region Private Methods
2305
+ #addDefaultMergeBehavior(behaviors, config) {
2306
+ const mergeBehaviors = config.behaviors.filter((behaviorClass) => {
2307
+ return behaviorClass.type === BehaviorTypes.Merge;
2308
+ });
2309
+ if (mergeBehaviors.length > 1) {
2310
+ const names = mergeBehaviors.map((mergeBehavior) => mergeBehavior.key).join(', ');
2311
+ throw new Error(`SDuX Error: More than one MergeBehavior was provided. Only one merge strategy can be active per FeatureCell. Received: ${names}. Fix: Remove additional merge behaviors or combine them into a single behavior.`);
2312
+ }
2313
+ if (mergeBehaviors.length === 1) {
2314
+ behaviors.push(mergeBehaviors[0]);
2315
+ behaviors = behaviors.filter((behavior) => behavior.type !== BehaviorTypes.Merge);
2316
+ }
2317
+ return behaviors;
2318
+ }
2319
+ // ---------------------------------------------------------------------------
2320
+ // NORMALIZATION
2321
+ // ---------------------------------------------------------------------------
2322
+ #defineDefaultBehaviors(config) {
2323
+ let defaultBehaviors = config.defaultBehaviors ?? [];
2324
+ defaultBehaviors = this.#determineCoreCallbackErrorBehavior(defaultBehaviors, config);
2325
+ defaultBehaviors = this.#addDefaultMergeBehavior(defaultBehaviors, config);
2326
+ defaultBehaviors = this.#addCoreEmitStateCallbackBehavior(defaultBehaviors, config);
2327
+ return defaultBehaviors;
2328
+ }
2329
+ #determineCoreCallbackErrorBehavior(behaviors, config) {
2330
+ if (config?.errorCallbacks?.length === 0) {
2331
+ return behaviors.filter((behavior) => behavior.type !== BehaviorTypes.CoreErrorCallback);
2332
+ }
2333
+ return behaviors;
2334
+ }
2335
+ #addCoreEmitStateCallbackBehavior(behaviors, config) {
2336
+ if (config?.emitStateCallbacks?.length === 0) {
2337
+ return behaviors.filter((behavior) => behavior.type !== BehaviorTypes.CoreEmitState);
2338
+ }
2339
+ return behaviors;
2340
+ }
2341
+ #registerBehaviorsWithVault(behaviors) {
2342
+ // eslint-disable-next-line
2343
+ const behaviorMetadata = behaviors.map((behavior) => {
2344
+ const meta = behavior[BEHAVIOR_META];
2345
+ return {
2346
+ key: behavior.key,
2347
+ type: meta.type,
2348
+ critical: meta.critical,
2349
+ needsLicense: meta.needsLicense
2350
+ };
2351
+ });
2352
+ LicensingService().describeBehaviors({
2353
+ featureCellKey: this.cellKey,
2354
+ behaviors: behaviorMetadata
2355
+ });
2356
+ }
2357
+ #registerBehaviors(config) {
2358
+ const defaultBehaviors = this.#defineDefaultBehaviors(config);
2359
+ // Strip out any user-provided reducers; they are passed separately via `reducers`
2360
+ const filteredUserBehaviors = config.behaviors?.filter((behavior) => {
2361
+ return !(behavior.type === BehaviorTypes.CoreAfterTap ||
2362
+ behavior.type === BehaviorTypes.CoreBeforeTap ||
2363
+ behavior.type === BehaviorTypes.CoreError ||
2364
+ behavior.type === BehaviorTypes.CoreErrorCallback ||
2365
+ behavior.type === BehaviorTypes.CoreState ||
2366
+ behavior.type === BehaviorTypes.CoreEmitState ||
2367
+ behavior.type === BehaviorTypes.Filter ||
2368
+ behavior.type === BehaviorTypes.FromObservable ||
2369
+ behavior.type === BehaviorTypes.FromPromise ||
2370
+ behavior.type === BehaviorTypes.FromStream ||
2371
+ behavior.type === BehaviorTypes.Reduce ||
2372
+ behavior.type === BehaviorTypes.Resolve);
2373
+ });
2374
+ config.operators = config.operators ?? [];
2375
+ config.interceptors = config.interceptors ?? [];
2376
+ const allBehaviors = [
2377
+ ...defaultBehaviors,
2378
+ ...filteredUserBehaviors,
2379
+ ...config.operators,
2380
+ ...config.interceptors
2381
+ ];
2382
+ const behaviorInit = new BehaviorInitializationClass(this.cellKey);
2383
+ this.#registerBehaviorsWithVault(allBehaviors);
2384
+ this.#behaviors = behaviorInit.initializeBehaviors(allBehaviors, config.behaviorConfigs);
2385
+ this.#registerErrorBehavior();
2386
+ this.#registerMergeBehavior();
2387
+ this.#registerStageBehaviors();
2388
+ this.#registerStateBehavior();
2389
+ behaviorInit.applyBehaviorExtensions(this.#behaviors, config.cell, this.vaultMonitor);
2390
+ }
2391
+ #registerStageBehaviors() {
2392
+ // Remove merge behavior from the pipeline list
2393
+ this.#stageBehaviors = this.#behaviors.filter((behavior) => !(behavior.type === BehaviorTypes.CoreState ||
2394
+ behavior.type === BehaviorTypes.CoreEmitState ||
2395
+ behavior.type === BehaviorTypes.CoreError ||
2396
+ behavior.type === BehaviorTypes.ErrorTransform ||
2397
+ behavior.type === BehaviorTypes.CoreErrorCallback ||
2398
+ behavior.type === BehaviorTypes.Merge));
2399
+ }
2400
+ #registerStateBehavior() {
2401
+ // Extract and remove errot behavior
2402
+ const coreState = this.#behaviors.filter((behaviort) => behaviort.type === BehaviorTypes.CoreState);
2403
+ if (coreState.length > 1) {
2404
+ throw new Error('Only one core state behavior can be registered for a FeatureCell.');
2405
+ }
2406
+ this.#coreStateBehavior = coreState[0] ?? null;
2407
+ this.#emitStateCallbackBehavior = this.#behaviors.filter((behavior) => isCoreEmitStateCallbackBehavior(behavior))[0];
2408
+ }
2409
+ #registerErrorBehavior() {
2410
+ // Extract and remove error behavior
2411
+ const coreErrors = this.#behaviors.filter((behavior) => behavior.type === BehaviorTypes.CoreError);
2412
+ if (coreErrors.length > 1) {
2413
+ throw new Error('Only one core error behavior can be registered for a FeatureCell.');
2414
+ }
2415
+ this.#coreErrorBehavior = coreErrors[0] ?? null;
2416
+ this.#coreErrorCallbackBehavior = this.#behaviors.filter((behavior) => isCoreErrorCallbackBehavior(behavior))[0];
2417
+ this.#errorTransformBehaviors = this.#behaviors.filter((behavior) => isErrorTransformBehavior(behavior));
2418
+ }
2419
+ #registerMergeBehavior() {
2420
+ // Extract and remove merge behavior
2421
+ const merges = this.#behaviors.filter((behavior) => behavior.type === BehaviorTypes.Merge);
2422
+ this.#mergeBehavior = merges[0] ?? null;
2423
+ }
2424
+ async #runStepwise(behaviorType, ctx, current) {
2425
+ const stepwise = await this.#runUpstreamStage(behaviorType, ctx, current);
2426
+ if (isVaultClearState(stepwise)) {
2427
+ return VAULT_CLEAR_STATE;
2428
+ }
2429
+ if (isVaultNoop(stepwise)) {
2430
+ return VAULT_NOOP;
2431
+ }
2432
+ return VAULT_CONTINUE;
2433
+ }
2434
+ async #finishPipeline(ctx, resolved) {
2435
+ // Stage: operators
2436
+ let pipelineDataFlow;
2437
+ const stepwiseResolve = await this.#runStepwise(BehaviorTypes.StepwiseResolve, ctx, resolved);
2438
+ if (!isVaultContinue(stepwiseResolve)) {
2439
+ return stepwiseResolve;
2440
+ }
2441
+ if (this.#containsOperators()) {
2442
+ pipelineDataFlow = await this.#runOperatorStage(ctx, resolved);
2443
+ // Halt pipeline on noop
2444
+ if (isVaultNoop(pipelineDataFlow))
2445
+ return VAULT_NOOP;
2446
+ }
2447
+ else {
2448
+ pipelineDataFlow = resolved;
2449
+ }
2450
+ // Stage: filter
2451
+ pipelineDataFlow = await this.#runUpstreamStage(BehaviorTypes.Filter, ctx, pipelineDataFlow);
2452
+ const stepwiseFilter = await this.#runStepwise(BehaviorTypes.StepwiseFilter, ctx, pipelineDataFlow);
2453
+ if (!isVaultContinue(stepwiseFilter)) {
2454
+ return stepwiseFilter;
2455
+ }
2456
+ await this.#runUpstreamStage(BehaviorTypes.CoreBeforeTap, ctx, isolateValue(pipelineDataFlow));
2457
+ // Stage: reduce
2458
+ pipelineDataFlow = await this.#runUpstreamStage(BehaviorTypes.Reduce, ctx, pipelineDataFlow);
2459
+ const stepwiseReducer = await this.#runStepwise(BehaviorTypes.StepwiseReducer, ctx, pipelineDataFlow);
2460
+ if (!isVaultContinue(stepwiseReducer)) {
2461
+ return stepwiseReducer;
2462
+ }
2463
+ await this.#runUpstreamStage(BehaviorTypes.CoreAfterTap, ctx, isolateValue(pipelineDataFlow));
2464
+ // Clone AFTER reduce for purity
2465
+ const stateData = isolateValue(pipelineDataFlow);
2466
+ let persistPipelineValue = stateData;
2467
+ // Stage: encrypt
2468
+ persistPipelineValue = await this.#runPersistStage(BehaviorTypes.Encrypt, ctx, persistPipelineValue);
2469
+ // Stage: persist
2470
+ await this.#runPersistStage(BehaviorTypes.Persist, ctx, persistPipelineValue);
2471
+ // Commit the *cloned* pre-encrypted snapshot to signals
2472
+ return stateData;
2473
+ }
2474
+ async #orchestrateReplace(ctx) {
2475
+ this.vaultMonitor.startReplace(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2476
+ await this.#safeAsync(async () => {
2477
+ let finalState;
2478
+ finalState = await this.#runInterceptorStage(ctx);
2479
+ if (!isSignalStop(finalState)) {
2480
+ const resolved = await this.#runUpstreamStage(BehaviorTypes.Resolve, ctx, undefined);
2481
+ if (isVaultClearState(resolved)) {
2482
+ finalState = VAULT_CLEAR_STATE;
2483
+ }
2484
+ else {
2485
+ finalState = await this.#finishPipeline(ctx, resolved);
2486
+ }
2487
+ }
2488
+ return this.#handleFinalState(finalState, ctx);
2489
+ }, ctx);
2490
+ }
2491
+ async #orchestrateMerge(ctx, options) {
2492
+ this.vaultMonitor.startMerge(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2493
+ await this.#safeAsync(async () => {
2494
+ const current = ctx.lastSnapshot.value;
2495
+ let finalState;
2496
+ finalState = await this.#runInterceptorStage(ctx);
2497
+ if (!isSignalStop(finalState)) {
2498
+ const partial = await this.#runUpstreamStage(BehaviorTypes.Resolve, ctx, undefined);
2499
+ this.vaultMonitor.startComputeMerge(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2500
+ // eslint-disable-next-line
2501
+ const mergeResult = await this.#mergeBehavior.computeMerge(current, partial, options);
2502
+ this.vaultMonitor.endComputeMerge(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2503
+ if (isVaultClearState(mergeResult)) {
2504
+ finalState = VAULT_CLEAR_STATE;
2505
+ }
2506
+ else {
2507
+ const stateResult = isolateValue(mergeResult);
2508
+ finalState = await this.#finishPipeline(ctx, stateResult);
2509
+ }
2510
+ }
2511
+ return await this.#handleFinalState(finalState, ctx);
2512
+ }, ctx);
2513
+ }
2514
+ async #handleFinalState(finalState, ctx) {
2515
+ let payload;
2516
+ if (isSignalStop(finalState)) {
2517
+ payload = { pipelinePaused: true };
2518
+ }
2519
+ else if (isVaultClearState(finalState)) {
2520
+ payload = { pipelineStateCleared: true };
2521
+ }
2522
+ else if (isUndefined(finalState) || isVaultNoop(finalState)) {
2523
+ payload = { noop: true };
2524
+ }
2525
+ if (ctx.operation === OperationTypes.Replace) {
2526
+ this.vaultMonitor.endReplace(this.cellKey, VAULT_ORCHESTRATOR, ctx, payload);
2527
+ }
2528
+ else {
2529
+ this.vaultMonitor.endMerge(this.cellKey, VAULT_ORCHESTRATOR, ctx, payload);
2530
+ }
2531
+ return finalState;
2532
+ }
2533
+ async #safeAsync(fn, ctx) {
2534
+ try {
2535
+ const result = await fn();
2536
+ this.vaultMonitor.startCoreState(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2537
+ if (isSignalStop(result)) {
2538
+ this.#coreStateBehavior.finalizePipelineVaultStop(ctx);
2539
+ }
2540
+ else {
2541
+ this.#coreStateBehavior.finalizePipelineState(result, ctx);
2542
+ }
2543
+ await this.#runStateBehaviors(ctx);
2544
+ this.vaultMonitor.endCoreState(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2545
+ this.decisionEngine?.notifySuccess(this.buildControllerCtx(ctx));
2546
+ }
2547
+ catch (err) {
2548
+ const pipelineError = await this.#runErrorBehaviors(err, ctx);
2549
+ await this.decisionEngine?.notifyFailure(this.buildControllerCtx(ctx), pipelineError);
2550
+ }
2551
+ }
2552
+ async #runUpstreamStage(stage, ctx, current) {
2553
+ let stageBehaviors;
2554
+ if (stage === BehaviorTypes.Resolve) {
2555
+ stageBehaviors = this.#stageBehaviors.filter((behavior) => {
2556
+ return behavior.resolveType === ctx.resolveType && behavior.type === stage;
2557
+ });
2558
+ }
2559
+ else {
2560
+ stageBehaviors = this.#stageBehaviors.filter((behavior) => {
2561
+ return behavior.type === stage;
2562
+ });
2563
+ }
2564
+ for (const behavior of stageBehaviors) {
2565
+ let next;
2566
+ try {
2567
+ switch (stage) {
2568
+ case BehaviorTypes.Resolve:
2569
+ if (typeof behavior.computeResolve === 'function') {
2570
+ this.vaultMonitor.startResolve(this.cellKey, behavior.key, ctx);
2571
+ next = await behavior.computeResolve(ctx);
2572
+ this.vaultMonitor.endResolve(this.cellKey, behavior.key, ctx);
2573
+ }
2574
+ break;
2575
+ case BehaviorTypes.StepwiseResolve:
2576
+ case BehaviorTypes.StepwiseFilter:
2577
+ case BehaviorTypes.StepwiseReducer:
2578
+ if (typeof behavior.evaluateStepwise === 'function') {
2579
+ this.vaultMonitor.startStepwise(this.cellKey, behavior.key, ctx);
2580
+ next = await behavior.evaluateStepwise(isolateValue(ctx.lastSnapshot.value), isolateValue(current), ctx.featureCellKey);
2581
+ this.vaultMonitor.endStepwise(this.cellKey, behavior.key, ctx);
2582
+ }
2583
+ break;
2584
+ case BehaviorTypes.Filter:
2585
+ if (typeof behavior.applyFilter === 'function') {
2586
+ for (const filter of this.#filterFunctions) {
2587
+ this.vaultMonitor.startFilter(this.cellKey, behavior.key, ctx);
2588
+ const nextValue = await behavior.applyFilter(current, filter);
2589
+ this.vaultMonitor.endFilter(this.cellKey, behavior.key, ctx);
2590
+ if (isDefined(nextValue)) {
2591
+ current = nextValue;
2592
+ }
2593
+ }
2594
+ }
2595
+ break;
2596
+ case BehaviorTypes.CoreBeforeTap:
2597
+ if (typeof behavior.applyBeforeTap === 'function') {
2598
+ for (const beforeTap of this.#beforeTapCallbacks) {
2599
+ this.vaultMonitor.startBeforeTap(this.cellKey, behavior.key, ctx);
2600
+ await behavior.applyBeforeTap(current, beforeTap);
2601
+ this.vaultMonitor.endBeforeTap(this.cellKey, behavior.key, ctx);
2602
+ }
2603
+ }
2604
+ break;
2605
+ case BehaviorTypes.Reduce:
2606
+ if (typeof behavior.applyReducer === 'function') {
2607
+ if (isUndefined(current) && this.#reducerFunctions.length > 0) {
2608
+ throw new Error(`[vault] Reducer stage received undefined state in FeatureCell "${this.cellKey}", but reducers are registered.`);
2609
+ }
2610
+ for (const reducer of this.#reducerFunctions) {
2611
+ this.vaultMonitor.startReducer(this.cellKey, behavior.key, ctx);
2612
+ const nextValue = await behavior.applyReducer(current, reducer);
2613
+ this.vaultMonitor.endReducer(this.cellKey, behavior.key, ctx);
2614
+ if (isDefined(nextValue)) {
2615
+ current = nextValue;
2616
+ }
2617
+ }
2618
+ }
2619
+ break;
2620
+ case BehaviorTypes.CoreAfterTap:
2621
+ if (typeof behavior.applyAfterTap === 'function') {
2622
+ for (const afterTap of this.#afterTapCallbacks) {
2623
+ this.vaultMonitor.startAfterTap(this.cellKey, behavior.key, ctx);
2624
+ await behavior.applyAfterTap(current, afterTap);
2625
+ this.vaultMonitor.endAfterTap(this.cellKey, behavior.key, ctx);
2626
+ }
2627
+ }
2628
+ break;
2629
+ }
2630
+ }
2631
+ catch (err) {
2632
+ this.vaultMonitor.runtimeError(this.cellKey, behavior.key, ctx, err);
2633
+ throw err;
2634
+ }
2635
+ if (isDefined(next))
2636
+ current = next;
2637
+ }
2638
+ return current;
2639
+ }
2640
+ async #runInterceptorStage(ctx) {
2641
+ const interceptorBehaviors = this.#stageBehaviors.filter((behavior) => behavior.type === BehaviorTypes.Interceptor);
2642
+ for (const behavior of interceptorBehaviors) {
2643
+ try {
2644
+ this.vaultMonitor.startInterceptor(this.cellKey, behavior.key, ctx);
2645
+ const nextIncoming = await behavior.applyInterceptor(ctx);
2646
+ if (isSignalStop(nextIncoming)) {
2647
+ this.vaultMonitor.endInterceptor(this.cellKey, behavior.key, ctx, { pipelinePaused: true });
2648
+ return VAULT_STOP;
2649
+ }
2650
+ this.vaultMonitor.endInterceptor(this.cellKey, behavior.key, ctx);
2651
+ }
2652
+ catch (err) {
2653
+ this.vaultMonitor.runtimeError(this.cellKey, behavior.key, ctx, err);
2654
+ throw err;
2655
+ }
2656
+ }
2657
+ return;
2658
+ }
2659
+ #containsOperators() {
2660
+ const operatorBehaviors = this.#stageBehaviors.filter((behavior) => behavior.type === BehaviorTypes.Operator);
2661
+ return operatorBehaviors.length > 0;
2662
+ }
2663
+ async #runOperatorStage(ctx, current) {
2664
+ const operatorBehaviors = this.#stageBehaviors.filter((behavior) => behavior.type === BehaviorTypes.Operator);
2665
+ for (const behavior of operatorBehaviors) {
2666
+ try {
2667
+ this.vaultMonitor.startOperator(this.cellKey, behavior.key, ctx);
2668
+ const next = await behavior.applyOperator(current);
2669
+ if (isUndefined(next)) {
2670
+ // short-circuit pipeline — operator decided to block this write
2671
+ this.vaultMonitor.endOperator(this.cellKey, behavior.key, ctx, { noop: true });
2672
+ return undefined;
2673
+ }
2674
+ current = next;
2675
+ this.vaultMonitor.endOperator(this.cellKey, behavior.key, ctx);
2676
+ }
2677
+ catch (err) {
2678
+ this.vaultMonitor.runtimeError(this.cellKey, behavior.key, ctx, err);
2679
+ throw err;
2680
+ }
2681
+ }
2682
+ return current;
2683
+ }
2684
+ async #runPersistStage(stage, ctx, current) {
2685
+ let stageBehaviors;
2686
+ stageBehaviors = this.#stageBehaviors.filter((behavior) => behavior.type === stage);
2687
+ for (const behavior of stageBehaviors) {
2688
+ try {
2689
+ switch (stage) {
2690
+ case BehaviorTypes.Encrypt:
2691
+ if (typeof behavior.encryptState === 'function') {
2692
+ this.vaultMonitor.startEncrypt(this.cellKey, behavior.key, ctx);
2693
+ current = await behavior.encryptState(ctx, current);
2694
+ this.vaultMonitor.endEncrypt(this.cellKey, behavior.key, ctx);
2695
+ }
2696
+ break;
2697
+ case BehaviorTypes.Persist:
2698
+ if (typeof behavior.persistState === 'function') {
2699
+ this.vaultMonitor.startPersist(this.cellKey, behavior.key, ctx);
2700
+ await behavior.persistState(current);
2701
+ this.vaultMonitor.endPersist(this.cellKey, behavior.key, ctx);
2702
+ }
2703
+ break;
2704
+ }
2705
+ }
2706
+ catch (err) {
2707
+ this.vaultMonitor.runtimeError(this.cellKey, behavior.key, ctx, err);
2708
+ throw err;
2709
+ }
2710
+ }
2711
+ return current;
2712
+ }
2713
+ #destroyBehaviors(ctx) {
2714
+ for (const behavior of this.#behaviors) {
2715
+ this.vaultMonitor.startDestroy(this.cellKey, behavior.key, ctx);
2716
+ try {
2717
+ behavior.destroy?.(ctx);
2718
+ this.vaultMonitor.endDestroy(this.cellKey, behavior.key, ctx);
2719
+ }
2720
+ catch (err) {
2721
+ vaultError(`${behavior.key} destroy() failed`, err);
2722
+ this.vaultMonitor.endDestroy(this.cellKey, behavior.key, ctx, { destroyFailed: true });
2723
+ }
2724
+ }
2725
+ }
2726
+ #resetBehaviors(ctx) {
2727
+ for (const behavior of this.#behaviors) {
2728
+ this.vaultMonitor.startReset(this.cellKey, behavior.key, ctx);
2729
+ try {
2730
+ behavior.reset?.(ctx);
2731
+ this.vaultMonitor.endReset(this.cellKey, behavior.key, ctx);
2732
+ }
2733
+ catch (err) {
2734
+ vaultError(`${behavior.key} reset() failed`, err);
2735
+ this.vaultMonitor.endReset(this.cellKey, behavior.key, ctx, { resetFailed: true });
2736
+ }
2737
+ }
2738
+ }
2739
+ async #runStateBehaviors(ctx) {
2740
+ if (this.#emitStateCallbacks?.length > 0) {
2741
+ const lastSnapshotClone = isolateValue(ctx.lastSnapshot);
2742
+ this.vaultMonitor.startCoreEmitState(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2743
+ for (const callback of this.#emitStateCallbacks) {
2744
+ await this.#emitStateCallbackBehavior.emitState(lastSnapshotClone, callback);
2745
+ }
2746
+ this.vaultMonitor.endCoreEmitState(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2747
+ }
2748
+ }
2749
+ /**
2750
+ * Runs the error behaviors in sequence.
2751
+ *
2752
+ * - Starts from a normalized ResourceError produced by resourceError().
2753
+ * - Each behavior gets (rawError, currentResourceError, ctx).
2754
+ * - If a behavior returns:
2755
+ * • ResourceError → becomes the next currentResourceError
2756
+ * • VAULT_NOOP → previous ResourceError is kept
2757
+ *
2758
+ * Returns void
2759
+ */
2760
+ async #runErrorBehaviors(rawError, ctx) {
2761
+ let current;
2762
+ /**
2763
+ * The order is
2764
+ *
2765
+ * Core Error
2766
+ * Transforms
2767
+ * State
2768
+ * Callbacks
2769
+ * Global Error Service
2770
+ */
2771
+ // ---- Core error normalization (trusted) ----
2772
+ try {
2773
+ this.vaultMonitor.startCoreError(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2774
+ current = await this.#coreErrorBehavior.handleError(rawError, ctx.featureCellKey);
2775
+ vaultDebug(`${this.cellKey} #runErrorBehaviors starting with base ResourceError: ${JSON.stringify(current)}`);
2776
+ }
2777
+ catch (err) {
2778
+ vaultError('[vault] Core error normalization failed', err);
2779
+ current = createVaultError(rawError, ctx.featureCellKey);
2780
+ }
2781
+ finally {
2782
+ this.vaultMonitor.endCoreError(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2783
+ }
2784
+ // ---- Error behaviors (untrusted, isolated) ----
2785
+ for (const behavior of this.#errorTransformBehaviors) {
2786
+ try {
2787
+ this.vaultMonitor.startErrorTransform(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2788
+ const result = await behavior.transformError(isolateValue(rawError), isolateValue(current), isolateValue(ctx.lastSnapshot));
2789
+ if (!isVaultNoop(result) && !isNullish(result)) {
2790
+ current = result;
2791
+ }
2792
+ }
2793
+ catch (err) {
2794
+ vaultError(`[vault] ErrorBehavior "${behavior.key}" threw during error handling`, err);
2795
+ }
2796
+ finally {
2797
+ this.vaultMonitor.endErrorTransform(this.cellKey, VAULT_ORCHESTRATOR, ctx, current);
2798
+ }
2799
+ }
2800
+ // ---- Commit error state (trusted, must run) ----
2801
+ try {
2802
+ this.vaultMonitor.startCoreState(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2803
+ await this.#coreStateBehavior.finalizePipelineError(current, ctx);
2804
+ await this.#runStateBehaviors(ctx);
2805
+ }
2806
+ catch (err) {
2807
+ vaultError('[vault] Failed to finalize error state', err);
2808
+ }
2809
+ finally {
2810
+ this.vaultMonitor.endCoreState(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2811
+ }
2812
+ try {
2813
+ this.vaultMonitor.startGlobalError(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2814
+ await this.privateErrorService.setError(isolateValue(current));
2815
+ }
2816
+ catch (err) {
2817
+ // istanbul ignore next -- defensive only not testable
2818
+ vaultError('[vault] global error service', err);
2819
+ }
2820
+ finally {
2821
+ this.vaultMonitor.endGlobalError(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2822
+ }
2823
+ // ---- Error callbacks (untrusted, isolated) ----
2824
+ if (this.#errorCallbacks?.length > 0) {
2825
+ this.vaultMonitor.startCoreCallbackError(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2826
+ for (const callback of this.#errorCallbacks) {
2827
+ try {
2828
+ await this.#coreErrorCallbackBehavior.callbackError(isolateValue(current), isolateValue(ctx.lastSnapshot), callback);
2829
+ }
2830
+ catch (err) {
2831
+ vaultError('[vault] Error callback threw during error handling', err);
2832
+ }
2833
+ }
2834
+ this.vaultMonitor.endCoreCallbackError(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2835
+ }
2836
+ vaultDebug(`${this.cellKey} #runErrorBehaviors completed with final ResourceError: ${JSON.stringify(current)}`);
2837
+ return current;
2838
+ }
2839
+ #getResolveType(incoming) {
2840
+ if (isHttpResourceRef(incoming)) {
2841
+ return ResolveTypes.HttpResource;
2842
+ }
2843
+ else if (isObservable(incoming)) {
2844
+ return ResolveTypes.Observable;
2845
+ }
2846
+ else if (isFunction(incoming) || isFunction(incoming?.value)) {
2847
+ return ResolveTypes.Promise;
2848
+ }
2849
+ else if (isPromise(incoming) || isPromise(incoming?.value)) {
2850
+ throw new VaultUsagePromiseError();
2851
+ }
2852
+ else {
2853
+ return ResolveTypes.Value;
2854
+ }
2855
+ }
2856
+ async #loadInitialState(ctx) {
2857
+ const incoming = {
2858
+ value: undefined,
2859
+ loading: false,
2860
+ error: null
2861
+ };
2862
+ let value = undefined;
2863
+ if (isFunction(this.#initialState)) {
2864
+ value = this.#initialState;
2865
+ }
2866
+ else {
2867
+ const persistBehaviors = this.#getPersistedBehaviors();
2868
+ if (persistBehaviors.length > 0) {
2869
+ const persistedInitalValue = await this.#loadInitialPersistedState(ctx, persistBehaviors);
2870
+ if (isDefined(persistedInitalValue)) {
2871
+ vaultDebug('Persisted data loaded from storage');
2872
+ value = persistedInitalValue;
2873
+ }
2874
+ }
2875
+ else if (!isNullish(this.#initialState)) {
2876
+ this.vaultMonitor.startSetInitialValue(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2877
+ vaultDebug('Initialized data loaded from descriptor.initial');
2878
+ value = this.#initialState;
2879
+ this.vaultMonitor.endSetInitialValue(this.cellKey, VAULT_ORCHESTRATOR, ctx);
2880
+ }
2881
+ }
2882
+ incoming.value = value;
2883
+ if (!isNullish(incoming.value)) {
2884
+ await this.orchestrate(this.prepIncomingForOrchestration(ctx, incoming, OperationTypes.Replace));
2885
+ }
2886
+ else {
2887
+ this.decisionEngine?.notifySuccess(this.buildControllerCtx(ctx));
2888
+ }
2889
+ }
2890
+ #getPersistedBehaviors() {
2891
+ return this.#stageBehaviors.filter((behavior) => behavior.type === BehaviorTypes.Persist);
2892
+ }
2893
+ async #loadInitialPersistedState(ctx, persistBehaviors) {
2894
+ let loaded = undefined;
2895
+ for (const behavior of persistBehaviors) {
2896
+ try {
2897
+ this.vaultMonitor.startLoadPersist(this.cellKey, behavior.key, ctx);
2898
+ loaded = await behavior.loadState?.();
2899
+ if (isDefined(loaded)) {
2900
+ this.vaultMonitor.endLoadPersist(this.cellKey, behavior.key, ctx);
2901
+ break;
2902
+ }
2903
+ else {
2904
+ this.vaultMonitor.endLoadPersist(this.cellKey, behavior.key, ctx, { noop: true });
2905
+ }
2906
+ // eslint-disable-next-line
2907
+ }
2908
+ catch (err) {
2909
+ this.vaultMonitor.runtimeError(this.cellKey, behavior.key, ctx, err);
2910
+ vaultWarn(`"[vault] persist.loadState()" for ${behavior.key} failed with ${err.message}`);
2911
+ }
2912
+ }
2913
+ const decryptBehaviors = this.#stageBehaviors.filter((behavior) => {
2914
+ return behavior.type === BehaviorTypes.Encrypt;
2915
+ });
2916
+ if (isDefined(loaded) && decryptBehaviors.length > 0) {
2917
+ for (const behavior of decryptBehaviors) {
2918
+ try {
2919
+ this.vaultMonitor.startDecrypt(this.cellKey, behavior.key, ctx);
2920
+ const decrypted = await behavior.decryptState?.(ctx, loaded);
2921
+ if (isDefined(decrypted)) {
2922
+ this.vaultMonitor.endDecrypt(this.cellKey, behavior.key, ctx);
2923
+ loaded = decrypted;
2924
+ }
2925
+ else {
2926
+ this.vaultMonitor.endDecrypt(this.cellKey, behavior.key, ctx, { noop: true });
2927
+ }
2928
+ // eslint-disable-next-line
2929
+ }
2930
+ catch (err) {
2931
+ this.vaultMonitor.runtimeError(this.cellKey, behavior.key, ctx, err);
2932
+ vaultWarn(`"[vault] encrypt.decryptState()" for ${behavior.key} failed with ${err.message}`);
2933
+ return undefined;
2934
+ }
2935
+ }
2936
+ }
2937
+ return loaded;
2938
+ }
2939
+ }
2940
+
2941
+ const ConductorLicenseStatusTypes = {
2942
+ Pending: 'pending',
2943
+ Approved: 'approved',
2944
+ Denied: 'denied'
2945
+ };
2946
+
2947
+ // projects/core/src/lib/services/vault-controller-lifecycle.service.ts
2948
+ /**
2949
+ * Manages instantiation and validation of controllers for a FeatureCell.
2950
+ *
2951
+ * This class is responsible for constructing controller instances from
2952
+ * declared controller classes, validating required metadata, enforcing
2953
+ * uniqueness of controller keys, and wiring controller revote callbacks
2954
+ * into the controller event stream.
2955
+ */
2956
+ class ControllerInitializationClass {
2957
+ /** Tracks whether controller initialization has already occurred. */
2958
+ #initialized = false;
2959
+ /** FeatureCell identifier associated with this initializer. */
2960
+ #cellKey;
2961
+ /**
2962
+ * Creates a new initializer for a specific FeatureCell.
2963
+ *
2964
+ * @param cellKey - The unique FeatureCell key.
2965
+ */
2966
+ constructor(cellKey) {
2967
+ this.#cellKey = cellKey;
2968
+ }
2969
+ /**
2970
+ * Instantiates and validates all controllers declared for a FeatureCell.
2971
+ *
2972
+ * This method enforces one-time initialization, validates controller
2973
+ * metadata and key format, ensures uniqueness of controller keys, and
2974
+ * injects a revote callback that emits controller lifecycle events.
2975
+ *
2976
+ * @typeParam T - The FeatureCell state value type.
2977
+ * @param controllerClasses - Controller class definitions to instantiate.
2978
+ * @param events$ - Subject used to emit controller lifecycle events.
2979
+ * @returns An ordered array of instantiated controller objects.
2980
+ */
2981
+ initializeControllers(controllerClasses, events$, controllerConfigs) {
2982
+ if (this.#initialized)
2983
+ throw new Error(`[vault] VaultControllerRunner already initialized — cannot reissue core controller ID for feature cell "${this.#cellKey}".`);
2984
+ this.#initialized = true;
2985
+ if (!controllerClasses || controllerClasses.length === 0)
2986
+ return [];
2987
+ const seenKeys = new Set();
2988
+ return (controllerClasses
2989
+ // eslint-disable-next-line
2990
+ .map((ControllerClass) => {
2991
+ let isCritical = false;
2992
+ try {
2993
+ if (typeof ControllerClass !== 'function')
2994
+ return;
2995
+ const meta = ControllerClass[CONTROLLER_META];
2996
+ if (!meta) {
2997
+ isCritical = true;
2998
+ throw new Error(`[vault] Controller "${ControllerClass.name}" missing @VaultController metadata.`);
2999
+ }
3000
+ const controllerKey = meta.key;
3001
+ const controllerType = meta.type;
3002
+ if (!controllerKey) {
3003
+ isCritical = true;
3004
+ throw new Error(`[vault] Controller metadata missing "key".`);
3005
+ }
3006
+ if (!controllerType) {
3007
+ isCritical = true;
3008
+ throw new Error(`[vault] Controller metadata missing "type" for "${controllerKey}".`);
3009
+ }
3010
+ let controllerConfig = undefined;
3011
+ if (meta.wantsConfig) {
3012
+ if (!meta.configKey) {
3013
+ isCritical = true;
3014
+ throw new Error(`[vault] Controller "${controllerKey}" declares wantsConfig but has no configKey.`);
3015
+ }
3016
+ controllerConfig = controllerConfigs.get(meta.configKey);
3017
+ }
3018
+ let licensePayload = undefined;
3019
+ if (meta.needsLicense) {
3020
+ if (!meta.licenseId) {
3021
+ isCritical = true;
3022
+ throw new Error(`[vault] Controller "${controllerKey}" declares needsLicense but has no licenseId.`);
3023
+ }
3024
+ licensePayload = getLicensePayload(meta.licenseId);
3025
+ if (licensePayload === undefined) {
3026
+ isCritical = true;
3027
+ throw new Error(`[vault] License "${meta.licenseId}" required by controller "${controllerKey}" is not registered in Vault config.`);
3028
+ }
3029
+ }
3030
+ const controllerClassContext = {
3031
+ featureCellKey: this.#cellKey,
3032
+ requestRevote: (traceId) => {
3033
+ events$.next({
3034
+ traceId,
3035
+ type: ControllerEventTypes.Revote
3036
+ });
3037
+ },
3038
+ requestAbort: (traceId) => {
3039
+ events$.next({
3040
+ traceId,
3041
+ type: ControllerEventTypes.Abort
3042
+ });
3043
+ },
3044
+ controllerConfig,
3045
+ licensePayload
3046
+ };
3047
+ if (controllerType === ControllerTypes.License) {
3048
+ controllerClassContext.licenseDenied = (traceId) => {
3049
+ events$.next({
3050
+ traceId,
3051
+ type: ControllerEventTypes.LicenseDenied
3052
+ });
3053
+ };
3054
+ controllerClassContext.licenseApproved = (traceId) => {
3055
+ events$.next({
3056
+ traceId,
3057
+ type: ControllerEventTypes.LicenseApproved
3058
+ });
3059
+ };
3060
+ }
3061
+ const instance = new ControllerClass(controllerKey, controllerClassContext);
3062
+ if (!instance.key) {
3063
+ isCritical = true;
3064
+ throw new Error(`[vault] Controller missing key for type "${controllerType}". Every controller must define a unique "key".`);
3065
+ }
3066
+ if (!validateControllerKey(instance.key)) {
3067
+ isCritical = true;
3068
+ throw new Error(`[vault] Controller key "${instance.key}" not valid format for "${controllerType}" controller.`);
3069
+ }
3070
+ if (instance.key && seenKeys.has(instance.key)) {
3071
+ vaultWarn(`[vault] Skipping duplicate controller with key "${instance.key}"`);
3072
+ return null;
3073
+ }
3074
+ if (instance.key)
3075
+ seenKeys.add(instance.key);
3076
+ return instance;
3077
+ }
3078
+ catch (err) {
3079
+ if (isCritical) {
3080
+ throw err;
3081
+ }
3082
+ // eslint-disable-next-line
3083
+ vaultWarn(`[vault] Non-critical controller initialization failed: ${err?.message}`);
3084
+ return null;
3085
+ }
3086
+ })
3087
+ .filter((controller) => !!controller));
3088
+ }
3089
+ }
3090
+
3091
+ class Conductor extends Orchestrator {
3092
+ #attemptQueue = [];
3093
+ #controllers = [];
3094
+ #events$ = new Subject();
3095
+ #processing = false;
3096
+ #isDenyStateForTesting = false;
3097
+ #conductorLicenseStatus = ConductorLicenseStatusTypes.Pending;
3098
+ #settled$ = new Subject();
3099
+ constructor(config) {
3100
+ super(config);
3101
+ LicensingService().describeFeature({
3102
+ featureCellKey: config.cell.key,
3103
+ fluentApis: {
3104
+ filters: config.filterCallbacks,
3105
+ reducers: config.reducerCallbacks,
3106
+ beforeTaps: config.beforeTapCallbacks,
3107
+ afterTaps: config.afterTapCallbacks,
3108
+ emitStateCallbacks: config.emitStateCallbacks,
3109
+ errorCallbacks: config.errorCallbacks
3110
+ }
3111
+ });
3112
+ if (DevMode.active) {
3113
+ // eslint-disable-next-line
3114
+ this.vaultSettled = this.#conductorSettled.bind(this);
3115
+ }
3116
+ this.#registerControllers(config);
3117
+ this.vaultMonitor.conductorLicenseAttempt(this.cellKey, `${this.cellKey}::license`);
3118
+ this.initializeOrchestrator(config);
3119
+ }
3120
+ initialize(ctx) {
3121
+ const initCtx = this.#buildPipelineCtx(ctx, OperationTypes.Initialize, undefined);
3122
+ this.#enqueueAttempt({
3123
+ behaviorCtx: initCtx,
3124
+ controllerCtx: this.buildControllerCtx(initCtx),
3125
+ options: undefined
3126
+ });
3127
+ }
3128
+ //#region public method
3129
+ conduct(ctx, incoming, operation, options) {
3130
+ const behaviorCtx = this.#buildPipelineCtx(ctx, operation, options);
3131
+ const ensuredIncoming = this.#ensureIncoming(this.prepIncomingForOrchestration(behaviorCtx, incoming, operation));
3132
+ if (isVaultNoop(ensuredIncoming)) {
3133
+ return;
3134
+ }
3135
+ behaviorCtx.incoming = ensuredIncoming;
3136
+ const controllerCtx = this.buildControllerCtx(behaviorCtx);
3137
+ this.#enqueueAttempt({ behaviorCtx, controllerCtx, options });
3138
+ }
3139
+ /**
3140
+ * Resets pipeline + controller state for this FeatureCell.
3141
+ *
3142
+ * FeatureCell should call this from its own reset() method.
3143
+ */
3144
+ reset(ctx) {
3145
+ this.vaultMonitor.startReset(this.cellKey, VAULT_CONDUCTOR, ctx);
3146
+ ctx.traceId = ctx.traceId ?? assignTraceId();
3147
+ this.#resetConductor();
3148
+ this.resetBehaviors(ctx);
3149
+ this.#resetControllers(ctx);
3150
+ // Reset controllers as well.
3151
+ this.vaultMonitor.endReset(this.cellKey, VAULT_CONDUCTOR, ctx);
3152
+ }
3153
+ /**
3154
+ * Destroys pipeline + controller state for this FeatureCell.
3155
+ *
3156
+ * FeatureCell should call this from its own destroy() method.
3157
+ */
3158
+ destroy(ctx) {
3159
+ vaultDebug(`${VAULT_CONDUCTOR} - destroy`);
3160
+ ctx.traceId = ctx.traceId ?? assignTraceId();
3161
+ this.vaultMonitor.startDestroy(this.cellKey, VAULT_CONDUCTOR, ctx);
3162
+ this.#resetConductor();
3163
+ this.destroyBehaviors(ctx);
3164
+ this.#destroyControllers(ctx);
3165
+ // Tear down controllers.
3166
+ this.#events$.complete();
3167
+ this.vaultMonitor.endDestroy(this.cellKey, VAULT_CONDUCTOR, ctx);
3168
+ }
3169
+ //#endregion
3170
+ //#region Private Methods
3171
+ async #processEvent(ctx, options) {
3172
+ if (ctx.operation === OperationTypes.Initialize) {
3173
+ await this.initializeFeatureCell(ctx);
3174
+ return;
3175
+ }
3176
+ if (ctx.operation === OperationTypes.Replace || ctx.operation === OperationTypes.Merge) {
3177
+ await this.orchestrate(ctx, options);
3178
+ return;
3179
+ }
3180
+ this.vaultMonitor.runtimeError(this.cellKey, VAULT_CONDUCTOR, ctx, new Error(`Unknown operation type: "${ctx.operation}"`));
3181
+ this.#completeCurrentAttempt(ctx);
3182
+ }
3183
+ #enqueueMicrotask() {
3184
+ queueMicrotask(() => {
3185
+ this.#denyAttemptCompleted();
3186
+ });
3187
+ }
3188
+ /**
3189
+ * Enqueue an attempt and trigger queue processing if idle.
3190
+ */
3191
+ #enqueueAttempt(pending) {
3192
+ if (this.#conductorLicenseStatus === ConductorLicenseStatusTypes.Pending ||
3193
+ this.#conductorLicenseStatus === ConductorLicenseStatusTypes.Approved) {
3194
+ this.vaultMonitor.startControllerAttempt(this.cellKey, pending.behaviorCtx.traceId, pending.controllerCtx);
3195
+ this.#attemptQueue.push(pending);
3196
+ if (this.#conductorLicenseStatus === ConductorLicenseStatusTypes.Approved) {
3197
+ if (!this.#processing && this.#attemptQueue.length === 1) {
3198
+ this.#processQueue();
3199
+ }
3200
+ else {
3201
+ if (this.#isDenyStateForTesting) {
3202
+ this.#enqueueMicrotask();
3203
+ }
3204
+ }
3205
+ }
3206
+ else {
3207
+ this.#enqueueMicrotask();
3208
+ }
3209
+ }
3210
+ else {
3211
+ this.#enqueueMicrotask();
3212
+ }
3213
+ }
3214
+ #completeCurrentAttempt(ctx) {
3215
+ const head = this.#attemptQueue[0];
3216
+ /* istanbul ignore next */
3217
+ if (!head || head.finalized)
3218
+ return;
3219
+ head.finalized = true;
3220
+ /**
3221
+ * IMPORTANT: Finalization MUST occur inside a microtask.
3222
+ *
3223
+ * Why?
3224
+ * The conductor processes controller outcomes and orchestrator work
3225
+ * within the same JavaScript turn. If `notifyFinalize()` runs
3226
+ * synchronously here, it can:
3227
+ *
3228
+ * - Re-enter the pipeline before the current call stack fully drains
3229
+ * - Trigger controller/orchestrator side effects in the same tick
3230
+ * - Cause double-finalization or out-of-order queue mutation
3231
+ * - Break `vaultSettled()` determinism in DevMode
3232
+ *
3233
+ * By deferring to a microtask:
3234
+ *
3235
+ * 1. The current pipeline cycle fully drains.
3236
+ * 2. All synchronous controller emissions complete.
3237
+ * 3. The attempt is finalized in a clean turn boundary.
3238
+ * 4. Queue mutation (shift) happens atomically with finalize.
3239
+ * 5. `#processQueue()` runs only after processing state resets.
3240
+ *
3241
+ * DO NOT:
3242
+ * - Move `notifyFinalize()` outside this microtask.
3243
+ * - Split this into multiple microtasks.
3244
+ * - Make queue mutation synchronous.
3245
+ *
3246
+ * The ordering inside this microtask is intentional:
3247
+ *
3248
+ * notifyFinalize →
3249
+ * shift queue →
3250
+ * reset processing flag →
3251
+ * signal vault settled →
3252
+ * process next attempt
3253
+ *
3254
+ * This guarantees:
3255
+ * - Single active attempt at a time
3256
+ * - No reentrancy
3257
+ * - Deterministic queue progression
3258
+ * - Stable DevMode/test flushing behavior
3259
+ */
3260
+ queueMicrotask(() => {
3261
+ this.decisionEngine.notifyFinalize(ctx);
3262
+ this.#attemptQueue.shift();
3263
+ this.#processing = false;
3264
+ this.#attemptCompleted();
3265
+ this.#processQueue();
3266
+ });
3267
+ }
3268
+ #resetProcessingState(ctx, outcome) {
3269
+ this.vaultMonitor.restartControllerAttempt(this.cellKey, ctx.traceId, ctx, outcome);
3270
+ this.#processing = false;
3271
+ }
3272
+ /**
3273
+ * Central queue processor:
3274
+ * - ensures only one attempt is processed at a time
3275
+ * - runs controllers for the active attempt
3276
+ * - on Allow → runs Orchestrator
3277
+ * - on Reset/other → calls reset() for that context
3278
+ * - then continues to the next queued attempt
3279
+ */
3280
+ async #processQueue() {
3281
+ if (this.#processing)
3282
+ return;
3283
+ if (!this.#attemptQueue.length)
3284
+ return;
3285
+ this.#processing = true;
3286
+ const activeAttempt = this.#attemptQueue[0];
3287
+ // istanbul ignore next line - defensive only
3288
+ if (!activeAttempt) {
3289
+ // istanbul ignore next line - defensive only
3290
+ this.#processing = false;
3291
+ // istanbul ignore next line - defensive only
3292
+ return;
3293
+ }
3294
+ try {
3295
+ const outcome = await firstValueFrom(this.#runControllers(activeAttempt));
3296
+ const pending = this.#attemptQueue[0];
3297
+ if (!pending) {
3298
+ this.#processing = false;
3299
+ return;
3300
+ }
3301
+ const { behaviorCtx, options } = pending;
3302
+ let isDeny = false;
3303
+ switch (outcome) {
3304
+ // ───────────────────────────────────────────────
3305
+ // ALLOW → orchestrator will run → outcome will later drive CLEAR
3306
+ // ───────────────────────────────────────────────
3307
+ case DecisionOutcomeTypes.Abstain: {
3308
+ vaultDebug(`${this.cellKey} DecisionOutcome: "${DecisionOutcomeTypes.Abstain} received. Process Event dispatched.`);
3309
+ await this.#processEvent(behaviorCtx, options);
3310
+ break;
3311
+ }
3312
+ // ───────────────────────────────────────────────
3313
+ // FAIL → request is dead → remove + continue
3314
+ // ───────────────────────────────────────────────
3315
+ case DecisionOutcomeTypes.Abort: {
3316
+ this.controllerOutcomeNotification(DecisionOutcomeTypes.Abort, behaviorCtx);
3317
+ this.vaultMonitor.endControllerAttempt(this.cellKey, behaviorCtx.traceId, behaviorCtx, {
3318
+ status: outcome
3319
+ });
3320
+ this.#completeCurrentAttempt(behaviorCtx);
3321
+ break;
3322
+ }
3323
+ // ───────────────────────────────────────────────
3324
+ // DENY → pipeline blocked → keep attempt at head, do NOT shift
3325
+ // ───────────────────────────────────────────────
3326
+ case DecisionOutcomeTypes.Deny: {
3327
+ // do nothing — wait for external change
3328
+ this.#denyAttemptCompleted();
3329
+ isDeny = true;
3330
+ this.#processing = false;
3331
+ this.vaultMonitor.notifyConductorDeny(this.cellKey, behaviorCtx.traceId, behaviorCtx);
3332
+ this.controllerOutcomeNotification(DecisionOutcomeTypes.Deny, behaviorCtx);
3333
+ break;
3334
+ }
3335
+ }
3336
+ if (isDeny) {
3337
+ this.#isDenyStateForTesting = true;
3338
+ }
3339
+ else {
3340
+ this.#isDenyStateForTesting = false;
3341
+ return this.#processQueue();
3342
+ }
3343
+ // ───────────────────────────────────────────────
3344
+ // ERROR → treat as fail, shift queue, continue
3345
+ // ───────────────────────────────────────────────
3346
+ /*********************************
3347
+ * Catastrophic unreachable state — controller pipeline failed outside arbitrator
3348
+ * on 2025-12-10 I made the decision to leave it though I'm not a fan of untestable
3349
+ * and should be pruned code
3350
+ *********************************/
3351
+ }
3352
+ catch (err) {
3353
+ //istanbul ignore next
3354
+ vaultError('[conductor] Unreachable subscription error', err);
3355
+ //istanbul ignore next
3356
+ this.vaultMonitor.conductorCrashed(this.cellKey, activeAttempt?.controllerCtx.traceId ?? 'unknown', activeAttempt?.controllerCtx ?? { traceId: 'unknown' }, err);
3357
+ // Try to drop the corrupted request to recover
3358
+ //istanbul ignore next
3359
+ this.#attemptQueue.shift();
3360
+ //istanbul ignore next
3361
+ this.#processQueue();
3362
+ }
3363
+ }
3364
+ #registerDecisionEngine() {
3365
+ this.decisionEngine = new DecisionEngine(this.#controllers, this.#events$);
3366
+ this.#events$.subscribe({
3367
+ next: (event) => {
3368
+ if (event.type === ControllerEventTypes.LicenseDenied) {
3369
+ this.vaultMonitor.conductorLicenseDenied(this.cellKey, event.traceId);
3370
+ this.#conductorLicenseStatus = ConductorLicenseStatusTypes.Denied;
3371
+ const error = new Error(`${this.cellKey} Conductor Decision Engine: The FeatureCell received a "License Denied". Pipeline is disabled.`);
3372
+ vaultDebug(error.message);
3373
+ this.privateErrorService.setError(createVaultError(error, this.cellKey));
3374
+ this.#attemptQueue.length = 0;
3375
+ return;
3376
+ }
3377
+ if (event.type === ControllerEventTypes.LicenseApproved) {
3378
+ this.vaultMonitor.conductorLicenseApproved(this.cellKey, event.traceId);
3379
+ this.#conductorLicenseStatus = ConductorLicenseStatusTypes.Approved;
3380
+ vaultDebug(`${this.cellKey} Conductor Decision Engine: License Approved.`);
3381
+ this.#processQueue();
3382
+ return;
3383
+ }
3384
+ const head = this.#attemptQueue[0];
3385
+ if (!head)
3386
+ return;
3387
+ // Defensive guard: with serialized pipeline execution,
3388
+ // controller events cannot arrive out of order.
3389
+ // This is retained as a safety net against future regressions.
3390
+ /* istanbul ignore next */
3391
+ if (head.controllerCtx.traceId !== event.traceId) {
3392
+ vaultDebug(`The head ctx is not the same as the event. ${head.controllerCtx.traceId} != ${event.traceId}`);
3393
+ return;
3394
+ }
3395
+ // For both Success and Fail, this request is DONE from the queue’s POV
3396
+ switch (event.type) {
3397
+ case ControllerEventTypes.Success: {
3398
+ this.vaultMonitor.endControllerAttempt(this.cellKey, head.behaviorCtx.traceId, head.controllerCtx, {
3399
+ status: 'success'
3400
+ });
3401
+ this.#completeCurrentAttempt(head.controllerCtx);
3402
+ break;
3403
+ }
3404
+ case ControllerEventTypes.Failure: {
3405
+ this.vaultMonitor.endControllerAttempt(this.cellKey, head.behaviorCtx.traceId, head.controllerCtx, {
3406
+ status: 'failure'
3407
+ });
3408
+ this.#resetProcessingState(head.behaviorCtx, event.type);
3409
+ break;
3410
+ }
3411
+ case ControllerEventTypes.Abort: {
3412
+ this.vaultMonitor.conductorAbort(this.cellKey, event.traceId, head.controllerCtx);
3413
+ vaultDebug(`${this.cellKey} Conductor Decision Engine: Abort request received for Behavior TraceId: ${head.controllerCtx.traceId}.`);
3414
+ this.#completeCurrentAttempt(head.controllerCtx);
3415
+ break;
3416
+ }
3417
+ case ControllerEventTypes.Revote: {
3418
+ vaultDebug(`${this.cellKey} Conductor Decision Engine: Revote request received for Behavior TraceId: ${head.controllerCtx.traceId}.`);
3419
+ this.vaultMonitor.conductorRevote(this.cellKey, event.traceId, head.controllerCtx);
3420
+ this.#processing = false;
3421
+ vaultDebug(`${this.cellKey} Conductor Decision Engine: processQueue event dispatched for Behavior TraceId: ${head.controllerCtx.traceId}.`);
3422
+ this.#processQueue();
3423
+ break;
3424
+ }
3425
+ }
3426
+ }
3427
+ });
3428
+ }
3429
+ #buildPipelineCtx(ctx, operation, options) {
3430
+ const traceId = assignTraceId();
3431
+ return {
3432
+ // shared, live references (DO NOT CLONE)
3433
+ destroyed$: ctx.destroyed$,
3434
+ reset$: ctx.reset$,
3435
+ state$: ctx.state$,
3436
+ featureCellKey: ctx.featureCellKey,
3437
+ state: ctx.state,
3438
+ lastSnapshot: ctx.lastSnapshot,
3439
+ options: options != null ? isolateValue(options) : options,
3440
+ // per-attempt immutable values
3441
+ traceId,
3442
+ operation,
3443
+ resolveType: undefined,
3444
+ incoming: undefined
3445
+ };
3446
+ }
3447
+ #ensureIncoming(ctx) {
3448
+ const incoming = this.preparingIncoming(ctx);
3449
+ if (isVaultNoop(incoming)) {
3450
+ this.vaultMonitor.startCoreState(this.cellKey, VAULT_CONDUCTOR, ctx);
3451
+ this.reset(ctx);
3452
+ this.vaultMonitor.endCoreState(this.cellKey, VAULT_CONDUCTOR, ctx);
3453
+ }
3454
+ return incoming;
3455
+ }
3456
+ #resetConductor() {
3457
+ this.#attemptQueue.length = 0;
3458
+ this.#processing = false;
3459
+ }
3460
+ #destroyControllers(ctx) {
3461
+ for (const controller of this.#controllers) {
3462
+ this.vaultMonitor.startDestroy(this.cellKey, controller.key, ctx);
3463
+ try {
3464
+ controller.destroy?.();
3465
+ this.vaultMonitor.endDestroy(this.cellKey, controller.key, ctx);
3466
+ }
3467
+ catch (err) {
3468
+ vaultError(`${controller.key} destroy() failed`, err);
3469
+ this.vaultMonitor.endDestroy(this.cellKey, controller.key, ctx, { destroyFailed: true });
3470
+ }
3471
+ }
3472
+ }
3473
+ #resetControllers(ctx) {
3474
+ for (const controller of this.#controllers) {
3475
+ this.vaultMonitor.startReset(this.cellKey, controller.key, ctx);
3476
+ try {
3477
+ controller.reset?.();
3478
+ this.vaultMonitor.endReset(this.cellKey, controller.key, ctx);
3479
+ }
3480
+ catch (err) {
3481
+ vaultError(`${controller.key} reset() failed`, err);
3482
+ this.vaultMonitor.endReset(this.cellKey, controller.key, ctx, { resetFailed: true });
3483
+ }
3484
+ }
3485
+ }
3486
+ #addDefaultErrorController(controllers, config) {
3487
+ const errorControllers = config.controllers.filter((controllerClass) => {
3488
+ return controllerClass.type === ControllerTypes.Error;
3489
+ });
3490
+ if (errorControllers.length > 1) {
3491
+ const names = errorControllers.map((errorController) => errorController.key).join(', ');
3492
+ throw new Error(`SDuX Error: More than one ErrorController was provided. Only one error policy can be active per FeatureCell. Received: ${names}. Fix: Remove additional error controllers or combine them into a single controller.`);
3493
+ }
3494
+ if (errorControllers.length === 1) {
3495
+ controllers.push(errorControllers[0]);
3496
+ }
3497
+ else {
3498
+ controllers.unshift(withCoreErrorController);
3499
+ }
3500
+ }
3501
+ // ---------------------------------------------------------------------------
3502
+ // INTERNAL SCHEDULING
3503
+ // ---------------------------------------------------------------------------
3504
+ #registerControllers(config) {
3505
+ config.controllers = config.controllers ?? [];
3506
+ const allControllers = config.controllers.filter(Boolean);
3507
+ this.#addDefaultErrorController(allControllers, config);
3508
+ allControllers.unshift(withCoreLicenseController);
3509
+ allControllers.unshift(withCoreAbstainController);
3510
+ // eslint-disable-next-line
3511
+ const controllerMetadata = allControllers.map((controller) => {
3512
+ const meta = controller[CONTROLLER_META];
3513
+ return {
3514
+ key: controller.key,
3515
+ type: meta.type,
3516
+ critical: meta.critical,
3517
+ needsLicense: meta.needsLicense
3518
+ };
3519
+ });
3520
+ LicensingService().describeControllers({
3521
+ featureCellKey: this.cellKey,
3522
+ controllers: controllerMetadata
3523
+ });
3524
+ const controllerInit = new ControllerInitializationClass(config.cell.key);
3525
+ this.#controllers = controllerInit.initializeControllers(allControllers, this.#events$, config.behaviorConfigs);
3526
+ this.#registerDecisionEngine();
3527
+ }
3528
+ /**
3529
+ * Run controllers for a single pending attempt and normalize their decision.
3530
+ *
3531
+ * Returns:
3532
+ * - ControllerVotes.Allow
3533
+ * - ControllerVotes.Reset
3534
+ *
3535
+ * All more detailed opinions (Deny/Retry/Buffer/Abstain) are reduced
3536
+ * at this layer for now.
3537
+ */
3538
+ #runControllers(pending) {
3539
+ this.vaultMonitor.startControllerVote(this.cellKey, pending.controllerCtx.traceId, pending.controllerCtx);
3540
+ return this.decisionEngine.evaluateAttempt(pending.controllerCtx)?.pipe(tap((decision) => {
3541
+ this.vaultMonitor.endControllerVote(this.cellKey, pending.controllerCtx.traceId, pending.controllerCtx, decision);
3542
+ }), map((decision) => decision.outcome));
3543
+ }
3544
+ #denyAttemptCompleted() {
3545
+ if (!DevMode.active)
3546
+ return;
3547
+ this.#settled$.next();
3548
+ }
3549
+ // ADD
3550
+ #attemptCompleted() {
3551
+ if (!DevMode.active || this.#attemptQueue.length > 0)
3552
+ return;
3553
+ queueMicrotask(() => {
3554
+ this.#settled$.next();
3555
+ });
3556
+ }
3557
+ #conductorSettled() {
3558
+ return firstValueFrom(this.#settled$);
3559
+ }
3560
+ }
3561
+
3562
+ /**
3563
+ * Canonical identifier used to label FeatureCell-related ownership or origin.
3564
+ */
3565
+ const VAULT_FEATURE_CELL = 'vault-feature-cell';
3566
+
3567
+ /**
3568
+ * Validates a `FeatureCelldescriptorModel` configuration prior to FeatureCell
3569
+ * initialization. This function ensures that the descriptor is structurally
3570
+ * correct and that conflicting behaviors are not registered on the same
3571
+ * FeatureCell instance.
3572
+ *
3573
+ * The validation enforces two primary constraints:
3574
+ *
3575
+ * 1. **Initial State Shape Validation**
3576
+ * The `initial` value must represent plain unwrapped data (e.g., `{}`, `[]`,
3577
+ * or primitives). Passing a resource-like object containing fields such as
3578
+ * `{ loading, data, error }` is not permitted because FeatureCells wrap
3579
+ * values internally and would otherwise double-encapsulate resource state.
3580
+ *
3581
+ * 2. **Encryption Behavior Exclusivity**
3582
+ * A FeatureCell may register at most one encryption behavior. Multiple
3583
+ * encryption behaviors introduce conflicting state guarantees and are
3584
+ * rejected during validation.
3585
+ *
3586
+ * @typeParam T - The type representing the FeatureCell’s state shape.
3587
+ *
3588
+ * @param descriptor - The declared FeatureCell descriptor, including key and initial state.
3589
+ * @param behaviors - A list of behavior classes registered for this FeatureCell.
3590
+ *
3591
+ * @throws Error if the initial state contains resource-like fields.
3592
+ * @throws Error if more than one encryption behavior is provided.
3593
+ */
3594
+ function featureCellValidation(descriptor, behaviors = []) {
3595
+ // Prevent incorrect initialization (e.g., passing a resource object)
3596
+ if (typeof descriptor.initialState === 'object' &&
3597
+ descriptor.initialState !== null &&
3598
+ // eslint-disable-next-line
3599
+ 'data' in descriptor.initialState) {
3600
+ throw new Error(`[vault] Invalid FeatureCelldescriptorModel.initial for feature "${descriptor.key}". Expected raw data (e.g., [] or {}), but received an object with resource fields { loading, data, error }. Pass plain data to avoid double-wrapping.`);
3601
+ }
3602
+ const encryptBehaviors = behaviors.filter((behavior) => behavior.type === BehaviorTypes.Encrypt);
3603
+ if (encryptBehaviors.length > 1) {
3604
+ throw new Error(`[vault] FeatureCell cannot register multiple encryption behaviors.`);
3605
+ }
3606
+ }
3607
+
3608
+ // --- AI Model File Path (DO NOT DELETE) ---
3609
+ // FilePath: projects > engine > src > lib > factories > feature-cell > feature-cell.builder.ts
3610
+ // Updated: 2026-03-02 19:52
3611
+ // Generated by pathcomment [tab] (see .vscode/typescript.code-snippets) or
3612
+ // cmd+alt+j (see .vscode/keybindings.json)
3613
+ // --- END AI MODEL FILE PATH ---
3614
+ class FeatureCellBuilder {
3615
+ featureCellConfiguration;
3616
+ defaultBehaviors;
3617
+ behaviors;
3618
+ controllers;
3619
+ #cellCorrupt = false;
3620
+ #conductor;
3621
+ #initialized = false;
3622
+ #vaultMonitor = VaultMonitor();
3623
+ cell;
3624
+ cellKey;
3625
+ ctx;
3626
+ destroyed$ = new Subject();
3627
+ reset$ = new Subject();
3628
+ state$ = new Subject();
3629
+ constructor(
3630
+ // kept for parity with original API, even if not used directly
3631
+ featureCellConfiguration, defaultBehaviors, behaviors, controllers) {
3632
+ this.featureCellConfiguration = featureCellConfiguration;
3633
+ this.defaultBehaviors = defaultBehaviors;
3634
+ this.behaviors = behaviors;
3635
+ this.controllers = controllers;
3636
+ this.cellKey = this.featureCellConfiguration.key;
3637
+ this.ctx = this.#buildCtx();
3638
+ }
3639
+ #buildCtx() {
3640
+ const destroyed$ = this.destroyed$.asObservable();
3641
+ const state$ = this.state$;
3642
+ const reset$ = this.reset$.asObservable();
3643
+ const lastSnapshot = {
3644
+ isLoading: false,
3645
+ value: undefined,
3646
+ error: null,
3647
+ hasValue: false
3648
+ };
3649
+ const ctx = {
3650
+ destroyed$,
3651
+ featureCellKey: this.cellKey,
3652
+ reset$,
3653
+ state$,
3654
+ get state() {
3655
+ const snapshot = this.lastSnapshot;
3656
+ return {
3657
+ isLoading: snapshot.isLoading,
3658
+ value: snapshot.value,
3659
+ error: snapshot.error,
3660
+ hasValue: snapshot.hasValue
3661
+ };
3662
+ }
3663
+ };
3664
+ // 🔒 CRITICAL: Lock the reference forever
3665
+ Object.defineProperty(ctx, 'lastSnapshot', {
3666
+ value: lastSnapshot,
3667
+ writable: false, // ❗ cannot reassign
3668
+ configurable: false, // ❗ cannot redefine
3669
+ enumerable: true
3670
+ });
3671
+ return ctx;
3672
+ }
3673
+ // ---------------------------------------------------------------------------
3674
+ // INTERNAL LIFECYCLE
3675
+ // ---------------------------------------------------------------------------
3676
+ reset() {
3677
+ this.#vaultMonitor.startReset(this.cellKey, VAULT_FEATURE_CELL, this.ctx);
3678
+ vaultWarn(`${VAULT_FEATURE_CELL}: reset`);
3679
+ this.#ensureInitialized();
3680
+ this.reset$.next();
3681
+ this.#conductor?.reset(this.ctx);
3682
+ this.#vaultMonitor.endReset(this.cellKey, VAULT_FEATURE_CELL, this.ctx);
3683
+ }
3684
+ destroy() {
3685
+ this.#vaultMonitor.startDestroy(this.cellKey, VAULT_FEATURE_CELL, this.ctx);
3686
+ vaultWarn(`${VAULT_FEATURE_CELL}: destroy`);
3687
+ this.reset$.next();
3688
+ this.reset$.complete();
3689
+ this.#conductor?.destroy(this.ctx);
3690
+ this.destroyed$.next();
3691
+ this.destroyed$.complete();
3692
+ this.state$.complete();
3693
+ this.#vaultMonitor.endDestroy(this.cellKey, VAULT_FEATURE_CELL, this.ctx);
3694
+ }
3695
+ #ensureInitialized() {
3696
+ if (this.#cellCorrupt) {
3697
+ const errorMessage = `[vault] FeatureCell "${this.featureCellConfiguration.key}" encountered a critical initialization failure and is now in a corrupted state. Further use is blocked.`;
3698
+ this.#vaultMonitor.runtimeError(this.cellKey, VAULT_FEATURE_CELL, this.ctx, errorMessage);
3699
+ throw new Error(errorMessage);
3700
+ }
3701
+ if (!this.#initialized) {
3702
+ const errorMessage = `[vault] FeatureCell "${this.featureCellConfiguration.key}" has not been initialized. You must call cell.initialize() before using state methods.`;
3703
+ this.#vaultMonitor.runtimeError(this.cellKey, VAULT_FEATURE_CELL, this.ctx, errorMessage);
3704
+ throw new Error(errorMessage);
3705
+ }
3706
+ }
3707
+ #initialize(ctx) {
3708
+ if (this.#initialized) {
3709
+ const errorMessage = `[vault] FeatureCell "${this.featureCellConfiguration.key}" already initialized.`;
3710
+ this.#vaultMonitor.runtimeError(this.cellKey, VAULT_FEATURE_CELL, this.ctx, errorMessage);
3711
+ throw new Error(errorMessage);
3712
+ }
3713
+ try {
3714
+ // Register cell + start lifecycle event
3715
+ this.#vaultMonitor.registerCell(this.cellKey, this.featureCellConfiguration.insights);
3716
+ this.#vaultMonitor.startInitialized(this.cellKey, VAULT_FEATURE_CELL, this.ctx);
3717
+ featureCellValidation(this.featureCellConfiguration, this.behaviors);
3718
+ // This must proceed the orchestrator instantiation
3719
+ this.#initialized = true;
3720
+ // Orchestrator needs the concrete cell API
3721
+ this.#conductor = new Conductor({
3722
+ afterTapCallbacks: ctx.afterTapCallbacks,
3723
+ beforeTapCallbacks: ctx.beforeTapCallbacks,
3724
+ behaviors: this.behaviors,
3725
+ behaviorConfigs: ctx.behaviorConfigs,
3726
+ cell: this.cell,
3727
+ defaultBehaviors: this.defaultBehaviors,
3728
+ controllers: this.controllers,
3729
+ emitStateCallbacks: ctx.emitStateCallbacks,
3730
+ errorCallbacks: ctx.errorCallbacks,
3731
+ filterCallbacks: ctx.filterFunctions,
3732
+ initialState: ctx.hydrate || this.featureCellConfiguration.initialState,
3733
+ interceptors: ctx.interceptors,
3734
+ operators: ctx.operators,
3735
+ reducerCallbacks: ctx.reducerFunctions
3736
+ });
3737
+ this.#conductor.initialize(this.ctx);
3738
+ if (DevMode.active) {
3739
+ Object.defineProperty(this.cell, 'vaultSettled', {
3740
+ enumerable: false,
3741
+ configurable: false,
3742
+ writable: false,
3743
+ // eslint-disable-next-line
3744
+ value: () => this.#conductor.vaultSettled()
3745
+ });
3746
+ // eslint-disable-next-line
3747
+ registerVaultSettled(this.cellKey, this.#conductor.vaultSettled.bind(this.#conductor));
3748
+ }
3749
+ this.#vaultMonitor.endInitialized(this.cellKey, VAULT_FEATURE_CELL, this.ctx);
3750
+ }
3751
+ catch (err) {
3752
+ this.#cellCorrupt = true;
3753
+ this.#vaultMonitor.runtimeError(this.cellKey, VAULT_FEATURE_CELL, this.ctx, err);
3754
+ throw err;
3755
+ }
3756
+ }
3757
+ #handleCorruptionError(message) {
3758
+ this.#cellCorrupt = true;
3759
+ this.#vaultMonitor.runtimeError(this.cellKey, VAULT_FEATURE_CELL, this.ctx, message);
3760
+ throw new Error(message);
3761
+ }
3762
+ setup() {
3763
+ const afterTapCallbacks = [];
3764
+ const beforeTapCallbacks = [];
3765
+ const errorCallbacks = [];
3766
+ const filterFunctions = [];
3767
+ let hydrate;
3768
+ const interceptors = [];
3769
+ const operators = [];
3770
+ const reducerFunctions = [];
3771
+ const emitStateCallbacks = [];
3772
+ const behaviorConfigs = new Map();
3773
+ const builder = {
3774
+ behaviorConfigs,
3775
+ afterTaps: (incomingAfterTaps) => {
3776
+ if (this.#initialized) {
3777
+ this.#handleCorruptionError('Cannot call "afterTaps" after initialize(). Configuration must be done before initialization.');
3778
+ }
3779
+ if (Array.isArray(incomingAfterTaps)) {
3780
+ afterTapCallbacks.push(...incomingAfterTaps);
3781
+ }
3782
+ return builder;
3783
+ },
3784
+ beforeTaps: (incomingBeforeTaps) => {
3785
+ if (this.#initialized) {
3786
+ this.#handleCorruptionError('Cannot call "beforeTaps" after initialize(). Configuration must be done before initialization.');
3787
+ }
3788
+ if (Array.isArray(incomingBeforeTaps)) {
3789
+ beforeTapCallbacks.push(...incomingBeforeTaps);
3790
+ }
3791
+ return builder;
3792
+ },
3793
+ emitStates: (incomingEmitStates) => {
3794
+ if (this.#initialized) {
3795
+ this.#handleCorruptionError('Cannot call "emitStates" after initialize(). Configuration must be done before initialization.');
3796
+ }
3797
+ if (Array.isArray(incomingEmitStates)) {
3798
+ emitStateCallbacks.push(...incomingEmitStates);
3799
+ }
3800
+ return builder;
3801
+ },
3802
+ errors: (incomingErrors) => {
3803
+ if (this.#initialized) {
3804
+ this.#handleCorruptionError('Cannot call "errors" after initialize(). Configuration must be done before initialization.');
3805
+ }
3806
+ if (Array.isArray(incomingErrors)) {
3807
+ errorCallbacks.push(...incomingErrors);
3808
+ }
3809
+ return builder;
3810
+ },
3811
+ filters: (incomingFilters) => {
3812
+ if (this.#initialized) {
3813
+ this.#handleCorruptionError('Cannot call "filters" after initialize(). Configuration must be done before initialization.');
3814
+ }
3815
+ if (Array.isArray(incomingFilters)) {
3816
+ filterFunctions.push(...incomingFilters);
3817
+ }
3818
+ return builder;
3819
+ },
3820
+ hydrate: (incoming) => {
3821
+ if (this.#initialized) {
3822
+ this.#handleCorruptionError('Cannot call "hydrate" after initialize(). Configuration must be done before initialization.');
3823
+ }
3824
+ hydrate = incoming;
3825
+ return builder;
3826
+ },
3827
+ initialize: () => {
3828
+ this.#initialize({
3829
+ afterTapCallbacks,
3830
+ beforeTapCallbacks,
3831
+ behaviorConfigs,
3832
+ emitStateCallbacks,
3833
+ errorCallbacks,
3834
+ filterFunctions,
3835
+ hydrate,
3836
+ interceptors,
3837
+ operators,
3838
+ reducerFunctions
3839
+ });
3840
+ },
3841
+ interceptors: (incomingInterceptors) => {
3842
+ if (this.#initialized) {
3843
+ this.#handleCorruptionError('Cannot call "interceptors" after initialize(). Configuration must be done before initialization.');
3844
+ }
3845
+ if (Array.isArray(incomingInterceptors)) {
3846
+ interceptors.push(...incomingInterceptors);
3847
+ }
3848
+ return builder;
3849
+ },
3850
+ operators: (incomingOperators) => {
3851
+ if (this.#initialized) {
3852
+ this.#handleCorruptionError('Cannot call "operators" after initialize(). Configuration must be done before initialization.');
3853
+ }
3854
+ if (Array.isArray(incomingOperators)) {
3855
+ operators.push(...incomingOperators);
3856
+ }
3857
+ return builder;
3858
+ },
3859
+ reducers: (incomingReducers) => {
3860
+ if (this.#initialized) {
3861
+ this.#handleCorruptionError('Cannot call "reducers" after initialize(). Configuration must be done before initialization.');
3862
+ }
3863
+ if (Array.isArray(incomingReducers)) {
3864
+ reducerFunctions.push(...incomingReducers);
3865
+ }
3866
+ return builder;
3867
+ }
3868
+ };
3869
+ return builder;
3870
+ }
3871
+ // ---------------------------------------------------------------------------
3872
+ // STATE OPERATIONS
3873
+ // ---------------------------------------------------------------------------
3874
+ mergeState(incoming, options) {
3875
+ this.#ensureInitialized();
3876
+ return this.#conductor.conduct(this.ctx, incoming, OperationTypes.Merge, options);
3877
+ }
3878
+ replaceState(incoming, options) {
3879
+ this.#ensureInitialized();
3880
+ return this.#conductor.conduct(this.ctx, incoming, OperationTypes.Replace, options);
3881
+ }
3882
+ }
3883
+
3884
+ // --- AI Model File Path (DO NOT DELETE) ---
3885
+ // FilePath: projects > engine > src > lib > factories > feature-cell > feature-cell.class.ts
3886
+ // Updated: 2026-03-02 19:52
3887
+ // Generated by pathcomment [tab] (see .vscode/typescript.code-snippets) or
3888
+ // cmd+alt+j (see .vscode/keybindings.json)
3889
+ // --- END AI MODEL FILE PATH ---
3890
+ class FeatureCellClass extends FeatureCellBuilder {
3891
+ constructor(descriptor, defaultBehaviors, behaviors, controllers) {
3892
+ super(descriptor, defaultBehaviors, behaviors, controllers);
3893
+ }
3894
+ build() {
3895
+ const builder = this.setup();
3896
+ const ctx = this.ctx;
3897
+ const cell = {
3898
+ afterTaps: builder.afterTaps,
3899
+ beforeTaps: builder.beforeTaps,
3900
+ destroy: this.destroy.bind(this),
3901
+ destroyed$: this.destroyed$.asObservable(),
3902
+ errors: builder.errors,
3903
+ filters: builder.filters,
3904
+ hydrate: builder.hydrate,
3905
+ initialize: builder.initialize,
3906
+ interceptors: builder.interceptors,
3907
+ key: this.cellKey,
3908
+ mergeState: this.mergeState.bind(this),
3909
+ operators: builder.operators,
3910
+ reducers: builder.reducers,
3911
+ emitStates: builder.emitStates,
3912
+ replaceState: this.replaceState.bind(this),
3913
+ reset$: this.reset$.asObservable(),
3914
+ reset: this.reset.bind(this),
3915
+ state$: this.state$.asObservable(),
3916
+ get state() {
3917
+ return {
3918
+ isLoading: ctx.lastSnapshot.isLoading,
3919
+ value: ctx.lastSnapshot.value,
3920
+ error: ctx.lastSnapshot.error,
3921
+ hasValue: ctx.lastSnapshot.hasValue
3922
+ };
3923
+ }
3924
+ };
3925
+ // Keep a reference for orchestrator creation
3926
+ this.cell = cell;
3927
+ this.behaviors.forEach((behavior) => {
3928
+ behavior?.installFluentApi?.(this.cell, builder.behaviorConfigs);
3929
+ });
3930
+ this.controllers.forEach((controller) => {
3931
+ controller?.installFluentApi?.(this.cell, builder.behaviorConfigs);
3932
+ });
3933
+ Object.defineProperty(cell, 'ctx', {
3934
+ value: this.ctx,
3935
+ enumerable: false,
3936
+ writable: false
3937
+ });
3938
+ Object.defineProperty(cell, 'key', {
3939
+ value: this.featureCellConfiguration.key,
3940
+ enumerable: false,
3941
+ writable: false
3942
+ });
3943
+ return cell;
3944
+ }
3945
+ }
3946
+
3947
+ class LicensingAbstract {
3948
+ static needsLicense;
3949
+ static key;
3950
+ #licenseToken;
3951
+ #featureCellKey;
3952
+ #key;
3953
+ #licenseService;
3954
+ constructor(ctx) {
3955
+ const ctor = this.constructor;
3956
+ if (typeof ctor.key !== 'string' || !ctor.key.trim()) {
3957
+ throw new VaultLicenseError(`LicensingClass requires a static "key". Did you forget @VaultBehavior?`);
3958
+ }
3959
+ this.#licenseService = LicensingService();
3960
+ this.#key = ctor.key;
3961
+ this.#featureCellKey = ctx.featureCellKey;
3962
+ if (ctor.needsLicense) {
3963
+ this.#requestLicense();
3964
+ }
3965
+ }
3966
+ #requestLicense() {
3967
+ this.#licenseToken = this.#licenseService.requestLicense(this.#featureCellKey, this.#key);
3968
+ }
3969
+ validateLicense(valid) {
3970
+ if (!this.#licenseToken) {
3971
+ throw new VaultLicenseError(`validateLicense() called but no license was requested for "${this.#featureCellKey}" and "${this.#key}".`);
3972
+ }
3973
+ this.#licenseService.validateLicense(this.#featureCellKey, this.#key, this.#licenseToken, valid);
3974
+ }
3975
+ }
3976
+
3977
+ /**
3978
+ * The feature cell tokens map
3979
+ */
3980
+ const featureCellTokens = new Map();
3981
+ /**
3982
+ * Tracks whether a FeatureCell token has already been retrieved
3983
+ */
3984
+ const featureCellTokenRequested = new Map();
3985
+ /**
3986
+ * Internal token resolution mechanism used by both createFeatureCellToken()
3987
+ * and getFeatureCellToken().
3988
+ *
3989
+ * Enforces:
3990
+ * - unique FeatureCell keys
3991
+ * - single retrieval per key (unless dev mode)
3992
+ */
3993
+ function internalFeatureCellToken(key, isCreate) {
3994
+ // ──────────────────────────────────────────────
3995
+ // CREATE MODE
3996
+ // ──────────────────────────────────────────────
3997
+ if (isCreate) {
3998
+ if (featureCellTokens.has(key)) {
3999
+ if (!DevMode.active) {
4000
+ const existing = featureCellTokens.get(key);
4001
+ throw new Error(`[vault] Duplicate FeatureCell key detected: "${key}". ` +
4002
+ `Each FeatureCell must have a unique key. Existing token: "${existing?.key}"`);
4003
+ }
4004
+ return featureCellTokens.get(key);
4005
+ }
4006
+ const token = {
4007
+ key
4008
+ };
4009
+ // const token: FeatureCellToken = { key };
4010
+ featureCellTokens.set(key, token);
4011
+ return token;
4012
+ }
4013
+ // ──────────────────────────────────────────────
4014
+ // RETRIEVE MODE
4015
+ // ──────────────────────────────────────────────
4016
+ if (!featureCellTokens.has(key)) {
4017
+ throw new Error(`[vault] FeatureCell token not found for key "${key}". You must call provideFeatureCell() before retrieving this FeatureCell.`);
4018
+ }
4019
+ if (featureCellTokenRequested.has(key)) {
4020
+ if (!DevMode.active) {
4021
+ throw new Error(`[vault] FeatureCell "${key}" can only be owned by a single consumer.`);
4022
+ }
4023
+ return featureCellTokens.get(key);
4024
+ }
4025
+ featureCellTokenRequested.set(key, true);
4026
+ return featureCellTokens.get(key);
4027
+ }
4028
+ /**
4029
+ * Creates a FeatureCell token for the specified key.
4030
+ */
4031
+ function createFeatureCellToken(key) {
4032
+ return internalFeatureCellToken(key, true);
4033
+ }
4034
+ /**
4035
+ * Retrieves the FeatureCell token associated with a key.
4036
+ */
4037
+ function getFeatureCellToken(key) {
4038
+ return internalFeatureCellToken(key, false);
4039
+ }
4040
+ /**
4041
+ * Clears FeatureCell token state for test isolation.
4042
+ *
4043
+ * This must be called explicitly by test utilities.
4044
+ * No framework assumptions are made.
4045
+ */
4046
+ function resetFeatureCellToken() {
4047
+ // DevMode already gates unsafe behavior
4048
+ if (!DevMode.active)
4049
+ return;
4050
+ featureCellTokens.clear();
4051
+ featureCellTokenRequested.clear();
4052
+ }
4053
+
4054
+ // --- AI Model File Path (DO NOT DELETE) ---
4055
+ // FilePath: lib > public-api.ts
4056
+ // Updated: 2026-03-30 15:42
4057
+ // Generated by pathcomment [tab] (see .vscode/typescript.code-snippets) or
4058
+ // cmd+alt+j (see .vscode/keybindings.json)
4059
+ // --- END AI MODEL FILE PATH ---
4060
+ /*
4061
+ * This is for version support in dev mode and tracking in the devtools
4062
+ */
4063
+
4064
+ /**
4065
+ * Generated bundle index. Do not edit.
4066
+ */
4067
+
4068
+ export { Conductor, FeatureCellClass, LicensingAbstract, VaultCore, createFeatureCellToken, getFeatureCellToken, getLicensePayload, getVaultRegistryForTests, isPipelineTerminal, registerFeatureCell, registerVaultSettled, resetFeatureCellRegistry, resetVaultForTests, vaultAllSettled, vaultSettled };
4069
+ //# sourceMappingURL=sdux-vault-engine.mjs.map