@adcp/sdk 15.6.0 → 15.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/reporting/ledger/handler.js +20 -8
- package/dist/lib/reporting/ledger/handler.mjs +20 -8
- package/dist/lib/reporting/ledger/postgres.d.mts +11 -0
- package/dist/lib/reporting/ledger/postgres.d.ts +11 -0
- package/dist/lib/reporting/ledger/postgres.js +12 -0
- package/dist/lib/reporting/ledger/postgres.mjs +12 -0
- package/dist/lib/reporting/service/index.d.mts +36 -21
- package/dist/lib/reporting/service/index.d.ts +36 -21
- package/dist/lib/reporting/service/index.js +75 -65
- package/dist/lib/reporting/service/index.mjs +79 -64
- package/dist/lib/reporting/service/maintenance.d.mts +91 -0
- package/dist/lib/reporting/service/maintenance.d.ts +91 -0
- package/dist/lib/reporting/service/maintenance.js +140 -0
- package/dist/lib/reporting/service/maintenance.mjs +114 -0
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/docs/TYPE-SUMMARY.md +1 -1
- package/docs/guides/REPORTING-LEDGER.md +89 -0
- package/docs/guides/REPORTING-OPERATIONS.md +6 -0
- package/docs/llms.txt +1 -1
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/package.json +1 -1
|
@@ -30,6 +30,10 @@ import {
|
|
|
30
30
|
createPostgresReportingWebhookActivityV1,
|
|
31
31
|
projectListAccountsReportingWebhookActivityV1
|
|
32
32
|
} from "../webhook-activity.mjs";
|
|
33
|
+
import {
|
|
34
|
+
assertReportingLedgerMaintenanceOptions,
|
|
35
|
+
runReportingLedgerMaintenance
|
|
36
|
+
} from "./maintenance.mjs";
|
|
33
37
|
import { assertReportingCalendarDaySource, reportingCalendarDayOrigin } from "../ledger/schedule.mjs";
|
|
34
38
|
import {
|
|
35
39
|
ReportingCoverageConstituentIdentityV1Schema,
|
|
@@ -45,6 +49,7 @@ import {
|
|
|
45
49
|
} from "../source/index.mjs";
|
|
46
50
|
const ADAPTER_SCOPE_KEY = "_adcp_reporting_adapter";
|
|
47
51
|
const PRODUCTION_MANAGED_COMPOSITION = /* @__PURE__ */ Symbol("production-managed-reporting-composition");
|
|
52
|
+
const DEFAULT_MAINTENANCE_INTERVAL_MILLISECONDS = 3e5;
|
|
48
53
|
function createReliableReportingService(options) {
|
|
49
54
|
const allowManagedDelivery = options[PRODUCTION_MANAGED_COMPOSITION] === true;
|
|
50
55
|
const adapterEntries = Object.entries(options.adapters);
|
|
@@ -106,6 +111,42 @@ function createReliableReportingService(options) {
|
|
|
106
111
|
if (hasConsumerStatus) assertConsumerStatusStore(options.store);
|
|
107
112
|
const retentionDays = options.statusRetentionDays;
|
|
108
113
|
positiveInteger(retentionDays, "statusRetentionDays");
|
|
114
|
+
const maintenance = options.maintenance;
|
|
115
|
+
let maintenanceStore;
|
|
116
|
+
let maintenanceIntervalMilliseconds = DEFAULT_MAINTENANCE_INTERVAL_MILLISECONDS;
|
|
117
|
+
if (maintenance !== void 0) {
|
|
118
|
+
if (maintenance === null || typeof maintenance !== "object") {
|
|
119
|
+
throw new TypeError("maintenance must be an options object");
|
|
120
|
+
}
|
|
121
|
+
const candidate = options.store;
|
|
122
|
+
for (const method of ["retireExpiredPeriods", "pruneChanges", "sweepRowWriteIntents"]) {
|
|
123
|
+
if (typeof candidate[method] !== "function") {
|
|
124
|
+
throw new TypeError(
|
|
125
|
+
`maintenance requires a PostgresReportingLedgerStore (or a store with ${method}); the configured store cannot run ledger maintenance`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
maintenanceStore = candidate;
|
|
130
|
+
if (maintenance.changeFeed && maintenanceStore.changeFeedEnabled === false) {
|
|
131
|
+
throw new TypeError(
|
|
132
|
+
"maintenance.changeFeed requires the store to be built with changeFeed: true; otherwise pruning silently does nothing"
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
if (maintenance.intervalMilliseconds !== void 0) {
|
|
136
|
+
positiveInteger(maintenance.intervalMilliseconds, "maintenance.intervalMilliseconds");
|
|
137
|
+
maintenanceIntervalMilliseconds = maintenance.intervalMilliseconds;
|
|
138
|
+
}
|
|
139
|
+
assertReportingLedgerMaintenanceOptions(maintenance, retentionDays);
|
|
140
|
+
}
|
|
141
|
+
const runLedgerMaintenance = (signal, report) => runReportingLedgerMaintenance({
|
|
142
|
+
store: maintenanceStore,
|
|
143
|
+
...typeof maintenanceStore.sweepExpiredState === "function" ? { sweepSnapshots: (limit) => maintenanceStore.sweepExpiredState(limit) } : {},
|
|
144
|
+
statusRetentionDays: retentionDays,
|
|
145
|
+
retention: maintenance.retention,
|
|
146
|
+
changeFeed: maintenance.changeFeed,
|
|
147
|
+
signal,
|
|
148
|
+
...report ? { report } : {}
|
|
149
|
+
});
|
|
109
150
|
const capabilities = deepFreeze({
|
|
110
151
|
supported: true,
|
|
111
152
|
reliable_reporting_version: "1.0",
|
|
@@ -278,12 +319,26 @@ function createReliableReportingService(options) {
|
|
|
278
319
|
});
|
|
279
320
|
return { planned: planned.length, ...result };
|
|
280
321
|
},
|
|
322
|
+
async runMaintenance(maintenanceOptions) {
|
|
323
|
+
if (maintenance === void 0) {
|
|
324
|
+
throw new TypeError("runMaintenance requires the maintenance option");
|
|
325
|
+
}
|
|
326
|
+
maintenanceOptions?.signal?.throwIfAborted();
|
|
327
|
+
return runLedgerMaintenance(maintenanceOptions?.signal);
|
|
328
|
+
},
|
|
281
329
|
start(scheduler) {
|
|
282
330
|
if (schedulerPromise) throw new Error("Reliable reporting scheduler is already running");
|
|
283
331
|
positiveInteger(scheduler.intervalMilliseconds, "intervalMilliseconds");
|
|
284
332
|
schedulerAbort = new AbortController();
|
|
285
333
|
const signal = schedulerAbort.signal;
|
|
286
|
-
|
|
334
|
+
const scheduledMaintenance = maintenance === void 0 ? void 0 : {
|
|
335
|
+
intervalMilliseconds: maintenanceIntervalMilliseconds,
|
|
336
|
+
run: () => runLedgerMaintenance(
|
|
337
|
+
signal,
|
|
338
|
+
(error) => reportSchedulerError(scheduler, signal, error, { phase: "maintenance" })
|
|
339
|
+
)
|
|
340
|
+
};
|
|
341
|
+
schedulerPromise = schedulerLoop(service, scheduler, signal, options.store, scheduledMaintenance).finally(() => {
|
|
287
342
|
schedulerPromise = void 0;
|
|
288
343
|
schedulerAbort = void 0;
|
|
289
344
|
});
|
|
@@ -515,53 +570,15 @@ async function createPostgresReliableReportingProductionService(options) {
|
|
|
515
570
|
for (const result of pruneResults) {
|
|
516
571
|
if (result.status === "rejected" && !signal.aborted) await reportAuxiliaryError(scheduler, result.reason);
|
|
517
572
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
...options.changeFeed.maxFeedHoldDays !== void 0 ? { maxFeedHoldDays: options.changeFeed.maxFeedHoldDays } : {}
|
|
528
|
-
} : {}
|
|
529
|
-
)
|
|
530
|
-
]
|
|
531
|
-
] : [],
|
|
532
|
-
...options.retention?.enabled ? [
|
|
533
|
-
[
|
|
534
|
-
"retention",
|
|
535
|
-
coreStore.retireExpiredPeriods({
|
|
536
|
-
statusRetentionDays: options.statusRetentionDays,
|
|
537
|
-
...options.retention.recordRetentionDays !== void 0 ? { recordRetentionDays: options.retention.recordRetentionDays } : {},
|
|
538
|
-
...typeof options.changeFeed === "object" && options.changeFeed.maxFeedHoldDays !== void 0 ? { maxFeedHoldDays: options.changeFeed.maxFeedHoldDays } : {},
|
|
539
|
-
limit: options.retention.limit ?? 100,
|
|
540
|
-
signal
|
|
541
|
-
})
|
|
542
|
-
]
|
|
543
|
-
] : []
|
|
544
|
-
];
|
|
545
|
-
const maintenanceResults = await Promise.allSettled(ledgerMaintenance.map(([, task]) => task));
|
|
546
|
-
for (const [index, result] of maintenanceResults.entries()) {
|
|
547
|
-
const name = ledgerMaintenance[index][0];
|
|
548
|
-
if (result.status === "rejected") {
|
|
549
|
-
if (!signal.aborted) await reportAuxiliaryError(scheduler, result.reason);
|
|
550
|
-
continue;
|
|
551
|
-
}
|
|
552
|
-
const value = result.value;
|
|
553
|
-
const failed = Array.isArray(value?.failed) ? value.failed.length : typeof value?.failed === "number" ? value.failed : 0;
|
|
554
|
-
if (failed > 0 && !signal.aborted) {
|
|
555
|
-
await reportAuxiliaryError(
|
|
556
|
-
scheduler,
|
|
557
|
-
new ReportingMaintenancePartialFailureError(
|
|
558
|
-
name,
|
|
559
|
-
failed,
|
|
560
|
-
value?.failures?.length ? value.failures.map((entry) => `${entry.reporting_obligation_id} (${entry.cause})`) : Array.isArray(value?.failed) ? value.failed : []
|
|
561
|
-
)
|
|
562
|
-
);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
573
|
+
await runReportingLedgerMaintenance({
|
|
574
|
+
store: coreStore,
|
|
575
|
+
sweepSnapshots: (limit) => sweepExpiredReportingLedgerState(options.db, limit),
|
|
576
|
+
statusRetentionDays: options.statusRetentionDays,
|
|
577
|
+
retention: options.retention,
|
|
578
|
+
changeFeed: options.changeFeed,
|
|
579
|
+
signal,
|
|
580
|
+
report: (error) => reportAuxiliaryError(scheduler, error)
|
|
581
|
+
});
|
|
565
582
|
const accountIds = rotate(await deploymentWideAccountIds(coreStore), auxiliaryRotation);
|
|
566
583
|
auxiliaryRotation += 1;
|
|
567
584
|
for (const accountId of accountIds) {
|
|
@@ -687,8 +704,9 @@ function routeSource(sources, sourceScope) {
|
|
|
687
704
|
if (!source) throw new TypeError("Reporting sourceScope has no installed adapter route");
|
|
688
705
|
return source;
|
|
689
706
|
}
|
|
690
|
-
async function schedulerLoop(service, options, signal, store) {
|
|
707
|
+
async function schedulerLoop(service, options, signal, store, maintenance) {
|
|
691
708
|
let rotation = 0;
|
|
709
|
+
let lastMaintenanceAt;
|
|
692
710
|
while (!signal.aborted) {
|
|
693
711
|
let accountIds = [];
|
|
694
712
|
try {
|
|
@@ -725,6 +743,14 @@ async function schedulerLoop(service, options, signal, store) {
|
|
|
725
743
|
await reportSchedulerError(options, signal, error, { phase: "cycle", accountId });
|
|
726
744
|
}
|
|
727
745
|
}
|
|
746
|
+
if (maintenance && !signal.aborted && (lastMaintenanceAt === void 0 || Date.now() - lastMaintenanceAt >= maintenance.intervalMilliseconds)) {
|
|
747
|
+
lastMaintenanceAt = Date.now();
|
|
748
|
+
try {
|
|
749
|
+
await maintenance.run();
|
|
750
|
+
} catch (error) {
|
|
751
|
+
await reportSchedulerError(options, signal, error, { phase: "maintenance" });
|
|
752
|
+
}
|
|
753
|
+
}
|
|
728
754
|
if (!signal.aborted) await abortableDelay(options.intervalMilliseconds, signal);
|
|
729
755
|
}
|
|
730
756
|
}
|
|
@@ -741,7 +767,7 @@ async function reportSchedulerError(options, signal, error, site) {
|
|
|
741
767
|
warnSchedulerFailure(options, site, error);
|
|
742
768
|
}
|
|
743
769
|
function warnSchedulerFailure(options, site, error, observerError) {
|
|
744
|
-
const where = site.phase === "roster" ? "could not resolve its account roster, so this pass ran no tenants" : site.accountId === void 0 ? "deployment-wide cycle failed; the scheduler continues" : `cycle failed for account ${site.accountId}; later tenants continue`;
|
|
770
|
+
const where = site.phase === "roster" ? "could not resolve its account roster, so this pass ran no tenants" : site.phase === "maintenance" ? "ledger maintenance failed; the scheduler continues" : site.accountId === void 0 ? "deployment-wide cycle failed; the scheduler continues" : `cycle failed for account ${site.accountId}; later tenants continue`;
|
|
745
771
|
const observed = observerError === void 0 ? "" : ` (the configured onError also threw: ${safeMessage(observerError)})`;
|
|
746
772
|
const message = `[adcp/reporting] scheduler ${where}: ${safeMessage(error)}${observed}`;
|
|
747
773
|
const logger = options.logger;
|
|
@@ -1210,20 +1236,9 @@ function deepFreeze(value) {
|
|
|
1210
1236
|
return value;
|
|
1211
1237
|
}
|
|
1212
1238
|
export * from "./conformance.mjs";
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
`Reporting ${task} could not process ${failedCount} item(s)${failedIds.length ? `: ${failedIds.slice(0, 20).join(", ")}` : ""}`
|
|
1217
|
-
);
|
|
1218
|
-
this.task = task;
|
|
1219
|
-
this.failedCount = failedCount;
|
|
1220
|
-
this.failedIds = failedIds;
|
|
1221
|
-
this.name = "ReportingMaintenancePartialFailureError";
|
|
1222
|
-
}
|
|
1223
|
-
task;
|
|
1224
|
-
failedCount;
|
|
1225
|
-
failedIds;
|
|
1226
|
-
}
|
|
1239
|
+
import {
|
|
1240
|
+
ReportingMaintenancePartialFailureError
|
|
1241
|
+
} from "./maintenance.mjs";
|
|
1227
1242
|
export {
|
|
1228
1243
|
ReportingMaintenancePartialFailureError,
|
|
1229
1244
|
createPostgresReliableReportingProductionService,
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { PostgresReportingLedgerStore } from '../ledger/index.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* Period-aligned retention. Whole periods are retired once
|
|
4
|
+
* `max(statusRetentionDays, recordRetentionDays)` has elapsed since both the
|
|
5
|
+
* period end and its latest publication. `statusRetentionDays` always comes
|
|
6
|
+
* from the service, so retention can never disagree with the commitment the
|
|
7
|
+
* service advertises to buyers.
|
|
8
|
+
*/
|
|
9
|
+
export interface ReportingRetentionOptionsV1 {
|
|
10
|
+
enabled: true;
|
|
11
|
+
recordRetentionDays?: number;
|
|
12
|
+
limit?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Change-feed pruning window and the consumer hold shared with retention. */
|
|
15
|
+
export type ReportingChangeFeedMaintenanceOptionsV1 = boolean | {
|
|
16
|
+
changeRetentionDays?: number;
|
|
17
|
+
maxFeedHoldDays?: number;
|
|
18
|
+
};
|
|
19
|
+
/** The ledger-store surface a maintenance pass drives. */
|
|
20
|
+
export type ReportingLedgerMaintenanceStoreV1 = Pick<PostgresReportingLedgerStore, 'sweepRowWriteIntents' | 'pruneChanges' | 'retireExpiredPeriods'>;
|
|
21
|
+
interface ReportingLedgerMaintenanceTaskOutcomesV1 {
|
|
22
|
+
'snapshot sweep': Awaited<ReturnType<PostgresReportingLedgerStore['sweepExpiredState']>>;
|
|
23
|
+
'row upload sweep': Awaited<ReturnType<PostgresReportingLedgerStore['sweepRowWriteIntents']>>;
|
|
24
|
+
'change-feed pruning': Awaited<ReturnType<PostgresReportingLedgerStore['pruneChanges']>>;
|
|
25
|
+
retention: Awaited<ReturnType<PostgresReportingLedgerStore['retireExpiredPeriods']>>;
|
|
26
|
+
}
|
|
27
|
+
export type ReportingLedgerMaintenanceTaskNameV1 = keyof ReportingLedgerMaintenanceTaskOutcomesV1;
|
|
28
|
+
/**
|
|
29
|
+
* A scheduled maintenance pass completed but could not process some items
|
|
30
|
+
* (for example, a retention period whose objects could not be deleted). The
|
|
31
|
+
* pass resumes them next time; repeated reports need operator attention.
|
|
32
|
+
*/
|
|
33
|
+
export declare class ReportingMaintenancePartialFailureError extends Error {
|
|
34
|
+
readonly task: string;
|
|
35
|
+
readonly failedCount: number;
|
|
36
|
+
readonly failedIds: readonly string[];
|
|
37
|
+
constructor(task: string, failedCount: number, failedIds: readonly string[]);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Outcome of one maintenance task. A task that resolved but could not process
|
|
41
|
+
* some items (a retention period whose objects would not delete, an upload that
|
|
42
|
+
* would not sweep) is `completed` with `partialFailure` set; the pass resumes
|
|
43
|
+
* those items next time.
|
|
44
|
+
*/
|
|
45
|
+
export type ReportingLedgerMaintenanceTaskResultV1 = {
|
|
46
|
+
[Task in ReportingLedgerMaintenanceTaskNameV1]: {
|
|
47
|
+
readonly task: Task;
|
|
48
|
+
readonly status: 'completed';
|
|
49
|
+
readonly result: ReportingLedgerMaintenanceTaskOutcomesV1[Task];
|
|
50
|
+
readonly partialFailure?: ReportingMaintenancePartialFailureError;
|
|
51
|
+
} | {
|
|
52
|
+
readonly task: Task;
|
|
53
|
+
readonly status: 'failed';
|
|
54
|
+
readonly error: unknown;
|
|
55
|
+
};
|
|
56
|
+
}[ReportingLedgerMaintenanceTaskNameV1];
|
|
57
|
+
export interface RunReportingLedgerMaintenanceInputV1 {
|
|
58
|
+
store: ReportingLedgerMaintenanceStoreV1;
|
|
59
|
+
/**
|
|
60
|
+
* Expired cursor snapshot and checkpoint sweep. Omitted when the caller has
|
|
61
|
+
* no way to reach the database, in which case that task is not run.
|
|
62
|
+
*/
|
|
63
|
+
sweepSnapshots?: (limit: number) => Promise<ReportingLedgerMaintenanceTaskOutcomesV1['snapshot sweep']>;
|
|
64
|
+
statusRetentionDays: number;
|
|
65
|
+
retention?: ReportingRetentionOptionsV1 | undefined;
|
|
66
|
+
changeFeed?: ReportingChangeFeedMaintenanceOptionsV1 | undefined;
|
|
67
|
+
signal?: AbortSignal | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Called, in task order and awaited, for every task that rejected or
|
|
70
|
+
* partially failed, unless `signal` has aborted (an abort is shutdown, not a
|
|
71
|
+
* fault).
|
|
72
|
+
*/
|
|
73
|
+
report?: (error: unknown) => Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The single ledger-maintenance pass shared by the Core service and the
|
|
77
|
+
* production service, so partial-failure surfacing, the `maxFeedHoldDays` that
|
|
78
|
+
* retention honours, per-task limits and abort handling cannot drift between
|
|
79
|
+
* them. Tasks start together, in a fixed order, and are isolated: one task's
|
|
80
|
+
* failure never prevents another from running.
|
|
81
|
+
*/
|
|
82
|
+
export declare function runReportingLedgerMaintenance(input: RunReportingLedgerMaintenanceInputV1): Promise<ReportingLedgerMaintenanceTaskResultV1[]>;
|
|
83
|
+
/**
|
|
84
|
+
* Fail fast on maintenance options the store would otherwise reject on every
|
|
85
|
+
* pass. Mirrors the checks `retireExpiredPeriods` and `pruneChanges` apply.
|
|
86
|
+
*/
|
|
87
|
+
export declare function assertReportingLedgerMaintenanceOptions(input: {
|
|
88
|
+
retention?: ReportingRetentionOptionsV1 | undefined;
|
|
89
|
+
changeFeed?: ReportingChangeFeedMaintenanceOptionsV1 | undefined;
|
|
90
|
+
}, statusRetentionDays: number): void;
|
|
91
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { PostgresReportingLedgerStore } from '../ledger';
|
|
2
|
+
/**
|
|
3
|
+
* Period-aligned retention. Whole periods are retired once
|
|
4
|
+
* `max(statusRetentionDays, recordRetentionDays)` has elapsed since both the
|
|
5
|
+
* period end and its latest publication. `statusRetentionDays` always comes
|
|
6
|
+
* from the service, so retention can never disagree with the commitment the
|
|
7
|
+
* service advertises to buyers.
|
|
8
|
+
*/
|
|
9
|
+
export interface ReportingRetentionOptionsV1 {
|
|
10
|
+
enabled: true;
|
|
11
|
+
recordRetentionDays?: number;
|
|
12
|
+
limit?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Change-feed pruning window and the consumer hold shared with retention. */
|
|
15
|
+
export type ReportingChangeFeedMaintenanceOptionsV1 = boolean | {
|
|
16
|
+
changeRetentionDays?: number;
|
|
17
|
+
maxFeedHoldDays?: number;
|
|
18
|
+
};
|
|
19
|
+
/** The ledger-store surface a maintenance pass drives. */
|
|
20
|
+
export type ReportingLedgerMaintenanceStoreV1 = Pick<PostgresReportingLedgerStore, 'sweepRowWriteIntents' | 'pruneChanges' | 'retireExpiredPeriods'>;
|
|
21
|
+
interface ReportingLedgerMaintenanceTaskOutcomesV1 {
|
|
22
|
+
'snapshot sweep': Awaited<ReturnType<PostgresReportingLedgerStore['sweepExpiredState']>>;
|
|
23
|
+
'row upload sweep': Awaited<ReturnType<PostgresReportingLedgerStore['sweepRowWriteIntents']>>;
|
|
24
|
+
'change-feed pruning': Awaited<ReturnType<PostgresReportingLedgerStore['pruneChanges']>>;
|
|
25
|
+
retention: Awaited<ReturnType<PostgresReportingLedgerStore['retireExpiredPeriods']>>;
|
|
26
|
+
}
|
|
27
|
+
export type ReportingLedgerMaintenanceTaskNameV1 = keyof ReportingLedgerMaintenanceTaskOutcomesV1;
|
|
28
|
+
/**
|
|
29
|
+
* A scheduled maintenance pass completed but could not process some items
|
|
30
|
+
* (for example, a retention period whose objects could not be deleted). The
|
|
31
|
+
* pass resumes them next time; repeated reports need operator attention.
|
|
32
|
+
*/
|
|
33
|
+
export declare class ReportingMaintenancePartialFailureError extends Error {
|
|
34
|
+
readonly task: string;
|
|
35
|
+
readonly failedCount: number;
|
|
36
|
+
readonly failedIds: readonly string[];
|
|
37
|
+
constructor(task: string, failedCount: number, failedIds: readonly string[]);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Outcome of one maintenance task. A task that resolved but could not process
|
|
41
|
+
* some items (a retention period whose objects would not delete, an upload that
|
|
42
|
+
* would not sweep) is `completed` with `partialFailure` set; the pass resumes
|
|
43
|
+
* those items next time.
|
|
44
|
+
*/
|
|
45
|
+
export type ReportingLedgerMaintenanceTaskResultV1 = {
|
|
46
|
+
[Task in ReportingLedgerMaintenanceTaskNameV1]: {
|
|
47
|
+
readonly task: Task;
|
|
48
|
+
readonly status: 'completed';
|
|
49
|
+
readonly result: ReportingLedgerMaintenanceTaskOutcomesV1[Task];
|
|
50
|
+
readonly partialFailure?: ReportingMaintenancePartialFailureError;
|
|
51
|
+
} | {
|
|
52
|
+
readonly task: Task;
|
|
53
|
+
readonly status: 'failed';
|
|
54
|
+
readonly error: unknown;
|
|
55
|
+
};
|
|
56
|
+
}[ReportingLedgerMaintenanceTaskNameV1];
|
|
57
|
+
export interface RunReportingLedgerMaintenanceInputV1 {
|
|
58
|
+
store: ReportingLedgerMaintenanceStoreV1;
|
|
59
|
+
/**
|
|
60
|
+
* Expired cursor snapshot and checkpoint sweep. Omitted when the caller has
|
|
61
|
+
* no way to reach the database, in which case that task is not run.
|
|
62
|
+
*/
|
|
63
|
+
sweepSnapshots?: (limit: number) => Promise<ReportingLedgerMaintenanceTaskOutcomesV1['snapshot sweep']>;
|
|
64
|
+
statusRetentionDays: number;
|
|
65
|
+
retention?: ReportingRetentionOptionsV1 | undefined;
|
|
66
|
+
changeFeed?: ReportingChangeFeedMaintenanceOptionsV1 | undefined;
|
|
67
|
+
signal?: AbortSignal | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Called, in task order and awaited, for every task that rejected or
|
|
70
|
+
* partially failed, unless `signal` has aborted (an abort is shutdown, not a
|
|
71
|
+
* fault).
|
|
72
|
+
*/
|
|
73
|
+
report?: (error: unknown) => Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The single ledger-maintenance pass shared by the Core service and the
|
|
77
|
+
* production service, so partial-failure surfacing, the `maxFeedHoldDays` that
|
|
78
|
+
* retention honours, per-task limits and abort handling cannot drift between
|
|
79
|
+
* them. Tasks start together, in a fixed order, and are isolated: one task's
|
|
80
|
+
* failure never prevents another from running.
|
|
81
|
+
*/
|
|
82
|
+
export declare function runReportingLedgerMaintenance(input: RunReportingLedgerMaintenanceInputV1): Promise<ReportingLedgerMaintenanceTaskResultV1[]>;
|
|
83
|
+
/**
|
|
84
|
+
* Fail fast on maintenance options the store would otherwise reject on every
|
|
85
|
+
* pass. Mirrors the checks `retireExpiredPeriods` and `pruneChanges` apply.
|
|
86
|
+
*/
|
|
87
|
+
export declare function assertReportingLedgerMaintenanceOptions(input: {
|
|
88
|
+
retention?: ReportingRetentionOptionsV1 | undefined;
|
|
89
|
+
changeFeed?: ReportingChangeFeedMaintenanceOptionsV1 | undefined;
|
|
90
|
+
}, statusRetentionDays: number): void;
|
|
91
|
+
export {};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var maintenance_exports = {};
|
|
20
|
+
__export(maintenance_exports, {
|
|
21
|
+
ReportingMaintenancePartialFailureError: () => ReportingMaintenancePartialFailureError,
|
|
22
|
+
assertReportingLedgerMaintenanceOptions: () => assertReportingLedgerMaintenanceOptions,
|
|
23
|
+
runReportingLedgerMaintenance: () => runReportingLedgerMaintenance
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(maintenance_exports);
|
|
26
|
+
class ReportingMaintenancePartialFailureError extends Error {
|
|
27
|
+
constructor(task, failedCount, failedIds) {
|
|
28
|
+
super(
|
|
29
|
+
`Reporting ${task} could not process ${failedCount} item(s)${failedIds.length ? `: ${failedIds.slice(0, 20).join(", ")}` : ""}`
|
|
30
|
+
);
|
|
31
|
+
this.task = task;
|
|
32
|
+
this.failedCount = failedCount;
|
|
33
|
+
this.failedIds = failedIds;
|
|
34
|
+
this.name = "ReportingMaintenancePartialFailureError";
|
|
35
|
+
}
|
|
36
|
+
task;
|
|
37
|
+
failedCount;
|
|
38
|
+
failedIds;
|
|
39
|
+
}
|
|
40
|
+
async function runReportingLedgerMaintenance(input) {
|
|
41
|
+
const { store, signal, changeFeed, retention } = input;
|
|
42
|
+
const start = (name, run) => {
|
|
43
|
+
try {
|
|
44
|
+
return [name, run()];
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return [name, Promise.reject(error)];
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const tasks = [
|
|
50
|
+
...input.sweepSnapshots ? [start("snapshot sweep", () => input.sweepSnapshots(1e3))] : [],
|
|
51
|
+
start("row upload sweep", () => store.sweepRowWriteIntents({ limit: 100, ...signal ? { signal } : {} })),
|
|
52
|
+
...changeFeed ? [
|
|
53
|
+
start(
|
|
54
|
+
"change-feed pruning",
|
|
55
|
+
() => store.pruneChanges(
|
|
56
|
+
typeof changeFeed === "object" ? {
|
|
57
|
+
...changeFeed.changeRetentionDays !== void 0 ? { changeRetentionDays: changeFeed.changeRetentionDays } : {},
|
|
58
|
+
...changeFeed.maxFeedHoldDays !== void 0 ? { maxFeedHoldDays: changeFeed.maxFeedHoldDays } : {}
|
|
59
|
+
} : {}
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
] : [],
|
|
63
|
+
...retention?.enabled ? [
|
|
64
|
+
start(
|
|
65
|
+
"retention",
|
|
66
|
+
() => store.retireExpiredPeriods({
|
|
67
|
+
statusRetentionDays: input.statusRetentionDays,
|
|
68
|
+
...retention.recordRetentionDays !== void 0 ? { recordRetentionDays: retention.recordRetentionDays } : {},
|
|
69
|
+
...typeof changeFeed === "object" && changeFeed.maxFeedHoldDays !== void 0 ? { maxFeedHoldDays: changeFeed.maxFeedHoldDays } : {},
|
|
70
|
+
limit: retention.limit ?? 100,
|
|
71
|
+
...signal ? { signal } : {}
|
|
72
|
+
})
|
|
73
|
+
)
|
|
74
|
+
] : []
|
|
75
|
+
];
|
|
76
|
+
const settled = await Promise.allSettled(tasks.map(([, task]) => task));
|
|
77
|
+
const results = [];
|
|
78
|
+
for (const [index, outcome] of settled.entries()) {
|
|
79
|
+
const name = tasks[index][0];
|
|
80
|
+
if (outcome.status === "rejected") {
|
|
81
|
+
if (!signal?.aborted) await input.report?.(outcome.reason);
|
|
82
|
+
results.push({ task: name, status: "failed", error: outcome.reason });
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const value = outcome.value;
|
|
86
|
+
const failed = Array.isArray(value?.failed) ? value.failed.length : typeof value?.failed === "number" ? value.failed : 0;
|
|
87
|
+
let partialFailure;
|
|
88
|
+
if (failed > 0) {
|
|
89
|
+
partialFailure = new ReportingMaintenancePartialFailureError(
|
|
90
|
+
name,
|
|
91
|
+
failed,
|
|
92
|
+
value?.failures?.length ? value.failures.map((entry) => `${entry.reporting_obligation_id} (${entry.cause})`) : Array.isArray(value?.failed) ? value.failed : []
|
|
93
|
+
);
|
|
94
|
+
if (!signal?.aborted) await input.report?.(partialFailure);
|
|
95
|
+
}
|
|
96
|
+
results.push({
|
|
97
|
+
task: name,
|
|
98
|
+
status: "completed",
|
|
99
|
+
result: outcome.value,
|
|
100
|
+
...partialFailure ? { partialFailure } : {}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return results;
|
|
104
|
+
}
|
|
105
|
+
function assertReportingLedgerMaintenanceOptions(input, statusRetentionDays) {
|
|
106
|
+
const positive = (value, field) => {
|
|
107
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
108
|
+
throw new TypeError(`${field} must be a positive safe integer`);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const { retention, changeFeed } = input;
|
|
112
|
+
if (retention !== void 0) {
|
|
113
|
+
if (retention === null || typeof retention !== "object" || retention.enabled !== true) {
|
|
114
|
+
throw new TypeError("maintenance.retention must be { enabled: true, ... }");
|
|
115
|
+
}
|
|
116
|
+
if (retention.recordRetentionDays !== void 0) {
|
|
117
|
+
positive(retention.recordRetentionDays, "maintenance.retention.recordRetentionDays");
|
|
118
|
+
if (retention.recordRetentionDays < statusRetentionDays) {
|
|
119
|
+
throw new RangeError(
|
|
120
|
+
"maintenance.retention.recordRetentionDays must be no shorter than statusRetentionDays, the retention advertised to buyers"
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (retention.limit !== void 0) positive(retention.limit, "maintenance.retention.limit");
|
|
125
|
+
}
|
|
126
|
+
if (typeof changeFeed === "object" && changeFeed !== null) {
|
|
127
|
+
if (changeFeed.changeRetentionDays !== void 0) {
|
|
128
|
+
positive(changeFeed.changeRetentionDays, "maintenance.changeFeed.changeRetentionDays");
|
|
129
|
+
}
|
|
130
|
+
if (changeFeed.maxFeedHoldDays !== void 0) {
|
|
131
|
+
positive(changeFeed.maxFeedHoldDays, "maintenance.changeFeed.maxFeedHoldDays");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
136
|
+
0 && (module.exports = {
|
|
137
|
+
ReportingMaintenancePartialFailureError,
|
|
138
|
+
assertReportingLedgerMaintenanceOptions,
|
|
139
|
+
runReportingLedgerMaintenance
|
|
140
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
class ReportingMaintenancePartialFailureError extends Error {
|
|
2
|
+
constructor(task, failedCount, failedIds) {
|
|
3
|
+
super(
|
|
4
|
+
`Reporting ${task} could not process ${failedCount} item(s)${failedIds.length ? `: ${failedIds.slice(0, 20).join(", ")}` : ""}`
|
|
5
|
+
);
|
|
6
|
+
this.task = task;
|
|
7
|
+
this.failedCount = failedCount;
|
|
8
|
+
this.failedIds = failedIds;
|
|
9
|
+
this.name = "ReportingMaintenancePartialFailureError";
|
|
10
|
+
}
|
|
11
|
+
task;
|
|
12
|
+
failedCount;
|
|
13
|
+
failedIds;
|
|
14
|
+
}
|
|
15
|
+
async function runReportingLedgerMaintenance(input) {
|
|
16
|
+
const { store, signal, changeFeed, retention } = input;
|
|
17
|
+
const start = (name, run) => {
|
|
18
|
+
try {
|
|
19
|
+
return [name, run()];
|
|
20
|
+
} catch (error) {
|
|
21
|
+
return [name, Promise.reject(error)];
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const tasks = [
|
|
25
|
+
...input.sweepSnapshots ? [start("snapshot sweep", () => input.sweepSnapshots(1e3))] : [],
|
|
26
|
+
start("row upload sweep", () => store.sweepRowWriteIntents({ limit: 100, ...signal ? { signal } : {} })),
|
|
27
|
+
...changeFeed ? [
|
|
28
|
+
start(
|
|
29
|
+
"change-feed pruning",
|
|
30
|
+
() => store.pruneChanges(
|
|
31
|
+
typeof changeFeed === "object" ? {
|
|
32
|
+
...changeFeed.changeRetentionDays !== void 0 ? { changeRetentionDays: changeFeed.changeRetentionDays } : {},
|
|
33
|
+
...changeFeed.maxFeedHoldDays !== void 0 ? { maxFeedHoldDays: changeFeed.maxFeedHoldDays } : {}
|
|
34
|
+
} : {}
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
] : [],
|
|
38
|
+
...retention?.enabled ? [
|
|
39
|
+
start(
|
|
40
|
+
"retention",
|
|
41
|
+
() => store.retireExpiredPeriods({
|
|
42
|
+
statusRetentionDays: input.statusRetentionDays,
|
|
43
|
+
...retention.recordRetentionDays !== void 0 ? { recordRetentionDays: retention.recordRetentionDays } : {},
|
|
44
|
+
...typeof changeFeed === "object" && changeFeed.maxFeedHoldDays !== void 0 ? { maxFeedHoldDays: changeFeed.maxFeedHoldDays } : {},
|
|
45
|
+
limit: retention.limit ?? 100,
|
|
46
|
+
...signal ? { signal } : {}
|
|
47
|
+
})
|
|
48
|
+
)
|
|
49
|
+
] : []
|
|
50
|
+
];
|
|
51
|
+
const settled = await Promise.allSettled(tasks.map(([, task]) => task));
|
|
52
|
+
const results = [];
|
|
53
|
+
for (const [index, outcome] of settled.entries()) {
|
|
54
|
+
const name = tasks[index][0];
|
|
55
|
+
if (outcome.status === "rejected") {
|
|
56
|
+
if (!signal?.aborted) await input.report?.(outcome.reason);
|
|
57
|
+
results.push({ task: name, status: "failed", error: outcome.reason });
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const value = outcome.value;
|
|
61
|
+
const failed = Array.isArray(value?.failed) ? value.failed.length : typeof value?.failed === "number" ? value.failed : 0;
|
|
62
|
+
let partialFailure;
|
|
63
|
+
if (failed > 0) {
|
|
64
|
+
partialFailure = new ReportingMaintenancePartialFailureError(
|
|
65
|
+
name,
|
|
66
|
+
failed,
|
|
67
|
+
value?.failures?.length ? value.failures.map((entry) => `${entry.reporting_obligation_id} (${entry.cause})`) : Array.isArray(value?.failed) ? value.failed : []
|
|
68
|
+
);
|
|
69
|
+
if (!signal?.aborted) await input.report?.(partialFailure);
|
|
70
|
+
}
|
|
71
|
+
results.push({
|
|
72
|
+
task: name,
|
|
73
|
+
status: "completed",
|
|
74
|
+
result: outcome.value,
|
|
75
|
+
...partialFailure ? { partialFailure } : {}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return results;
|
|
79
|
+
}
|
|
80
|
+
function assertReportingLedgerMaintenanceOptions(input, statusRetentionDays) {
|
|
81
|
+
const positive = (value, field) => {
|
|
82
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
83
|
+
throw new TypeError(`${field} must be a positive safe integer`);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const { retention, changeFeed } = input;
|
|
87
|
+
if (retention !== void 0) {
|
|
88
|
+
if (retention === null || typeof retention !== "object" || retention.enabled !== true) {
|
|
89
|
+
throw new TypeError("maintenance.retention must be { enabled: true, ... }");
|
|
90
|
+
}
|
|
91
|
+
if (retention.recordRetentionDays !== void 0) {
|
|
92
|
+
positive(retention.recordRetentionDays, "maintenance.retention.recordRetentionDays");
|
|
93
|
+
if (retention.recordRetentionDays < statusRetentionDays) {
|
|
94
|
+
throw new RangeError(
|
|
95
|
+
"maintenance.retention.recordRetentionDays must be no shorter than statusRetentionDays, the retention advertised to buyers"
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (retention.limit !== void 0) positive(retention.limit, "maintenance.retention.limit");
|
|
100
|
+
}
|
|
101
|
+
if (typeof changeFeed === "object" && changeFeed !== null) {
|
|
102
|
+
if (changeFeed.changeRetentionDays !== void 0) {
|
|
103
|
+
positive(changeFeed.changeRetentionDays, "maintenance.changeFeed.changeRetentionDays");
|
|
104
|
+
}
|
|
105
|
+
if (changeFeed.maxFeedHoldDays !== void 0) {
|
|
106
|
+
positive(changeFeed.maxFeedHoldDays, "maintenance.changeFeed.maxFeedHoldDays");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export {
|
|
111
|
+
ReportingMaintenancePartialFailureError,
|
|
112
|
+
assertReportingLedgerMaintenanceOptions,
|
|
113
|
+
runReportingLedgerMaintenance
|
|
114
|
+
};
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
"source_sha": "4e553ad955f83b49c7d221ab5c3ff78237ad02e3",
|
|
5
5
|
"source_tarball_sha256": "580656d6466ef9f0d1119985e6726c2efea718dc671e2ad30957fcb2fd54af0f",
|
|
6
6
|
"upstream_adcp_version": "2.5.3",
|
|
7
|
-
"synced_at": "2026-10-
|
|
7
|
+
"synced_at": "2026-10-09T23:50:25.104Z"
|
|
8
8
|
}
|
|
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
|
|
|
197
197
|
*/
|
|
198
198
|
export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
|
|
199
199
|
/** Capability semver emitted in run summaries and used for advisory expiry. */
|
|
200
|
-
export declare const RUNNER_CAPABILITY_VERSION = "15.
|
|
200
|
+
export declare const RUNNER_CAPABILITY_VERSION = "15.7.0";
|
|
201
201
|
/** True when a failed validation contributes to the owning step's grade. */
|
|
202
202
|
export declare function validationFailsStep(result: ValidationResult): boolean;
|
|
203
203
|
/**
|
|
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
|
|
|
197
197
|
*/
|
|
198
198
|
export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
|
|
199
199
|
/** Capability semver emitted in run summaries and used for advisory expiry. */
|
|
200
|
-
export declare const RUNNER_CAPABILITY_VERSION = "15.
|
|
200
|
+
export declare const RUNNER_CAPABILITY_VERSION = "15.7.0";
|
|
201
201
|
/** True when a failed validation contributes to the owning step's grade. */
|
|
202
202
|
export declare function validationFailsStep(result: ValidationResult): boolean;
|
|
203
203
|
/**
|