@happyvertical/smrt-reports 0.49.3 → 0.49.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 CHANGED
@@ -224,7 +224,17 @@ state; it redacts lock owners, raw errors, and tenant-fanout identifiers. Pass a
224
224
  that context; the result then distinguishes a current, stale, or read-triggered
225
225
  refresh. `previewReportRefresh()` and `applyReportRefresh()` require an
226
226
  application action host to authorize and audit the caller before a durable
227
- report-refresh job is queued. Only a registered `SmrtReportCollection` can
227
+ report-refresh job is queued. Manual refresh hosts must also return a versioned,
228
+ non-secret `executionAuthority` binding. Each worker process registers the named
229
+ host with `registerReportRefreshExecutionAuthorityHost()`; the task checks the
230
+ persisted tenant binding, resolves current membership and permission, and records
231
+ an allowed or denied execution audit immediately before refresh. Every enqueue
232
+ path also receives a `DurableJobPayloadSigner`, and every worker registers it
233
+ with `registerReportRefreshJobIntegritySigner()`. This authenticates the task
234
+ target, trigger, tenant scope, authority reference, and payload across storage;
235
+ the signing key is never persisted. Scheduled and on-change maintenance jobs
236
+ remain system-authority jobs and do not carry an initiating user, but their job
237
+ payloads use the same integrity boundary. Only a registered `SmrtReportCollection` can
228
238
  synchronously refresh a stale read, and only when its TTL policy is positive and
229
239
  not manual.
230
240
 
package/dist/index.d.ts CHANGED
@@ -13,7 +13,10 @@ import { DataQueryRequest } from '@happyvertical/smrt-types';
13
13
  import { DataQueryResult } from '@happyvertical/smrt-types';
14
14
  import { DataQuerySchema } from '@happyvertical/smrt-types';
15
15
  import { DataQuerySort } from '@happyvertical/smrt-types';
16
+ import { DurableJobPayloadIntegrity } from '@happyvertical/smrt-jobs';
17
+ import { DurableJobPayloadSigner } from '@happyvertical/smrt-jobs';
16
18
  import { EventEmitter } from 'node:events';
19
+ import { JobExecutionContext } from '@happyvertical/smrt-jobs';
17
20
  import { SmrtCollection } from '@happyvertical/smrt-core';
18
21
  import { SmrtJob } from '@happyvertical/smrt-jobs';
19
22
  import { SmrtListOptions } from '@happyvertical/smrt-core';
@@ -70,6 +73,7 @@ export declare function applyReportExport(descriptor: ReportAdapterDescriptor, r
70
73
  export declare function applyReportRefresh(reportCtor: ReportCtor, options: ApplyReportRefreshOptions): Promise<AppliedReportRefresh>;
71
74
 
72
75
  export declare interface ApplyReportRefreshOptions extends PreviewReportRefreshOptions {
76
+ jobIntegritySigner?: DurableJobPayloadSigner;
73
77
  queue?: string;
74
78
  priority?: number;
75
79
  timeout?: number;
@@ -147,6 +151,8 @@ export declare interface EnqueueReportRefreshOptions extends ReportRefreshJobArg
147
151
  timeout?: number;
148
152
  maxAttempts?: number;
149
153
  tenantJobCap?: number;
154
+ /** Server-only signer; register the same key in every worker process. */
155
+ integritySigner?: DurableJobPayloadSigner;
150
156
  }
151
157
 
152
158
  export declare function ensureReportRefreshSchedules(options: EnsureReportSchedulesOptions): Promise<void>;
@@ -232,8 +238,12 @@ export declare function queryReportMaterializedRows(reportCtor: new (...args: an
232
238
 
233
239
  export declare function refreshReport(reportCtor: ReportCtor_2, options?: ReportRefreshOptions): Promise<ReportRefreshResult>;
234
240
 
241
+ export declare function registerReportRefreshExecutionAuthorityHost(hostId: string, host: ReportRefreshExecutionAuthorityHost): () => void;
242
+
235
243
  export declare function registerReportRefreshInterceptor(options: ReportRefreshInterceptorOptions): () => boolean;
236
244
 
245
+ export declare function registerReportRefreshJobIntegritySigner(signer: DurableJobPayloadSigner): () => void;
246
+
237
247
  export declare function report(options: ReportOptions): <T extends abstract new (...args: any[]) => any>(ctor: T) => T;
238
248
 
239
249
  export declare const REPORT_LOCKS_TABLE = "_smrt_report_locks";
@@ -521,6 +531,15 @@ export declare interface ReportDrilldownQuery {
521
531
  inherits: Array<'principal' | 'tenant' | 'report-definition' | 'field-policy'>;
522
532
  }
523
533
 
534
+ export declare interface ReportExecutionPrincipalReference {
535
+ version: 1;
536
+ actorUserId: string;
537
+ tenantId: string | null;
538
+ onBehalfOfUserId?: string | null;
539
+ actsAsProfileId?: string | null;
540
+ agentClass?: string | null;
541
+ }
542
+
524
543
  export declare interface ReportExportActionContext {
525
544
  phase: 'preview' | 'apply';
526
545
  reportClassName: string;
@@ -824,8 +843,12 @@ export declare interface ReportRefreshActionDescriptor {
824
843
 
825
844
  /** Authority and audit stay with the application action host, not reports. */
826
845
  export declare interface ReportRefreshActionHost {
827
- authorize(context: ReportRefreshActionContext): Promise<void> | void;
828
- audit(context: ReportRefreshActionContext): Promise<void> | void;
846
+ authorize(context: ReportRefreshActionContext, authority?: ReportRefreshExecutionAuthority): Promise<void> | void;
847
+ audit(context: ReportRefreshActionContext, authority?: ReportRefreshExecutionAuthority): Promise<void> | void;
848
+ /** Capture only a non-secret identity reference for worker-time reauthorization. */
849
+ executionAuthority?(context: ReportRefreshActionContext): ReportRefreshExecutionAuthority | Promise<ReportRefreshExecutionAuthority>;
850
+ /** Server-only signer shared with report worker processes. */
851
+ jobIntegritySigner?(context: ReportRefreshActionContext): DurableJobPayloadSigner | Promise<DurableJobPayloadSigner>;
829
852
  }
830
853
 
831
854
  export declare interface ReportRefreshConfig {
@@ -860,6 +883,31 @@ export declare interface ReportRefreshDescriptor {
860
883
  action: ReportRefreshActionDescriptor;
861
884
  }
862
885
 
886
+ export declare interface ReportRefreshExecutionAuditEvent extends ReportRefreshExecutionAuthorityContext {
887
+ outcome: 'allowed' | 'denied';
888
+ principal: ReportExecutionPrincipalReference;
889
+ reason?: string;
890
+ }
891
+
892
+ export declare interface ReportRefreshExecutionAuthority {
893
+ version: 1;
894
+ hostId: string;
895
+ principal: ReportExecutionPrincipalReference;
896
+ }
897
+
898
+ export declare interface ReportRefreshExecutionAuthorityContext {
899
+ phase: 'execute';
900
+ reportClass: string;
901
+ mode: ReportRefreshMode;
902
+ trigger: ReportRefreshTrigger;
903
+ tenantId: string | null;
904
+ }
905
+
906
+ export declare interface ReportRefreshExecutionAuthorityHost {
907
+ authorize(principal: Readonly<ReportExecutionPrincipalReference>, context: Readonly<ReportRefreshExecutionAuthorityContext>): Promise<void> | void;
908
+ audit(event: Readonly<ReportRefreshExecutionAuditEvent>): Promise<void> | void;
909
+ }
910
+
863
911
  export declare interface ReportRefreshInterceptorOptions {
864
912
  db: DatabaseInterface;
865
913
  reports: ReportCtor_3[];
@@ -869,6 +917,7 @@ export declare interface ReportRefreshInterceptorOptions {
869
917
  timeout?: number;
870
918
  tenantJobCap?: number;
871
919
  name?: string;
920
+ integritySigner?: DurableJobPayloadSigner;
872
921
  }
873
922
 
874
923
  export declare interface ReportRefreshJobArgs {
@@ -881,6 +930,8 @@ export declare interface ReportRefreshJobArgs {
881
930
  adapterType?: SqlAdapterType;
882
931
  changedRows?: Record<string, unknown>[];
883
932
  _scheduleId?: string;
933
+ executionAuthority?: ReportRefreshExecutionAuthority;
934
+ integrity?: DurableJobPayloadIntegrity;
884
935
  }
885
936
 
886
937
  export declare interface ReportRefreshJobHandle {
@@ -1011,6 +1062,7 @@ export declare interface ReportScheduleRunnerConfig {
1011
1062
  id?: string;
1012
1063
  pollInterval?: number;
1013
1064
  batchSize?: number;
1065
+ integritySigner?: DurableJobPayloadSigner;
1014
1066
  }
1015
1067
 
1016
1068
  export declare interface ReportScheduleRunnerEvents {
@@ -1051,6 +1103,11 @@ declare interface SavedViewInput {
1051
1103
 
1052
1104
  export declare function scopeKeyForTenant(tenantId: string | null | undefined): string;
1053
1105
 
1106
+ /** Worker target whose authority requirement cannot be downgraded by job args. */
1107
+ export declare class SmrtPrincipalReportRefreshTask extends SmrtReportRefreshTask {
1108
+ run(args?: ReportRefreshJobArgs, context?: JobExecutionContext): Promise<unknown>;
1109
+ }
1110
+
1054
1111
  export declare class SmrtReport extends SmrtObject {
1055
1112
  static readonly _isReportBase: true;
1056
1113
  refreshedAt: Date | null;
@@ -1108,7 +1165,7 @@ export declare class SmrtReportRefreshTask extends SmrtObject {
1108
1165
  mode: ReportRefreshMode;
1109
1166
  trigger: ReportRefreshTrigger;
1110
1167
  args: ReportRefreshJobArgs;
1111
- run(args?: ReportRefreshJobArgs): Promise<unknown>;
1168
+ run(args?: ReportRefreshJobArgs, context?: JobExecutionContext): Promise<unknown>;
1112
1169
  }
1113
1170
 
1114
1171
  export declare class SmrtReportRun extends SmrtObject {
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { aggregate, avg, count, day, getRuntimeReportOptions, groupBy, hour, max
2
2
  import { buildReportDefinition, compileReportDefinition, compileReportSpec, getReportGroupingColumns } from "./compiler.js";
3
3
  import { REPORT_LOCKS_TABLE, REPORT_REFRESH_TASKS_TABLE, REPORT_RUNS_TABLE, REPORT_RUNTIME_TABLES, REPORT_SCHEDULER_TABLES, REPORT_SCHEDULES_TABLE, REPORT_WATERMARKS_TABLE, SmrtReportLock, SmrtReportLockCollection, SmrtReportRun, SmrtReportRunCollection, SmrtReportSchedule, SmrtReportScheduleCollection, SmrtReportWatermark, SmrtReportWatermarkCollection, assertReportTablesReady, scopeKeyForTenant } from "./state.js";
4
4
  import { refreshReport, reportRowIdentity } from "./refresh.js";
5
- import { ReportScheduleRunner, SmrtReportRefreshTask, enqueueReportRefresh, ensureReportRefreshSchedules, registerReportRefreshInterceptor } from "./scheduler.js";
5
+ import { ReportScheduleRunner, SmrtPrincipalReportRefreshTask, SmrtReportRefreshTask, enqueueReportRefresh, ensureReportRefreshSchedules, registerReportRefreshExecutionAuthorityHost, registerReportRefreshInterceptor, registerReportRefreshJobIntegritySigner } from "./scheduler.js";
6
6
  import { bucketExpr, buildAggregate } from "./aggregate.js";
7
7
  import { DataQueryValidationError, ObjectRegistry, SmrtCollection, SmrtObject, createDataQueryFingerprint, normalizeDataQueryRequest, normalizeDataQueryResult } from "@happyvertical/smrt-core";
8
8
  import { toSnakeCase } from "@happyvertical/smrt-core/utils";
@@ -159,8 +159,12 @@ async function previewReportRefresh(reportCtor, options) {
159
159
  async function applyReportRefresh(reportCtor, options) {
160
160
  const mode = await actionMode(reportCtor, options.mode);
161
161
  const action = actionContext$1(reportCtor, "apply", mode, options.refreshAction);
162
- await options.host.authorize(action);
163
- await options.host.audit(action);
162
+ const executionAuthority = await options.host.executionAuthority?.(action);
163
+ if (!executionAuthority) throw new Error("Manual report refresh requires execution-time authority");
164
+ await options.host.authorize(action, executionAuthority);
165
+ await options.host.audit(action, executionAuthority);
166
+ const integritySigner = options.jobIntegritySigner ?? await options.host.jobIntegritySigner?.(action);
167
+ if (!integritySigner) throw new Error("Report refresh queue requires a durable job integrity signer");
164
168
  const tenantId = lifecycleTenantId(reportCtor);
165
169
  const enqueue = () => enqueueReportRefresh({
166
170
  db: options.db,
@@ -168,11 +172,13 @@ async function applyReportRefresh(reportCtor, options) {
168
172
  mode,
169
173
  trigger: "manual",
170
174
  tenantId,
175
+ executionAuthority,
171
176
  queue: options.queue,
172
177
  priority: options.priority,
173
178
  timeout: options.timeout,
174
179
  maxAttempts: options.maxAttempts,
175
- tenantJobCap: options.tenantJobCap
180
+ tenantJobCap: options.tenantJobCap,
181
+ integritySigner
176
182
  });
177
183
  return {
178
184
  phase: "apply",
@@ -1509,6 +1515,6 @@ function validateReportExportArtifact(descriptor, artifact, now = /* @__PURE__ *
1509
1515
  };
1510
1516
  }
1511
1517
  //#endregion
1512
- export { REPORT_LOCKS_TABLE, REPORT_REFRESH_TASKS_TABLE, REPORT_RUNS_TABLE, REPORT_RUNTIME_TABLES, REPORT_SCHEDULER_TABLES, REPORT_SCHEDULES_TABLE, REPORT_WATERMARKS_TABLE, ReportScheduleRunner, ReportSurfaceValidationError, SmrtReport, SmrtReportCollection, SmrtReportLock, SmrtReportLockCollection, SmrtReportRefreshTask, SmrtReportRun, SmrtReportRunCollection, SmrtReportSchedule, SmrtReportScheduleCollection, SmrtReportWatermark, SmrtReportWatermarkCollection, aggregate, applyReportExport, applyReportRefresh, assertReportTablesReady, avg, bucketExpr, buildAggregate, buildReportAdapterDescriptor, buildReportDefinition, buildReportDrilldownQuery, compileReportDefinition, compileReportSpec, count, createReportExportPageRequest, createReportExportRequest, createReportExportSnapshot, day, enqueueReportRefresh, ensureReportRefreshSchedules, getReportGroupingColumns, getReportLifecycle, getRuntimeReportOptions, groupBy, hour, max, migrateReportSavedView, min, minute, month, normalizeReportSavedView, previewReportExport, previewReportRefresh, quarter, queryReportMaterializedRows, refreshReport, registerReportRefreshInterceptor, report, reportDefinitionFingerprint, reportMaterializedRowKey, reportRefreshOutcome, reportRowIdentity, restoreReportSavedView, scopeKeyForTenant, splitReportFilterScopes, sum, validateReportExportArtifact, validateReportExportExecution, validateReportExportRequest, verifyReportExportSnapshot, week, year };
1518
+ export { REPORT_LOCKS_TABLE, REPORT_REFRESH_TASKS_TABLE, REPORT_RUNS_TABLE, REPORT_RUNTIME_TABLES, REPORT_SCHEDULER_TABLES, REPORT_SCHEDULES_TABLE, REPORT_WATERMARKS_TABLE, ReportScheduleRunner, ReportSurfaceValidationError, SmrtPrincipalReportRefreshTask, SmrtReport, SmrtReportCollection, SmrtReportLock, SmrtReportLockCollection, SmrtReportRefreshTask, SmrtReportRun, SmrtReportRunCollection, SmrtReportSchedule, SmrtReportScheduleCollection, SmrtReportWatermark, SmrtReportWatermarkCollection, aggregate, applyReportExport, applyReportRefresh, assertReportTablesReady, avg, bucketExpr, buildAggregate, buildReportAdapterDescriptor, buildReportDefinition, buildReportDrilldownQuery, compileReportDefinition, compileReportSpec, count, createReportExportPageRequest, createReportExportRequest, createReportExportSnapshot, day, enqueueReportRefresh, ensureReportRefreshSchedules, getReportGroupingColumns, getReportLifecycle, getRuntimeReportOptions, groupBy, hour, max, migrateReportSavedView, min, minute, month, normalizeReportSavedView, previewReportExport, previewReportRefresh, quarter, queryReportMaterializedRows, refreshReport, registerReportRefreshExecutionAuthorityHost, registerReportRefreshInterceptor, registerReportRefreshJobIntegritySigner, report, reportDefinitionFingerprint, reportMaterializedRowKey, reportRefreshOutcome, reportRowIdentity, restoreReportSavedView, scopeKeyForTenant, splitReportFilterScopes, sum, validateReportExportArtifact, validateReportExportExecution, validateReportExportRequest, verifyReportExportSnapshot, week, year };
1513
1519
 
1514
1520
  //# sourceMappingURL=index.js.map