@memberjunction/server 5.51.2 → 5.51.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -1
- package/dist/agentSessions/SessionManager.d.ts +5 -2
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +17 -8
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
- package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
- package/dist/agentSessions/SessionRunIDVerifier.js +136 -0
- package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
- package/dist/agentSessions/index.d.ts +1 -0
- package/dist/agentSessions/index.d.ts.map +1 -1
- package/dist/agentSessions/index.js +1 -0
- package/dist/agentSessions/index.js.map +1 -1
- package/dist/auth/actionAuthorization.d.ts +40 -0
- package/dist/auth/actionAuthorization.d.ts.map +1 -0
- package/dist/auth/actionAuthorization.js +94 -0
- package/dist/auth/actionAuthorization.js.map +1 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/scopeLimitedPrincipal.d.ts +39 -0
- package/dist/auth/scopeLimitedPrincipal.d.ts.map +1 -0
- package/dist/auth/scopeLimitedPrincipal.js +49 -0
- package/dist/auth/scopeLimitedPrincipal.js.map +1 -0
- package/dist/config.d.ts +53 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +34 -1
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +6 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +24 -34
- package/dist/context.js.map +1 -1
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +3262 -3273
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +124 -4
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +170 -15
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/FireAndForgetHeartbeat.d.ts +5 -0
- package/dist/generic/FireAndForgetHeartbeat.d.ts.map +1 -1
- package/dist/generic/FireAndForgetHeartbeat.js +5 -4
- package/dist/generic/FireAndForgetHeartbeat.js.map +1 -1
- package/dist/generic/PushStatusResolver.d.ts +58 -0
- package/dist/generic/PushStatusResolver.d.ts.map +1 -1
- package/dist/generic/PushStatusResolver.js +42 -3
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +148 -3
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +287 -30
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +12 -0
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -50
- package/dist/index.js.map +1 -1
- package/dist/multiTenancy/index.d.ts.map +1 -1
- package/dist/multiTenancy/index.js +22 -2
- package/dist/multiTenancy/index.js.map +1 -1
- package/dist/postgresqlCompat.d.ts +8 -0
- package/dist/postgresqlCompat.d.ts.map +1 -0
- package/dist/postgresqlCompat.js +19 -0
- package/dist/postgresqlCompat.js.map +1 -0
- package/dist/postgresqlPoolSettings.d.ts +108 -0
- package/dist/postgresqlPoolSettings.d.ts.map +1 -0
- package/dist/postgresqlPoolSettings.js +134 -0
- package/dist/postgresqlPoolSettings.js.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +2 -2
- package/dist/realtimeWidget/widgetGuestElevation.js +2 -2
- package/dist/resolvers/ActionResolver.d.ts +31 -5
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +75 -14
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts +39 -17
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +97 -157
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/DatasetResolver.d.ts +24 -3
- package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
- package/dist/resolvers/DatasetResolver.js +57 -8
- package/dist/resolvers/DatasetResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +14 -4
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +18 -6
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts +33 -7
- package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.js +117 -38
- package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +13 -1
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +13 -16
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +26 -14
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +63 -46
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +24 -27
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +30 -40
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +12 -11
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTestResolver.js +90 -112
- package/dist/resolvers/RunTestResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +11 -2
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/VersionHistoryResolver.d.ts.map +1 -1
- package/dist/resolvers/VersionHistoryResolver.js +8 -12
- package/dist/resolvers/VersionHistoryResolver.js.map +1 -1
- package/dist/resolvers/adhocTimeout.d.ts +7 -0
- package/dist/resolvers/adhocTimeout.d.ts.map +1 -0
- package/dist/resolvers/adhocTimeout.js +12 -0
- package/dist/resolvers/adhocTimeout.js.map +1 -0
- package/dist/rest/ViewOperationsHandler.d.ts +3 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +29 -17
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/services/TaskOrchestrator.d.ts.map +1 -1
- package/dist/services/TaskOrchestrator.js +17 -15
- package/dist/services/TaskOrchestrator.js.map +1 -1
- package/package.json +90 -89
- package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
- package/src/__tests__/AdhocQueryResolver.bugs.test.ts +14 -35
- package/src/__tests__/AdhocQueryResolver.pagination.test.ts +6 -2
- package/src/__tests__/AdhocQueryResolver.scopeGuard.test.ts +84 -0
- package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
- package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
- package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
- package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -0
- package/src/__tests__/ListOperationsResolver.adhocScreen.test.ts +136 -0
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +318 -0
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +66 -27
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +134 -0
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +221 -0
- package/src/__tests__/ResolverBase.aggregateScreen.test.ts +173 -0
- package/src/__tests__/ResolverBase.balancedClause.test.ts +155 -0
- package/src/__tests__/ResolverBase.clauseUnscopedRead.test.ts +116 -0
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +351 -0
- package/src/__tests__/RunTemplateResolver.test.ts +132 -0
- package/src/__tests__/RunViewResolver.cacheCheckScreen.test.ts +164 -0
- package/src/__tests__/SessionManager.test.ts +88 -13
- package/src/__tests__/SessionRunIDVerifier.test.ts +255 -0
- package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
- package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +155 -0
- package/src/__tests__/actionAuthorization.test.ts +130 -0
- package/src/__tests__/adhocTimeoutClamp.test.ts +26 -0
- package/src/__tests__/cacheInvalidationRecordData.test.ts +241 -0
- package/src/__tests__/multiTenancy.postgresRewrite.test.ts +180 -0
- package/src/__tests__/multiTenancy.security.test.ts +51 -0
- package/src/__tests__/multiTenancy.test.ts +1 -1
- package/src/__tests__/newUserRolesDefault.test.ts +81 -0
- package/src/__tests__/postgresqlPoolSettings.test.ts +131 -0
- package/src/__tests__/pushStatusResolver.filter.test.ts +56 -0
- package/src/__tests__/resolverBase.refusalMessage.test.ts +88 -0
- package/src/__tests__/resolverBase.rls.test.ts +2 -0
- package/src/__tests__/translateBracketsToPG.test.ts +20 -0
- package/src/agentSessions/SessionManager.ts +24 -25
- package/src/agentSessions/SessionRunIDVerifier.ts +179 -0
- package/src/agentSessions/index.ts +1 -0
- package/src/auth/actionAuthorization.ts +119 -0
- package/src/auth/index.ts +1 -0
- package/src/auth/scopeLimitedPrincipal.ts +50 -0
- package/src/config.ts +34 -1
- package/src/context.ts +25 -34
- package/src/generated/generated.ts +3262 -3273
- package/src/generic/CacheInvalidationResolver.ts +206 -15
- package/src/generic/FireAndForgetHeartbeat.ts +10 -4
- package/src/generic/PushStatusResolver.ts +79 -4
- package/src/generic/ResolverBase.ts +339 -31
- package/src/generic/RunViewResolver.ts +17 -0
- package/src/index.ts +65 -52
- package/src/multiTenancy/index.ts +24 -2
- package/src/postgresqlCompat.ts +21 -0
- package/src/postgresqlPoolSettings.ts +213 -0
- package/src/realtimeWidget/widgetGuestElevation.ts +2 -2
- package/src/resolvers/ActionResolver.ts +85 -22
- package/src/resolvers/AdhocQueryResolver.ts +110 -177
- package/src/resolvers/DatasetResolver.ts +70 -10
- package/src/resolvers/EntityRecordNameResolver.ts +24 -7
- package/src/resolvers/GetDataContextDataResolver.ts +139 -41
- package/src/resolvers/GetDataResolver.ts +14 -1
- package/src/resolvers/MCPResolver.ts +13 -15
- package/src/resolvers/RealtimeClientSessionResolver.ts +82 -50
- package/src/resolvers/RemoteBrowserActionResolver.ts +30 -26
- package/src/resolvers/RunAIAgentResolver.ts +30 -40
- package/src/resolvers/RunTemplateResolver.ts +13 -12
- package/src/resolvers/RunTestResolver.ts +90 -112
- package/src/resolvers/SyncRolesUsersResolver.ts +11 -2
- package/src/resolvers/VersionHistoryResolver.ts +10 -13
- package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
- package/src/resolvers/__tests__/ReportResolver.test.ts +2 -0
- package/src/resolvers/adhocTimeout.ts +12 -0
- package/src/rest/ViewOperationsHandler.ts +37 -18
- package/src/services/TaskOrchestrator.ts +17 -17
- package/dist/resolvers/adhoc-query-helpers.d.ts +0 -27
- package/dist/resolvers/adhoc-query-helpers.d.ts.map +0 -1
- package/dist/resolvers/adhoc-query-helpers.js +0 -42
- package/dist/resolvers/adhoc-query-helpers.js.map +0 -1
- package/src/__tests__/adhoc-query-helpers.test.ts +0 -50
- package/src/resolvers/adhoc-query-helpers.ts +0 -50
|
@@ -15,7 +15,6 @@ import { AppContext, UserPayload } from '../types.js';
|
|
|
15
15
|
import { LogError, LogStatus, UserInfo } from '@memberjunction/core';
|
|
16
16
|
import { TestEngine } from '@memberjunction/testing-engine';
|
|
17
17
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
18
|
-
import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
|
|
19
18
|
import { startLivenessPulse } from '../generic/FireAndForgetHeartbeat.js';
|
|
20
19
|
import { TestRunVariables, TestLogMessage, TestRunResult as EngineTestRunResult } from '@memberjunction/testing-engine-base';
|
|
21
20
|
|
|
@@ -257,7 +256,7 @@ export class RunTestResolver extends ResolverBase {
|
|
|
257
256
|
): void {
|
|
258
257
|
// Keep-alive pulse so a long-running test never trips the client idle timeout.
|
|
259
258
|
// The client captures the testRunId from progress events for reconciliation.
|
|
260
|
-
const pulse = startLivenessPulse({ pubSub, sessionId: userPayload.sessionId, resolver: 'RunTestResolver' });
|
|
259
|
+
const pulse = startLivenessPulse({ pubSub, sessionId: userPayload.sessionId, ownerUserId: userPayload.userRecord.ID, resolver: 'RunTestResolver' });
|
|
261
260
|
|
|
262
261
|
this.executeTest(testId, verbose, environment, tags, variables, pubSub, userPayload, user)
|
|
263
262
|
.catch((error: unknown) => {
|
|
@@ -353,7 +352,7 @@ export class RunTestResolver extends ResolverBase {
|
|
|
353
352
|
// (Suite reconciliation isn't wired: suite progress carries the per-test run id, not the
|
|
354
353
|
// Test Suite Run id, so the client has no handle to reconcile against — the pulse is the
|
|
355
354
|
// protection here.)
|
|
356
|
-
const pulse = startLivenessPulse({ pubSub, sessionId: userPayload.sessionId, resolver: 'RunTestResolver' });
|
|
355
|
+
const pulse = startLivenessPulse({ pubSub, sessionId: userPayload.sessionId, ownerUserId: userPayload.userRecord.ID, resolver: 'RunTestResolver' });
|
|
357
356
|
|
|
358
357
|
this.executeSuite(
|
|
359
358
|
suiteId, verbose, environment, parallel, tags, variables,
|
|
@@ -494,51 +493,42 @@ export class RunTestResolver extends ResolverBase {
|
|
|
494
493
|
// ===== PubSub Publishing =====
|
|
495
494
|
|
|
496
495
|
private publishProgress(pubSub: PubSubEngine, data: TestExecutionStreamMessage, userPayload: UserPayload) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}),
|
|
504
|
-
sessionId: userPayload.sessionId,
|
|
505
|
-
});
|
|
496
|
+
this.PublishStatusUpdate(pubSub, userPayload.sessionId, JSON.stringify({
|
|
497
|
+
resolver: 'RunTestResolver',
|
|
498
|
+
type: 'TestExecutionProgress',
|
|
499
|
+
status: 'ok',
|
|
500
|
+
data,
|
|
501
|
+
}), userPayload);
|
|
506
502
|
}
|
|
507
503
|
|
|
508
504
|
private publishComplete(pubSub: PubSubEngine, userPayload: UserPayload, result: EngineTestRunResult) {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}),
|
|
522
|
-
sessionId: userPayload.sessionId,
|
|
523
|
-
});
|
|
505
|
+
this.PublishStatusUpdate(pubSub, userPayload.sessionId, JSON.stringify({
|
|
506
|
+
resolver: 'RunTestResolver',
|
|
507
|
+
type: 'TestExecutionComplete',
|
|
508
|
+
status: 'ok',
|
|
509
|
+
data: {
|
|
510
|
+
sessionId: userPayload.sessionId,
|
|
511
|
+
testRunId: result.testRunId,
|
|
512
|
+
type: 'complete',
|
|
513
|
+
result: JSON.stringify(result),
|
|
514
|
+
timestamp: new Date()
|
|
515
|
+
},
|
|
516
|
+
}), userPayload);
|
|
524
517
|
}
|
|
525
518
|
|
|
526
519
|
private publishError(pubSub: PubSubEngine, userPayload: UserPayload, testId: string, errorMsg: string) {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
}),
|
|
540
|
-
sessionId: userPayload.sessionId,
|
|
541
|
-
});
|
|
520
|
+
this.PublishStatusUpdate(pubSub, userPayload.sessionId, JSON.stringify({
|
|
521
|
+
resolver: 'RunTestResolver',
|
|
522
|
+
type: 'TestExecutionError',
|
|
523
|
+
status: 'error',
|
|
524
|
+
data: {
|
|
525
|
+
sessionId: userPayload.sessionId,
|
|
526
|
+
testRunId: testId,
|
|
527
|
+
type: 'error',
|
|
528
|
+
errorMessage: errorMsg,
|
|
529
|
+
timestamp: new Date()
|
|
530
|
+
},
|
|
531
|
+
}), userPayload);
|
|
542
532
|
}
|
|
543
533
|
|
|
544
534
|
/**
|
|
@@ -551,24 +541,21 @@ export class RunTestResolver extends ResolverBase {
|
|
|
551
541
|
testId: string,
|
|
552
542
|
testRunResult: TestRunResult
|
|
553
543
|
) {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
}),
|
|
570
|
-
sessionId: userPayload.sessionId,
|
|
571
|
-
});
|
|
544
|
+
this.PublishStatusUpdate(pubSub, userPayload.sessionId, JSON.stringify({
|
|
545
|
+
resolver: 'RunTestResolver',
|
|
546
|
+
type: 'FireAndForgetComplete',
|
|
547
|
+
status: 'ok',
|
|
548
|
+
data: {
|
|
549
|
+
sessionId: userPayload.sessionId,
|
|
550
|
+
testId,
|
|
551
|
+
type: 'complete',
|
|
552
|
+
success: testRunResult.success,
|
|
553
|
+
errorMessage: testRunResult.errorMessage,
|
|
554
|
+
executionTimeMs: testRunResult.executionTimeMs,
|
|
555
|
+
result: testRunResult.result,
|
|
556
|
+
timestamp: new Date()
|
|
557
|
+
},
|
|
558
|
+
}), userPayload);
|
|
572
559
|
}
|
|
573
560
|
|
|
574
561
|
/**
|
|
@@ -580,23 +567,20 @@ export class RunTestResolver extends ResolverBase {
|
|
|
580
567
|
testId: string,
|
|
581
568
|
errorMessage: string
|
|
582
569
|
) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
}),
|
|
598
|
-
sessionId: userPayload.sessionId,
|
|
599
|
-
});
|
|
570
|
+
this.PublishStatusUpdate(pubSub, userPayload.sessionId, JSON.stringify({
|
|
571
|
+
resolver: 'RunTestResolver',
|
|
572
|
+
type: 'FireAndForgetComplete',
|
|
573
|
+
status: 'ok',
|
|
574
|
+
data: {
|
|
575
|
+
sessionId: userPayload.sessionId,
|
|
576
|
+
testId,
|
|
577
|
+
type: 'complete',
|
|
578
|
+
success: false,
|
|
579
|
+
errorMessage,
|
|
580
|
+
result: JSON.stringify({ success: false, errorMessage }),
|
|
581
|
+
timestamp: new Date()
|
|
582
|
+
},
|
|
583
|
+
}), userPayload);
|
|
600
584
|
}
|
|
601
585
|
|
|
602
586
|
/**
|
|
@@ -609,24 +593,21 @@ export class RunTestResolver extends ResolverBase {
|
|
|
609
593
|
suiteId: string,
|
|
610
594
|
suiteRunResult: TestSuiteRunResult
|
|
611
595
|
) {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
}),
|
|
628
|
-
sessionId: userPayload.sessionId,
|
|
629
|
-
});
|
|
596
|
+
this.PublishStatusUpdate(pubSub, userPayload.sessionId, JSON.stringify({
|
|
597
|
+
resolver: 'RunTestResolver',
|
|
598
|
+
type: 'FireAndForgetSuiteComplete',
|
|
599
|
+
status: 'ok',
|
|
600
|
+
data: {
|
|
601
|
+
sessionId: userPayload.sessionId,
|
|
602
|
+
suiteId,
|
|
603
|
+
type: 'complete',
|
|
604
|
+
success: suiteRunResult.success,
|
|
605
|
+
errorMessage: suiteRunResult.errorMessage,
|
|
606
|
+
executionTimeMs: suiteRunResult.executionTimeMs,
|
|
607
|
+
result: suiteRunResult.result,
|
|
608
|
+
timestamp: new Date()
|
|
609
|
+
},
|
|
610
|
+
}), userPayload);
|
|
630
611
|
}
|
|
631
612
|
|
|
632
613
|
/**
|
|
@@ -638,22 +619,19 @@ export class RunTestResolver extends ResolverBase {
|
|
|
638
619
|
suiteId: string,
|
|
639
620
|
errorMessage: string
|
|
640
621
|
) {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
}),
|
|
656
|
-
sessionId: userPayload.sessionId,
|
|
657
|
-
});
|
|
622
|
+
this.PublishStatusUpdate(pubSub, userPayload.sessionId, JSON.stringify({
|
|
623
|
+
resolver: 'RunTestResolver',
|
|
624
|
+
type: 'FireAndForgetSuiteComplete',
|
|
625
|
+
status: 'ok',
|
|
626
|
+
data: {
|
|
627
|
+
sessionId: userPayload.sessionId,
|
|
628
|
+
suiteId,
|
|
629
|
+
type: 'complete',
|
|
630
|
+
success: false,
|
|
631
|
+
errorMessage,
|
|
632
|
+
result: JSON.stringify({ success: false, errorMessage }),
|
|
633
|
+
timestamp: new Date()
|
|
634
|
+
},
|
|
635
|
+
}), userPayload);
|
|
658
636
|
}
|
|
659
637
|
}
|
|
@@ -385,8 +385,17 @@ export class SyncRolesAndUsersResolver {
|
|
|
385
385
|
if (await user.Delete()) {
|
|
386
386
|
return;
|
|
387
387
|
}
|
|
388
|
-
//
|
|
389
|
-
//
|
|
388
|
+
// Fallback when the hard delete does not happen. TWO distinct causes reach here, and this
|
|
389
|
+
// path deliberately treats them the same way:
|
|
390
|
+
// 1. FK constraints — the user has dependent records that block a hard delete.
|
|
391
|
+
// 2. Authorization — `MJUserEntityServer` (issue #4260) refuses a delete outright for any
|
|
392
|
+
// caller whose `Type` is not 'Owner'. This mutation carries `@RequireSystemUser()` and
|
|
393
|
+
// `getSystemUser()` resolves the seeded Owner, so cause 2 does not arise on a default
|
|
394
|
+
// install; it does on a deployment whose system user is not an Owner.
|
|
395
|
+
// Soft-deleting is the right response to (1) and an acceptable one to (2) — but note the
|
|
396
|
+
// subsequent Save() is subject to the SAME guard, so under cause 2 it fails too and the
|
|
397
|
+
// error below is what the operator sees. Read `LatestResult` rather than assuming FK.
|
|
398
|
+
// A failure to soft-delete IS a real error and rolls back.
|
|
390
399
|
user.IsActive = false;
|
|
391
400
|
if (!await user.Save()) {
|
|
392
401
|
throw new Error(`Failed to delete or deactivate user '${user.Email}': ${user.LatestResult?.CompleteMessage ?? 'unknown error'}`);
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { Resolver, Mutation, Arg, Ctx, ObjectType, Field, Int, InputType, PubSub, PubSubEngine } from 'type-graphql';
|
|
2
|
-
import { AppContext } from '../types.js';
|
|
2
|
+
import { AppContext, UserPayload } from '../types.js';
|
|
3
3
|
import { LogError, LogStatus, CompositeKey, KeyValuePair } from '@memberjunction/core';
|
|
4
4
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
5
5
|
import { VersionHistoryEngine } from '@memberjunction/version-history';
|
|
6
6
|
import { CreateLabelParams, CreateLabelProgressUpdate, VersionLabelScope } from '@memberjunction/version-history';
|
|
7
7
|
import { KeyValuePairInput } from '../generic/KeyValuePairInput.js';
|
|
8
|
-
import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
|
|
9
8
|
|
|
10
9
|
// =========================================================================
|
|
11
10
|
// GraphQL types
|
|
@@ -115,7 +114,7 @@ export class VersionHistoryResolver extends ResolverBase {
|
|
|
115
114
|
|
|
116
115
|
// Build progress callback that publishes to PubSub
|
|
117
116
|
const resolvedSessionId = sessionId ?? context.userPayload.sessionId;
|
|
118
|
-
const onProgress = this.buildProgressCallback(pubSub, resolvedSessionId);
|
|
117
|
+
const onProgress = this.buildProgressCallback(pubSub, resolvedSessionId, context.userPayload);
|
|
119
118
|
|
|
120
119
|
const params: CreateLabelParams = {
|
|
121
120
|
Name: input.Name,
|
|
@@ -160,18 +159,16 @@ export class VersionHistoryResolver extends ResolverBase {
|
|
|
160
159
|
*/
|
|
161
160
|
private buildProgressCallback(
|
|
162
161
|
pubSub: PubSubEngine,
|
|
163
|
-
sessionId: string
|
|
162
|
+
sessionId: string,
|
|
163
|
+
userPayload: UserPayload
|
|
164
164
|
): (progress: CreateLabelProgressUpdate) => void {
|
|
165
165
|
return (progress: CreateLabelProgressUpdate) => {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}),
|
|
173
|
-
sessionId,
|
|
174
|
-
});
|
|
166
|
+
this.PublishStatusUpdate(pubSub, sessionId, JSON.stringify({
|
|
167
|
+
resolver: 'VersionHistoryResolver',
|
|
168
|
+
type: 'CreateLabelProgress',
|
|
169
|
+
status: 'ok',
|
|
170
|
+
data: progress,
|
|
171
|
+
}), userPayload);
|
|
175
172
|
};
|
|
176
173
|
}
|
|
177
174
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// type-graphql decorators on the resolver class need the reflect-metadata polyfill.
|
|
2
|
+
import 'reflect-metadata';
|
|
3
|
+
|
|
4
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
5
|
+
import type { MockInstance } from 'vitest';
|
|
6
|
+
import { SQLExpressionValidator } from '@memberjunction/global';
|
|
7
|
+
|
|
8
|
+
const { executedSQL } = vi.hoisted(() => ({ executedSQL: [] as string[] }));
|
|
9
|
+
|
|
10
|
+
// GetData's server bootstrap, provider, auth and driver imports are replaced so the resolver runs
|
|
11
|
+
// offline. The fake driver records every text it is asked to run.
|
|
12
|
+
vi.mock('../../index.js', () => ({ getDbType: () => 'sqlserver' }));
|
|
13
|
+
vi.mock('@memberjunction/generic-database-provider', () => ({
|
|
14
|
+
QueryCompositionEngine: class {
|
|
15
|
+
HasCompositionTokens(): boolean {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
}));
|
|
20
|
+
vi.mock('../../util.js', () => ({ GetReadOnlyDataSource: () => ({}), GetReadOnlyProvider: () => null }));
|
|
21
|
+
vi.mock('../../auth/index.js', () => ({ getSystemUser: async () => ({}) }));
|
|
22
|
+
vi.mock('mssql', () => ({
|
|
23
|
+
default: {
|
|
24
|
+
Request: class {
|
|
25
|
+
async query(text: string): Promise<{ recordset: Array<Record<string, unknown>> }> {
|
|
26
|
+
executedSQL.push(text);
|
|
27
|
+
return { recordset: [] };
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
import { GetDataResolver, GetDataInputType, GetDataOutputType, registerAccessToken } from '../GetDataResolver.js';
|
|
34
|
+
import type { AppContext } from '../../types.js';
|
|
35
|
+
|
|
36
|
+
const CONTEXT = { dataSources: [] } as unknown as AppContext;
|
|
37
|
+
|
|
38
|
+
let tokenSeq = 0;
|
|
39
|
+
|
|
40
|
+
/** Calls GetData with a fresh valid token. */
|
|
41
|
+
async function runGetData(queries: string[]): Promise<GetDataOutputType> {
|
|
42
|
+
const token = `getdata-validation-${Date.now()}-${++tokenSeq}`;
|
|
43
|
+
registerAccessToken(token, 60_000);
|
|
44
|
+
const input = Object.assign(new GetDataInputType(), { Token: token, Queries: queries });
|
|
45
|
+
return new GetDataResolver().GetData(input, CONTEXT);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The text of every call to the validator's `validate` method. */
|
|
49
|
+
function validatedTexts(spy: MockInstance<SQLExpressionValidator['validate']>): string[] {
|
|
50
|
+
return spy.mock.calls.map(([text]) => text);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe('GetData runs exactly the SQL it validated', () => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
executedSQL.length = 0;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('validates and runs the same text when the query contains backslash sequences', async () => {
|
|
59
|
+
const query = String.raw`SELECT Name FROM __mj.vwFiles WHERE Path = 'C:\new\temp'`;
|
|
60
|
+
const validateSpy = vi.spyOn(SQLExpressionValidator.Instance, 'validate');
|
|
61
|
+
|
|
62
|
+
const output = await runGetData([query]);
|
|
63
|
+
|
|
64
|
+
expect(output.ErrorMessages).toEqual([null]);
|
|
65
|
+
expect(executedSQL).toEqual(validatedTexts(validateSpy));
|
|
66
|
+
expect(executedSQL).toEqual([query]);
|
|
67
|
+
expect(validateSpy).toHaveBeenCalledWith(query, { context: 'full_query', Dialect: 'sqlserver' });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('runs the trimmed text that the validator reads', async () => {
|
|
71
|
+
const validateSpy = vi.spyOn(SQLExpressionValidator.Instance, 'validate');
|
|
72
|
+
|
|
73
|
+
const output = await runGetData(['\n SELECT 1 AS One \n']);
|
|
74
|
+
|
|
75
|
+
expect(output.ErrorMessages).toEqual([null]);
|
|
76
|
+
expect(executedSQL).toEqual(['SELECT 1 AS One']);
|
|
77
|
+
expect(validatedTexts(validateSpy)).toEqual(executedSQL);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('refuses a query whose text, as written, does not open with SELECT or WITH', async () => {
|
|
81
|
+
const output = await runGetData(["-- note\\n SELECT 1\nPRINT 'hello'"]);
|
|
82
|
+
|
|
83
|
+
expect(executedSQL).toEqual([]);
|
|
84
|
+
expect(output.Success).toBe(false);
|
|
85
|
+
expect(output.ErrorMessages[0]).toMatch(/must start with SELECT or WITH/);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('does not run a refused query and still runs the others', async () => {
|
|
89
|
+
const output = await runGetData(['SELECT 1; SELECT 2', 'SELECT 1 AS One']);
|
|
90
|
+
|
|
91
|
+
expect(executedSQL).toEqual(['SELECT 1 AS One']);
|
|
92
|
+
expect(output.ErrorMessages[0]).toMatch(/Semicolons are not allowed/);
|
|
93
|
+
expect(output.ErrorMessages[1]).toBeNull();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -133,6 +133,8 @@ vi.mock('@memberjunction/core', async () => {
|
|
|
133
133
|
});
|
|
134
134
|
|
|
135
135
|
vi.mock('@memberjunction/core-entities', () => ({}));
|
|
136
|
+
// ResolverBase imports the client clause screen from generic-database-provider; these tests do not reach it.
|
|
137
|
+
vi.mock('@memberjunction/generic-database-provider', () => ({}));
|
|
136
138
|
|
|
137
139
|
// ─── Import after mocks ──────────────────────────────────────────────────
|
|
138
140
|
import { ReportResolverExtended } from '../ReportResolver';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The timeout an ad-hoc query gets when the caller names none. */
|
|
2
|
+
const DEFAULT_TIMEOUT_SECONDS = 30;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The timeout an ad-hoc query runs with: what the caller asked for (or the default), but never
|
|
6
|
+
* longer than the server's `requestTimeout`, so a caller cannot hold a read-only connection longer
|
|
7
|
+
* than any other request may. `requestTimeoutMs` of 0 or less means the server sets no limit.
|
|
8
|
+
*/
|
|
9
|
+
export function ClampAdhocTimeoutSeconds(requested: number | undefined, requestTimeoutMs: number): number {
|
|
10
|
+
const seconds = requested != null && requested > 0 ? requested : DEFAULT_TIMEOUT_SECONDS;
|
|
11
|
+
return requestTimeoutMs > 0 ? Math.min(seconds, Math.max(1, Math.floor(requestTimeoutMs / 1000))) : seconds;
|
|
12
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
LogError, Metadata, RunView, RunViewParams,
|
|
3
|
-
RunViewResult, UserInfo
|
|
2
|
+
BaseEntity, LogError, Metadata, RunView, RunViewParams,
|
|
3
|
+
RunViewResult, UserInfo, type IRunViewProvider
|
|
4
4
|
} from '@memberjunction/core';
|
|
5
|
+
import { ClientClauseScreen } from '@memberjunction/generic-database-provider';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* View Operations Implementation for REST endpoints
|
|
@@ -32,11 +33,11 @@ export class ViewOperationsHandler {
|
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
// Sanitize and validate parameters
|
|
36
|
-
|
|
36
|
+
// Sanitize and validate parameters against the provider that runs the view
|
|
37
|
+
const runView = new RunView();
|
|
38
|
+
this.sanitizeRunViewParams(params, md, runView.ProviderToUse, user);
|
|
37
39
|
|
|
38
40
|
// Execute the view
|
|
39
|
-
const runView = new RunView();
|
|
40
41
|
const result = await runView.RunView(params, user);
|
|
41
42
|
|
|
42
43
|
return { success: true, result };
|
|
@@ -51,8 +52,9 @@ export class ViewOperationsHandler {
|
|
|
51
52
|
*/
|
|
52
53
|
static async runViews(paramsArray: RunViewParams[], user: UserInfo): Promise<{ success: boolean, results?: RunViewResult[], error?: string }> {
|
|
53
54
|
try {
|
|
54
|
-
// Validate and sanitize each set of parameters
|
|
55
|
+
// Validate and sanitize each set of parameters against the provider that runs the views
|
|
55
56
|
const md = new Metadata(); // global-provider-ok: REST endpoint — no per-request provider injection in REST middleware yet
|
|
57
|
+
const runView = new RunView();
|
|
56
58
|
for (const params of paramsArray) {
|
|
57
59
|
// Validate entity exists
|
|
58
60
|
const entity = md.Entities.find(e => e.Name === params.EntityName);
|
|
@@ -73,11 +75,10 @@ export class ViewOperationsHandler {
|
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
// Sanitize parameters
|
|
76
|
-
this.sanitizeRunViewParams(params);
|
|
78
|
+
this.sanitizeRunViewParams(params, md, runView.ProviderToUse, user);
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
// Execute the views
|
|
80
|
-
const runView = new RunView();
|
|
81
82
|
const results = await runView.RunViews(paramsArray, user);
|
|
82
83
|
|
|
83
84
|
return { success: true, results };
|
|
@@ -104,11 +105,11 @@ export class ViewOperationsHandler {
|
|
|
104
105
|
throw new Error(`User ${user.Name} does not have permission to read ${params.EntityName} records`);
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
// Sanitize and validate parameters
|
|
108
|
-
|
|
108
|
+
// Sanitize and validate parameters against the provider that runs the view
|
|
109
|
+
const runView = new RunView();
|
|
110
|
+
this.sanitizeRunViewParams(params, md, runView.ProviderToUse, user);
|
|
109
111
|
|
|
110
112
|
// Execute the view
|
|
111
|
-
const runView = new RunView();
|
|
112
113
|
return await runView.RunView(params, user);
|
|
113
114
|
} catch (error) {
|
|
114
115
|
LogError(error);
|
|
@@ -173,35 +174,53 @@ export class ViewOperationsHandler {
|
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
/**
|
|
176
|
-
* Sanitize and validate RunViewParams
|
|
177
|
+
* Sanitize and validate RunViewParams. Client filter and sort text passes the same clause
|
|
178
|
+
* screen as the GraphQL RunView resolvers, parsed in the dialect of `provider`, the provider
|
|
179
|
+
* that runs the view.
|
|
177
180
|
*/
|
|
178
|
-
private static sanitizeRunViewParams(params: RunViewParams): void {
|
|
181
|
+
private static sanitizeRunViewParams(params: RunViewParams, md: Metadata, provider: IRunViewProvider, user: UserInfo): void {
|
|
179
182
|
// Ensure EntityName is provided
|
|
180
183
|
if (!params.EntityName) {
|
|
181
184
|
throw new Error('EntityName is required');
|
|
182
185
|
}
|
|
186
|
+
|
|
187
|
+
// A loaded view entity cannot arrive over JSON; the provider would trust a plain object's
|
|
188
|
+
// fields (its ID is spliced into view SQL). REST callers name a saved view by ViewID or ViewName.
|
|
189
|
+
if (params.ViewEntity != null && !(params.ViewEntity instanceof BaseEntity)) {
|
|
190
|
+
throw new Error('ViewEntity is not accepted over REST; use ViewID or ViewName');
|
|
191
|
+
}
|
|
183
192
|
|
|
184
193
|
// Convert string arrays if they came in as comma-separated strings
|
|
185
194
|
if (params.Fields && typeof params.Fields === 'string') {
|
|
186
195
|
params.Fields = (params.Fields as string).split(',');
|
|
187
196
|
}
|
|
188
197
|
|
|
189
|
-
// Sanitize numeric values
|
|
198
|
+
// Sanitize numeric values: base-10, integral, and clamped to a sane range so a
|
|
199
|
+
// negative/garbage value can never reach the SQL layer.
|
|
190
200
|
if (params.MaxRows !== undefined) {
|
|
191
|
-
|
|
192
|
-
? parseInt(params.MaxRows as string)
|
|
201
|
+
const parsed = typeof params.MaxRows === 'string'
|
|
202
|
+
? parseInt(params.MaxRows as string, 10)
|
|
193
203
|
: params.MaxRows;
|
|
204
|
+
params.MaxRows = Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed as number)) : undefined;
|
|
194
205
|
}
|
|
195
206
|
|
|
196
207
|
if (params.StartRow !== undefined) {
|
|
197
|
-
|
|
198
|
-
? parseInt(params.StartRow as string)
|
|
208
|
+
const parsed = typeof params.StartRow === 'string'
|
|
209
|
+
? parseInt(params.StartRow as string, 10)
|
|
199
210
|
: params.StartRow;
|
|
211
|
+
params.StartRow = Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed as number)) : undefined;
|
|
200
212
|
}
|
|
201
213
|
|
|
202
214
|
// Default ResultType if not provided
|
|
203
215
|
if (!params.ResultType) {
|
|
204
216
|
params.ResultType = 'simple';
|
|
205
217
|
}
|
|
218
|
+
|
|
219
|
+
ClientClauseScreen.ScreenViewClauses(
|
|
220
|
+
{ ExtraFilter: params.ExtraFilter, OrderBy: params.OrderBy, OverrideExcludeFilter: params.OverrideExcludeFilter },
|
|
221
|
+
md.Entities,
|
|
222
|
+
ClientClauseScreen.DialectFor(provider),
|
|
223
|
+
user,
|
|
224
|
+
);
|
|
206
225
|
}
|
|
207
226
|
}
|
|
@@ -4,7 +4,7 @@ import { AgentRunner } from '@memberjunction/ai-agents';
|
|
|
4
4
|
import { ChatMessageRole } from '@memberjunction/ai';
|
|
5
5
|
import { PubSubEngine } from 'type-graphql';
|
|
6
6
|
import { UserPayload } from '../types.js';
|
|
7
|
-
import {
|
|
7
|
+
import { publishStatusUpdate } from '../generic/PushStatusResolver.js';
|
|
8
8
|
import { MJAIAgentEntityExtended } from '@memberjunction/ai-core-plus';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -226,7 +226,10 @@ export class TaskOrchestrator {
|
|
|
226
226
|
return;
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
LogStatus(`📡 Publishing task progress: ${taskName} - ${message} (${percentComplete}%) to session ${this.userPayload.sessionId}`);
|
|
230
|
+
publishStatusUpdate(this.pubSub, {
|
|
231
|
+
sessionId: this.userPayload.sessionId,
|
|
232
|
+
ownerUserId: this.userPayload.userRecord.ID,
|
|
230
233
|
message: JSON.stringify({
|
|
231
234
|
resolver: 'TaskOrchestrator',
|
|
232
235
|
type: 'TaskProgress',
|
|
@@ -238,12 +241,8 @@ export class TaskOrchestrator {
|
|
|
238
241
|
timestamp: new Date(),
|
|
239
242
|
conversationDetailId: this.conversationDetailId
|
|
240
243
|
}
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
LogStatus(`📡 Publishing task progress: ${taskName} - ${message} (${percentComplete}%) to session ${this.userPayload.sessionId}`);
|
|
246
|
-
this.pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, payload);
|
|
244
|
+
})
|
|
245
|
+
});
|
|
247
246
|
|
|
248
247
|
LogStatus(`[Task: ${taskName}] ${message} (${percentComplete}%)`);
|
|
249
248
|
}
|
|
@@ -257,7 +256,10 @@ export class TaskOrchestrator {
|
|
|
257
256
|
return;
|
|
258
257
|
}
|
|
259
258
|
|
|
260
|
-
|
|
259
|
+
LogStatus(`📡 Publishing agent progress: ${taskName} → ${agentStep} to session ${this.userPayload.sessionId}`);
|
|
260
|
+
publishStatusUpdate(this.pubSub, {
|
|
261
|
+
sessionId: this.userPayload.sessionId,
|
|
262
|
+
ownerUserId: this.userPayload.userRecord.ID,
|
|
261
263
|
message: JSON.stringify({
|
|
262
264
|
resolver: 'TaskOrchestrator',
|
|
263
265
|
type: 'AgentProgress',
|
|
@@ -269,12 +271,8 @@ export class TaskOrchestrator {
|
|
|
269
271
|
timestamp: new Date(),
|
|
270
272
|
conversationDetailId: this.conversationDetailId
|
|
271
273
|
}
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
LogStatus(`📡 Publishing agent progress: ${taskName} → ${agentStep} to session ${this.userPayload.sessionId}`);
|
|
277
|
-
this.pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, payload);
|
|
274
|
+
})
|
|
275
|
+
});
|
|
278
276
|
|
|
279
277
|
LogStatus(`[Task: ${taskName}] → ${agentStep}: ${agentMessage}`);
|
|
280
278
|
}
|
|
@@ -851,8 +849,10 @@ export class TaskOrchestrator {
|
|
|
851
849
|
|
|
852
850
|
// Publish real-time event if pubSub available
|
|
853
851
|
if (this.pubSub && this.userPayload) {
|
|
854
|
-
|
|
855
|
-
|
|
852
|
+
// NOTE (B49): normalized from a malformed no-sessionId payload
|
|
853
|
+
publishStatusUpdate(this.pubSub, {
|
|
854
|
+
sessionId: this.userPayload.sessionId,
|
|
855
|
+
ownerUserId: this.userPayload.userRecord.ID,
|
|
856
856
|
message: JSON.stringify({
|
|
857
857
|
type: 'notification',
|
|
858
858
|
notificationId: notification.ID,
|