@hasna/uptime 0.1.3 → 0.1.5
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/CHANGELOG.md +47 -0
- package/README.md +42 -3
- package/dist/api.js +626 -4
- package/dist/cli/index.js +1090 -4
- package/dist/cloud-plan.d.ts +113 -0
- package/dist/cloud-plan.d.ts.map +1 -0
- package/dist/cloud-plan.js +267 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +891 -4
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +664 -4
- package/dist/report.d.ts +2 -7
- package/dist/report.d.ts.map +1 -1
- package/dist/service.d.ts +41 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +577 -4
- package/dist/store.d.ts +22 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +427 -4
- package/dist/types.d.ts +92 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/aws-deployment-runbook.md +92 -0
- package/package.json +7 -2
package/dist/store.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CheckResult, Incident, ImportedMonitorInput, ImportedUpdateMonitorInput, ListResultsOptions, Monitor, ProbeCheckJob, ProbeIdentity, ProbeSubmissionReceipt, UptimeSummary } from "./types.js";
|
|
1
|
+
import type { AuditEvent, CheckResult, CreateReportScheduleInput, Incident, ImportedMonitorInput, ImportedUpdateMonitorInput, ListAuditEventsOptions, ListReportRunsOptions, ListResultsOptions, Monitor, ProbeCheckJob, ProbeIdentity, ProbeSubmissionReceipt, RecordAuditEventInput, ReportDeliveryRecord, ReportRun, ReportRunStatus, ReportSchedule, UpdateReportScheduleInput, UptimeSummary } from "./types.js";
|
|
2
2
|
export interface UptimeStoreOptions {
|
|
3
3
|
dbPath?: string;
|
|
4
4
|
mode?: UptimeRuntimeMode;
|
|
@@ -114,6 +114,26 @@ export declare class UptimeStore {
|
|
|
114
114
|
recordProbeSubmission(input: Omit<ProbeSubmissionReceipt, "id" | "submittedAt"> & {
|
|
115
115
|
submittedAt?: string;
|
|
116
116
|
}): ProbeSubmissionReceipt;
|
|
117
|
+
createReportSchedule(input: CreateReportScheduleInput): ReportSchedule;
|
|
118
|
+
listReportSchedules(options?: {
|
|
119
|
+
includeDisabled?: boolean;
|
|
120
|
+
}): ReportSchedule[];
|
|
121
|
+
listDueReportSchedules(nowIso?: string): ReportSchedule[];
|
|
122
|
+
getReportSchedule(idOrName: string): ReportSchedule | null;
|
|
123
|
+
updateReportSchedule(idOrName: string, input: UpdateReportScheduleInput): ReportSchedule;
|
|
124
|
+
deleteReportSchedule(idOrName: string): boolean;
|
|
125
|
+
recordReportRun(input: {
|
|
126
|
+
scheduleId?: string | null;
|
|
127
|
+
status: ReportRunStatus;
|
|
128
|
+
startedAt?: string;
|
|
129
|
+
finishedAt?: string;
|
|
130
|
+
deliveries?: ReportDeliveryRecord[];
|
|
131
|
+
error?: string | null;
|
|
132
|
+
reportJson?: Record<string, unknown> | null;
|
|
133
|
+
}): ReportRun;
|
|
134
|
+
listReportRuns(options?: ListReportRunsOptions): ReportRun[];
|
|
135
|
+
recordAuditEvent(input: RecordAuditEventInput): AuditEvent;
|
|
136
|
+
listAuditEvents(options?: ListAuditEventsOptions): AuditEvent[];
|
|
117
137
|
acquireCheckLease(monitorId: string, owner: string, ttlMs: number): boolean;
|
|
118
138
|
releaseCheckLease(monitorId: string, owner: string): void;
|
|
119
139
|
recordCheckResult(input: Omit<CheckResult, "id" | "checkedAt"> & {
|
|
@@ -139,6 +159,7 @@ export declare class UptimeStore {
|
|
|
139
159
|
private monitorSummary;
|
|
140
160
|
private reconcileIncidentInTransaction;
|
|
141
161
|
private closeOpenIncident;
|
|
162
|
+
private advanceReportSchedule;
|
|
142
163
|
private ensureColumn;
|
|
143
164
|
private ensureMonitorKindAllowsBrowserPage;
|
|
144
165
|
private vacuumInto;
|
package/dist/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAEV,WAAW,EACX,QAAQ,EACR,oBAAoB,EACpB,0BAA0B,EAC1B,kBAAkB,EAClB,OAAO,EAGP,aAAa,EAEb,aAAa,EACb,sBAAsB,EACtB,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAInD,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,4BAA4B;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,aAAa,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,EAAE,OAAO,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,EAAE,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/dist/store.js
CHANGED
|
@@ -108,9 +108,24 @@ var MAX_RESULT_LIMIT = 1000;
|
|
|
108
108
|
|
|
109
109
|
// src/store.ts
|
|
110
110
|
var SECRET_URL_PARAM_PATTERN = /(token|secret|password|passwd|api[_-]?key|access[_-]?token|auth|credential|session)/i;
|
|
111
|
-
var REQUIRED_TABLES = [
|
|
111
|
+
var REQUIRED_TABLES = [
|
|
112
|
+
"schema_migrations",
|
|
113
|
+
"monitors",
|
|
114
|
+
"check_results",
|
|
115
|
+
"incidents",
|
|
116
|
+
"check_leases",
|
|
117
|
+
"monitor_provenance",
|
|
118
|
+
"import_batches",
|
|
119
|
+
"probe_identities",
|
|
120
|
+
"probe_check_jobs",
|
|
121
|
+
"probe_submissions",
|
|
122
|
+
"report_schedules",
|
|
123
|
+
"report_runs",
|
|
124
|
+
"audit_events"
|
|
125
|
+
];
|
|
112
126
|
var PROBE_TABLES = new Set(["probe_identities", "probe_check_jobs", "probe_submissions"]);
|
|
113
|
-
var
|
|
127
|
+
var REPORT_AUDIT_TABLES = new Set(["report_schedules", "report_runs", "audit_events"]);
|
|
128
|
+
var CURRENT_SCHEMA_VERSION = "3";
|
|
114
129
|
|
|
115
130
|
class StaleCheckResultError extends Error {
|
|
116
131
|
constructor(message) {
|
|
@@ -270,6 +285,44 @@ class UptimeStore {
|
|
|
270
285
|
acquired_at TEXT NOT NULL
|
|
271
286
|
)
|
|
272
287
|
`);
|
|
288
|
+
this.db.run(`
|
|
289
|
+
CREATE TABLE IF NOT EXISTS report_schedules (
|
|
290
|
+
id TEXT PRIMARY KEY,
|
|
291
|
+
name TEXT NOT NULL UNIQUE,
|
|
292
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
293
|
+
interval_seconds INTEGER NOT NULL,
|
|
294
|
+
next_run_at TEXT NOT NULL,
|
|
295
|
+
last_run_at TEXT,
|
|
296
|
+
subject TEXT,
|
|
297
|
+
channels_json TEXT NOT NULL,
|
|
298
|
+
created_at TEXT NOT NULL,
|
|
299
|
+
updated_at TEXT NOT NULL
|
|
300
|
+
)
|
|
301
|
+
`);
|
|
302
|
+
this.db.run(`
|
|
303
|
+
CREATE TABLE IF NOT EXISTS report_runs (
|
|
304
|
+
id TEXT PRIMARY KEY,
|
|
305
|
+
schedule_id TEXT REFERENCES report_schedules(id) ON DELETE SET NULL,
|
|
306
|
+
status TEXT NOT NULL CHECK (status IN ('success', 'failed')),
|
|
307
|
+
started_at TEXT NOT NULL,
|
|
308
|
+
finished_at TEXT NOT NULL,
|
|
309
|
+
deliveries_json TEXT NOT NULL,
|
|
310
|
+
error TEXT,
|
|
311
|
+
report_json TEXT
|
|
312
|
+
)
|
|
313
|
+
`);
|
|
314
|
+
this.db.run(`
|
|
315
|
+
CREATE TABLE IF NOT EXISTS audit_events (
|
|
316
|
+
id TEXT PRIMARY KEY,
|
|
317
|
+
action TEXT NOT NULL,
|
|
318
|
+
resource_type TEXT,
|
|
319
|
+
resource_id TEXT,
|
|
320
|
+
message TEXT,
|
|
321
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
322
|
+
actor TEXT,
|
|
323
|
+
created_at TEXT NOT NULL
|
|
324
|
+
)
|
|
325
|
+
`);
|
|
273
326
|
this.db.run(`
|
|
274
327
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
275
328
|
key TEXT PRIMARY KEY,
|
|
@@ -287,6 +340,10 @@ class UptimeStore {
|
|
|
287
340
|
this.db.run("CREATE INDEX IF NOT EXISTS idx_probe_submissions_probe_time ON probe_submissions(probe_id, submitted_at DESC)");
|
|
288
341
|
this.db.run("CREATE INDEX IF NOT EXISTS idx_probe_submissions_monitor_time ON probe_submissions(monitor_id, checked_at DESC)");
|
|
289
342
|
this.db.run("CREATE UNIQUE INDEX IF NOT EXISTS idx_probe_submissions_job ON probe_submissions(job_id) WHERE job_id IS NOT NULL AND job_id != ''");
|
|
343
|
+
this.db.run("CREATE INDEX IF NOT EXISTS idx_report_schedules_due ON report_schedules(enabled, next_run_at)");
|
|
344
|
+
this.db.run("CREATE INDEX IF NOT EXISTS idx_report_runs_schedule_time ON report_runs(schedule_id, started_at DESC)");
|
|
345
|
+
this.db.run("CREATE INDEX IF NOT EXISTS idx_audit_events_resource_time ON audit_events(resource_type, resource_id, created_at DESC)");
|
|
346
|
+
this.db.run("CREATE INDEX IF NOT EXISTS idx_audit_events_time ON audit_events(created_at DESC)");
|
|
290
347
|
}
|
|
291
348
|
backup(destinationPath) {
|
|
292
349
|
if (this.dbPath === ":memory:" && !destinationPath) {
|
|
@@ -583,6 +640,136 @@ class UptimeStore {
|
|
|
583
640
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(receipt.id, receipt.probeId, receipt.jobId, receipt.monitorId, receipt.checkResultId, receipt.nonce, receipt.checkedAt, receipt.submittedAt);
|
|
584
641
|
return receipt;
|
|
585
642
|
}
|
|
643
|
+
createReportSchedule(input) {
|
|
644
|
+
const normalized = normalizeReportScheduleInput(input);
|
|
645
|
+
const now = new Date().toISOString();
|
|
646
|
+
const schedule = {
|
|
647
|
+
id: newId("rps"),
|
|
648
|
+
name: normalized.name,
|
|
649
|
+
enabled: normalized.enabled,
|
|
650
|
+
intervalSeconds: normalized.intervalSeconds,
|
|
651
|
+
nextRunAt: normalized.nextRunAt,
|
|
652
|
+
lastRunAt: null,
|
|
653
|
+
subject: normalized.subject,
|
|
654
|
+
channels: normalized.channels,
|
|
655
|
+
createdAt: now,
|
|
656
|
+
updatedAt: now
|
|
657
|
+
};
|
|
658
|
+
this.db.query(`INSERT INTO report_schedules (
|
|
659
|
+
id, name, enabled, interval_seconds, next_run_at, last_run_at,
|
|
660
|
+
subject, channels_json, created_at, updated_at
|
|
661
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(schedule.id, schedule.name, schedule.enabled ? 1 : 0, schedule.intervalSeconds, schedule.nextRunAt, schedule.lastRunAt, schedule.subject, JSON.stringify(schedule.channels), schedule.createdAt, schedule.updatedAt);
|
|
662
|
+
return schedule;
|
|
663
|
+
}
|
|
664
|
+
listReportSchedules(options = {}) {
|
|
665
|
+
const rows = options.includeDisabled ? this.db.query("SELECT * FROM report_schedules ORDER BY name ASC").all() : this.db.query("SELECT * FROM report_schedules WHERE enabled = 1 ORDER BY name ASC").all();
|
|
666
|
+
return rows.map(reportScheduleFromRow);
|
|
667
|
+
}
|
|
668
|
+
listDueReportSchedules(nowIso = new Date().toISOString()) {
|
|
669
|
+
assertIsoTimestamp(nowIso, "Report schedule due timestamp");
|
|
670
|
+
const rows = this.db.query("SELECT * FROM report_schedules WHERE enabled = 1 AND next_run_at <= ? ORDER BY next_run_at ASC, name ASC").all(nowIso);
|
|
671
|
+
return rows.map(reportScheduleFromRow);
|
|
672
|
+
}
|
|
673
|
+
getReportSchedule(idOrName) {
|
|
674
|
+
const row = this.db.query("SELECT * FROM report_schedules WHERE id = ? OR name = ?").get(idOrName, idOrName);
|
|
675
|
+
return row ? reportScheduleFromRow(row) : null;
|
|
676
|
+
}
|
|
677
|
+
updateReportSchedule(idOrName, input) {
|
|
678
|
+
const current = this.getReportSchedule(idOrName);
|
|
679
|
+
if (!current)
|
|
680
|
+
throw new Error(`Report schedule not found: ${idOrName}`);
|
|
681
|
+
const normalized = normalizeReportScheduleInput({
|
|
682
|
+
name: input.name ?? current.name,
|
|
683
|
+
intervalSeconds: input.intervalSeconds ?? current.intervalSeconds,
|
|
684
|
+
nextRunAt: input.nextRunAt ?? current.nextRunAt,
|
|
685
|
+
enabled: input.enabled ?? current.enabled,
|
|
686
|
+
subject: input.subject === undefined ? current.subject : input.subject,
|
|
687
|
+
channels: input.channels ?? current.channels
|
|
688
|
+
});
|
|
689
|
+
const updatedAt = new Date().toISOString();
|
|
690
|
+
this.db.query(`UPDATE report_schedules SET
|
|
691
|
+
name = ?, enabled = ?, interval_seconds = ?, next_run_at = ?,
|
|
692
|
+
subject = ?, channels_json = ?, updated_at = ?
|
|
693
|
+
WHERE id = ?`).run(normalized.name, normalized.enabled ? 1 : 0, normalized.intervalSeconds, normalized.nextRunAt, normalized.subject, JSON.stringify(normalized.channels), updatedAt, current.id);
|
|
694
|
+
return this.getReportSchedule(current.id);
|
|
695
|
+
}
|
|
696
|
+
deleteReportSchedule(idOrName) {
|
|
697
|
+
const current = this.getReportSchedule(idOrName);
|
|
698
|
+
if (!current)
|
|
699
|
+
return false;
|
|
700
|
+
this.db.query("DELETE FROM report_schedules WHERE id = ?").run(current.id);
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
recordReportRun(input) {
|
|
704
|
+
const startedAt = input.startedAt ?? new Date().toISOString();
|
|
705
|
+
const finishedAt = input.finishedAt ?? new Date().toISOString();
|
|
706
|
+
assertIsoTimestamp(startedAt, "Report run startedAt");
|
|
707
|
+
assertIsoTimestamp(finishedAt, "Report run finishedAt");
|
|
708
|
+
if (input.status !== "success" && input.status !== "failed") {
|
|
709
|
+
throw new Error("Report run status must be success or failed");
|
|
710
|
+
}
|
|
711
|
+
if (input.scheduleId && !this.getReportSchedule(input.scheduleId)) {
|
|
712
|
+
throw new Error(`Report schedule not found: ${input.scheduleId}`);
|
|
713
|
+
}
|
|
714
|
+
const run = {
|
|
715
|
+
id: newId("rpr"),
|
|
716
|
+
scheduleId: input.scheduleId ?? null,
|
|
717
|
+
status: input.status,
|
|
718
|
+
startedAt,
|
|
719
|
+
finishedAt,
|
|
720
|
+
deliveries: normalizeReportDeliveries(input.deliveries ?? []),
|
|
721
|
+
error: normalizeNullableRedactedText(input.error, "Report run error", 1000),
|
|
722
|
+
reportJson: input.reportJson ?? null
|
|
723
|
+
};
|
|
724
|
+
this.db.query(`INSERT INTO report_runs (
|
|
725
|
+
id, schedule_id, status, started_at, finished_at, deliveries_json,
|
|
726
|
+
error, report_json
|
|
727
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(run.id, run.scheduleId, run.status, run.startedAt, run.finishedAt, JSON.stringify(run.deliveries), run.error, run.reportJson ? JSON.stringify(run.reportJson) : null);
|
|
728
|
+
if (run.scheduleId) {
|
|
729
|
+
this.advanceReportSchedule(run.scheduleId, run.finishedAt);
|
|
730
|
+
}
|
|
731
|
+
return run;
|
|
732
|
+
}
|
|
733
|
+
listReportRuns(options = {}) {
|
|
734
|
+
const limit = clampLimit(options.limit ?? 50);
|
|
735
|
+
const rows = options.scheduleId ? this.db.query("SELECT * FROM report_runs WHERE schedule_id = ? ORDER BY started_at DESC, id DESC LIMIT ?").all(options.scheduleId, limit) : this.db.query("SELECT * FROM report_runs ORDER BY started_at DESC, id DESC LIMIT ?").all(limit);
|
|
736
|
+
return rows.map(reportRunFromRow);
|
|
737
|
+
}
|
|
738
|
+
recordAuditEvent(input) {
|
|
739
|
+
const action = normalizeAuditText(input.action, "Audit action", 160);
|
|
740
|
+
const createdAt = input.createdAt ?? new Date().toISOString();
|
|
741
|
+
assertIsoTimestamp(createdAt, "Audit event createdAt");
|
|
742
|
+
const event = {
|
|
743
|
+
id: newId("aud"),
|
|
744
|
+
action,
|
|
745
|
+
resourceType: normalizeNullableAuditText(input.resourceType, "Audit resourceType", 80),
|
|
746
|
+
resourceId: normalizeNullableAuditText(input.resourceId, "Audit resourceId", 160),
|
|
747
|
+
message: normalizeNullableAuditText(input.message, "Audit message", 500),
|
|
748
|
+
metadata: normalizeAuditMetadata(input.metadata ?? {}),
|
|
749
|
+
actor: normalizeNullableAuditText(input.actor, "Audit actor", 160),
|
|
750
|
+
createdAt
|
|
751
|
+
};
|
|
752
|
+
this.db.query(`INSERT INTO audit_events (
|
|
753
|
+
id, action, resource_type, resource_id, message, metadata_json, actor, created_at
|
|
754
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(event.id, event.action, event.resourceType, event.resourceId, event.message, JSON.stringify(event.metadata), event.actor, event.createdAt);
|
|
755
|
+
return event;
|
|
756
|
+
}
|
|
757
|
+
listAuditEvents(options = {}) {
|
|
758
|
+
const clauses = [];
|
|
759
|
+
const args = [];
|
|
760
|
+
if (options.resourceType) {
|
|
761
|
+
clauses.push("resource_type = ?");
|
|
762
|
+
args.push(options.resourceType);
|
|
763
|
+
}
|
|
764
|
+
if (options.resourceId) {
|
|
765
|
+
clauses.push("resource_id = ?");
|
|
766
|
+
args.push(options.resourceId);
|
|
767
|
+
}
|
|
768
|
+
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
769
|
+
args.push(clampLimit(options.limit ?? 50));
|
|
770
|
+
const rows = this.db.query(`SELECT * FROM audit_events ${where} ORDER BY created_at DESC, id DESC LIMIT ?`).all(...args);
|
|
771
|
+
return rows.map(auditEventFromRow);
|
|
772
|
+
}
|
|
586
773
|
acquireCheckLease(monitorId, owner, ttlMs) {
|
|
587
774
|
const now = new Date;
|
|
588
775
|
const nowIso = now.toISOString();
|
|
@@ -765,6 +952,18 @@ class UptimeStore {
|
|
|
765
952
|
closeOpenIncident(monitorId, closedAt) {
|
|
766
953
|
this.db.query("UPDATE incidents SET status = 'closed', closed_at = ? WHERE monitor_id = ? AND status = 'open'").run(closedAt, monitorId);
|
|
767
954
|
}
|
|
955
|
+
advanceReportSchedule(scheduleId, finishedAt) {
|
|
956
|
+
const schedule = this.getReportSchedule(scheduleId);
|
|
957
|
+
if (!schedule)
|
|
958
|
+
throw new Error(`Report schedule not found: ${scheduleId}`);
|
|
959
|
+
const finishedMs = Date.parse(finishedAt);
|
|
960
|
+
let nextMs = Math.max(Date.parse(schedule.nextRunAt), finishedMs);
|
|
961
|
+
do {
|
|
962
|
+
nextMs += schedule.intervalSeconds * 1000;
|
|
963
|
+
} while (nextMs <= finishedMs);
|
|
964
|
+
const nextRunAt = new Date(nextMs).toISOString();
|
|
965
|
+
this.db.query("UPDATE report_schedules SET last_run_at = ?, next_run_at = ?, updated_at = ? WHERE id = ?").run(finishedAt, nextRunAt, finishedAt, schedule.id);
|
|
966
|
+
}
|
|
768
967
|
ensureColumn(table, name, definition) {
|
|
769
968
|
const columns = this.db.query(`PRAGMA table_info(${table})`).all();
|
|
770
969
|
if (!columns.some((column) => column.name === name)) {
|
|
@@ -843,9 +1042,10 @@ function verifyBackupFile(backupPath) {
|
|
|
843
1042
|
const missingTables = REQUIRED_TABLES.filter((table) => !tableExists(db, table));
|
|
844
1043
|
const schemaVersion = missingTables.includes("schema_migrations") ? null : db.query("SELECT value FROM schema_migrations WHERE key = 'schema_version'").get()?.value ?? null;
|
|
845
1044
|
const currentOk = missingTables.length === 0 && schemaVersion === CURRENT_SCHEMA_VERSION;
|
|
846
|
-
const restorableV1 = schemaVersion === "1" && missingTables.every((table) => PROBE_TABLES.has(table));
|
|
1045
|
+
const restorableV1 = schemaVersion === "1" && missingTables.every((table) => PROBE_TABLES.has(table) || REPORT_AUDIT_TABLES.has(table));
|
|
1046
|
+
const restorableV2 = schemaVersion === "2" && missingTables.every((table) => REPORT_AUDIT_TABLES.has(table));
|
|
847
1047
|
return {
|
|
848
|
-
ok: integrity === "ok" && (currentOk || restorableV1),
|
|
1048
|
+
ok: integrity === "ok" && (currentOk || restorableV1 || restorableV2),
|
|
849
1049
|
backupPath,
|
|
850
1050
|
integrity,
|
|
851
1051
|
schemaVersion,
|
|
@@ -1009,6 +1209,175 @@ function normalizeScheduleSlot(value) {
|
|
|
1009
1209
|
rejectControlCharacters(slot, "Probe job scheduleSlot");
|
|
1010
1210
|
return slot;
|
|
1011
1211
|
}
|
|
1212
|
+
function normalizeReportScheduleInput(input) {
|
|
1213
|
+
const name = input.name?.trim();
|
|
1214
|
+
if (!name)
|
|
1215
|
+
throw new Error("Report schedule name is required");
|
|
1216
|
+
rejectControlCharacters(name, "Report schedule name");
|
|
1217
|
+
const intervalSeconds = boundedInteger(input.intervalSeconds, "intervalSeconds", MIN_INTERVAL_SECONDS, MAX_INTERVAL_SECONDS);
|
|
1218
|
+
const nextRunAt = input.nextRunAt ?? new Date().toISOString();
|
|
1219
|
+
assertIsoTimestamp(nextRunAt, "Report schedule nextRunAt");
|
|
1220
|
+
const enabled = normalizeEnabled(input.enabled);
|
|
1221
|
+
const subject = normalizeNullableBoundedText(input.subject, "Report schedule subject", 200);
|
|
1222
|
+
const channels = normalizeReportChannels(input.channels);
|
|
1223
|
+
return { name, intervalSeconds, nextRunAt, enabled, subject, channels };
|
|
1224
|
+
}
|
|
1225
|
+
function normalizeReportChannels(channels) {
|
|
1226
|
+
if (!channels || typeof channels !== "object")
|
|
1227
|
+
throw new Error("Report schedule channels are required");
|
|
1228
|
+
const normalized = {};
|
|
1229
|
+
if (channels.email !== undefined)
|
|
1230
|
+
normalized.email = normalizeChannelTarget(channels.email, "email", ["apiUrl", "from", "to", "subject", "providerId"]);
|
|
1231
|
+
if (channels.sms !== undefined)
|
|
1232
|
+
normalized.sms = normalizeChannelTarget(channels.sms, "sms", ["apiUrl", "from", "to"]);
|
|
1233
|
+
if (channels.logs !== undefined)
|
|
1234
|
+
normalized.logs = normalizeChannelTarget(channels.logs, "logs", ["apiUrl", "projectId", "environment", "service"]);
|
|
1235
|
+
if (!normalized.email && !normalized.sms && !normalized.logs) {
|
|
1236
|
+
throw new Error("Report schedule requires at least one channel");
|
|
1237
|
+
}
|
|
1238
|
+
return normalized;
|
|
1239
|
+
}
|
|
1240
|
+
function normalizeChannelTarget(value, channel, allowedKeys) {
|
|
1241
|
+
if (value === false || value == null)
|
|
1242
|
+
return false;
|
|
1243
|
+
if (value === true)
|
|
1244
|
+
return true;
|
|
1245
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1246
|
+
throw new Error(`Report schedule ${channel} channel must be true or an object`);
|
|
1247
|
+
}
|
|
1248
|
+
const record = value;
|
|
1249
|
+
const normalized = {};
|
|
1250
|
+
for (const [key, rawValue] of Object.entries(record)) {
|
|
1251
|
+
if (!allowedKeys.includes(key)) {
|
|
1252
|
+
if (/key|token|secret|password|credential|auth/i.test(key)) {
|
|
1253
|
+
throw new Error("Report schedules must not persist API keys or tokens; use environment variables or cloud channel refs");
|
|
1254
|
+
}
|
|
1255
|
+
throw new Error(`Unsupported report schedule ${channel} channel field: ${key}`);
|
|
1256
|
+
}
|
|
1257
|
+
if (rawValue === undefined || rawValue === null || rawValue === "")
|
|
1258
|
+
continue;
|
|
1259
|
+
if (key === "apiUrl" && Array.isArray(rawValue)) {
|
|
1260
|
+
throw new Error(`Report schedule ${channel}.${key} must be a string`);
|
|
1261
|
+
}
|
|
1262
|
+
if (Array.isArray(rawValue)) {
|
|
1263
|
+
const items = rawValue.map((item) => normalizeBoundedText(String(item), `Report schedule ${channel}.${key}`, 300));
|
|
1264
|
+
if (items.length > 0)
|
|
1265
|
+
normalized[key] = items;
|
|
1266
|
+
} else if (typeof rawValue === "string" || typeof rawValue === "number") {
|
|
1267
|
+
normalized[key] = key === "apiUrl" ? normalizeHttpIntegrationUrl(String(rawValue)) : normalizeBoundedText(String(rawValue), `Report schedule ${channel}.${key}`, 500);
|
|
1268
|
+
} else {
|
|
1269
|
+
throw new Error(`Report schedule ${channel}.${key} must be a string or string array`);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
return Object.keys(normalized).length > 0 ? normalized : true;
|
|
1273
|
+
}
|
|
1274
|
+
function normalizeHttpIntegrationUrl(value) {
|
|
1275
|
+
const parsed = new URL(value.trim());
|
|
1276
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
1277
|
+
throw new Error("Report schedule integration API URL must use http or https");
|
|
1278
|
+
}
|
|
1279
|
+
if (parsed.username || parsed.password) {
|
|
1280
|
+
throw new Error("Report schedule integration API URL must not include credentials");
|
|
1281
|
+
}
|
|
1282
|
+
for (const key of parsed.searchParams.keys()) {
|
|
1283
|
+
if (SECRET_URL_PARAM_PATTERN.test(key)) {
|
|
1284
|
+
throw new Error("Report schedule integration API URL must not include secret query parameters");
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
parsed.hash = "";
|
|
1288
|
+
return parsed.toString();
|
|
1289
|
+
}
|
|
1290
|
+
function normalizeReportDeliveries(deliveries) {
|
|
1291
|
+
return deliveries.map((delivery) => {
|
|
1292
|
+
if (delivery.channel !== "email" && delivery.channel !== "sms" && delivery.channel !== "logs") {
|
|
1293
|
+
throw new Error("Report delivery channel must be email, sms, or logs");
|
|
1294
|
+
}
|
|
1295
|
+
return {
|
|
1296
|
+
channel: delivery.channel,
|
|
1297
|
+
ok: Boolean(delivery.ok),
|
|
1298
|
+
status: delivery.status,
|
|
1299
|
+
id: delivery.id === undefined ? undefined : normalizeRedactedText(String(delivery.id), "Report delivery id", 300),
|
|
1300
|
+
error: delivery.error === undefined ? undefined : normalizeRedactedText(String(delivery.error), "Report delivery error", 1000)
|
|
1301
|
+
};
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
function normalizeAuditText(value, label, maxLength) {
|
|
1305
|
+
return normalizeBoundedText(value ?? "", label, maxLength);
|
|
1306
|
+
}
|
|
1307
|
+
function normalizeNullableAuditText(value, label, maxLength) {
|
|
1308
|
+
return normalizeNullableBoundedText(value, label, maxLength);
|
|
1309
|
+
}
|
|
1310
|
+
function normalizeNullableBoundedText(value, label, maxLength) {
|
|
1311
|
+
if (value == null)
|
|
1312
|
+
return null;
|
|
1313
|
+
const normalized = normalizeRedactedText(value, label, maxLength);
|
|
1314
|
+
return normalized || null;
|
|
1315
|
+
}
|
|
1316
|
+
function normalizeBoundedText(value, label, maxLength) {
|
|
1317
|
+
const normalized = value.trim();
|
|
1318
|
+
rejectControlCharacters(normalized, label);
|
|
1319
|
+
if (normalized.length > maxLength)
|
|
1320
|
+
throw new Error(`${label} is too long`);
|
|
1321
|
+
return normalized;
|
|
1322
|
+
}
|
|
1323
|
+
function normalizeNullableRedactedText(value, label, maxLength) {
|
|
1324
|
+
if (value == null)
|
|
1325
|
+
return null;
|
|
1326
|
+
const normalized = normalizeRedactedText(value, label, maxLength);
|
|
1327
|
+
return normalized || null;
|
|
1328
|
+
}
|
|
1329
|
+
function normalizeRedactedText(value, label, maxLength) {
|
|
1330
|
+
return normalizeBoundedText(redactSecretString(value), label, maxLength);
|
|
1331
|
+
}
|
|
1332
|
+
function normalizeAuditMetadata(value) {
|
|
1333
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1334
|
+
throw new Error("Audit metadata must be an object");
|
|
1335
|
+
}
|
|
1336
|
+
return redactAuditSecrets(JSON.parse(JSON.stringify(value)));
|
|
1337
|
+
}
|
|
1338
|
+
function redactAuditSecrets(value) {
|
|
1339
|
+
if (Array.isArray(value))
|
|
1340
|
+
return value.map(redactAuditSecrets);
|
|
1341
|
+
if (typeof value === "string")
|
|
1342
|
+
return redactSecretString(value);
|
|
1343
|
+
if (!value || typeof value !== "object")
|
|
1344
|
+
return value;
|
|
1345
|
+
const output = {};
|
|
1346
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
1347
|
+
output[key] = /key|token|secret|password|credential|auth/i.test(key) ? "[REDACTED]" : redactAuditSecrets(nested);
|
|
1348
|
+
}
|
|
1349
|
+
return output;
|
|
1350
|
+
}
|
|
1351
|
+
function redactSecretString(value) {
|
|
1352
|
+
let output = value.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]");
|
|
1353
|
+
output = output.replace(/https?:\/\/[^\s"'<>]+/gi, (match) => redactUrlString(match));
|
|
1354
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(output))
|
|
1355
|
+
return output;
|
|
1356
|
+
return redactUrlString(output);
|
|
1357
|
+
}
|
|
1358
|
+
function redactUrlString(value) {
|
|
1359
|
+
let trailing = "";
|
|
1360
|
+
let candidate = value;
|
|
1361
|
+
while (/[),.;\]]$/.test(candidate)) {
|
|
1362
|
+
trailing = `${candidate.slice(-1)}${trailing}`;
|
|
1363
|
+
candidate = candidate.slice(0, -1);
|
|
1364
|
+
}
|
|
1365
|
+
try {
|
|
1366
|
+
const parsed = new URL(candidate);
|
|
1367
|
+
if (parsed.username)
|
|
1368
|
+
parsed.username = "[REDACTED]";
|
|
1369
|
+
if (parsed.password)
|
|
1370
|
+
parsed.password = "[REDACTED]";
|
|
1371
|
+
for (const key of [...parsed.searchParams.keys()]) {
|
|
1372
|
+
if (SECRET_URL_PARAM_PATTERN.test(key))
|
|
1373
|
+
parsed.searchParams.set(key, "[REDACTED]");
|
|
1374
|
+
}
|
|
1375
|
+
parsed.hash = "";
|
|
1376
|
+
return `${parsed.toString()}${trailing}`;
|
|
1377
|
+
} catch {
|
|
1378
|
+
return value;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1012
1381
|
function assertIsoTimestamp(value, label) {
|
|
1013
1382
|
if (!Number.isFinite(Date.parse(value))) {
|
|
1014
1383
|
throw new Error(`${label} must be an ISO timestamp`);
|
|
@@ -1108,12 +1477,66 @@ function probeCheckJobFromRow(row) {
|
|
|
1108
1477
|
updatedAt: row.updated_at
|
|
1109
1478
|
};
|
|
1110
1479
|
}
|
|
1480
|
+
function reportScheduleFromRow(row) {
|
|
1481
|
+
return {
|
|
1482
|
+
id: row.id,
|
|
1483
|
+
name: row.name,
|
|
1484
|
+
enabled: Boolean(row.enabled),
|
|
1485
|
+
intervalSeconds: row.interval_seconds,
|
|
1486
|
+
nextRunAt: row.next_run_at,
|
|
1487
|
+
lastRunAt: row.last_run_at,
|
|
1488
|
+
subject: row.subject,
|
|
1489
|
+
channels: parseReportChannels(row.channels_json),
|
|
1490
|
+
createdAt: row.created_at,
|
|
1491
|
+
updatedAt: row.updated_at
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
function reportRunFromRow(row) {
|
|
1495
|
+
return {
|
|
1496
|
+
id: row.id,
|
|
1497
|
+
scheduleId: row.schedule_id,
|
|
1498
|
+
status: row.status,
|
|
1499
|
+
startedAt: row.started_at,
|
|
1500
|
+
finishedAt: row.finished_at,
|
|
1501
|
+
deliveries: parseReportDeliveries(row.deliveries_json),
|
|
1502
|
+
error: row.error,
|
|
1503
|
+
reportJson: parseRecord(row.report_json)
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
function auditEventFromRow(row) {
|
|
1507
|
+
return {
|
|
1508
|
+
id: row.id,
|
|
1509
|
+
action: row.action,
|
|
1510
|
+
resourceType: row.resource_type,
|
|
1511
|
+
resourceId: row.resource_id,
|
|
1512
|
+
message: row.message,
|
|
1513
|
+
metadata: parseRecord(row.metadata_json) ?? {},
|
|
1514
|
+
actor: row.actor,
|
|
1515
|
+
createdAt: row.created_at
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1111
1518
|
function parseEvidence(value) {
|
|
1112
1519
|
if (!value)
|
|
1113
1520
|
return null;
|
|
1114
1521
|
const parsed = parseJson(value);
|
|
1115
1522
|
return parsed && typeof parsed === "object" ? parsed : null;
|
|
1116
1523
|
}
|
|
1524
|
+
function parseReportChannels(value) {
|
|
1525
|
+
const parsed = parseJson(value);
|
|
1526
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
1527
|
+
return {};
|
|
1528
|
+
return parsed;
|
|
1529
|
+
}
|
|
1530
|
+
function parseReportDeliveries(value) {
|
|
1531
|
+
const parsed = parseJson(value);
|
|
1532
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
1533
|
+
}
|
|
1534
|
+
function parseRecord(value) {
|
|
1535
|
+
if (!value)
|
|
1536
|
+
return null;
|
|
1537
|
+
const parsed = parseJson(value);
|
|
1538
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
1539
|
+
}
|
|
1117
1540
|
function parseJson(value) {
|
|
1118
1541
|
try {
|
|
1119
1542
|
return JSON.parse(value);
|
package/dist/types.d.ts
CHANGED
|
@@ -122,6 +122,98 @@ export interface ProbeCheckJob {
|
|
|
122
122
|
createdAt: string;
|
|
123
123
|
updatedAt: string;
|
|
124
124
|
}
|
|
125
|
+
export type ReportScheduleStatus = "enabled" | "disabled";
|
|
126
|
+
export type ReportRunStatus = "success" | "failed";
|
|
127
|
+
export type ReportDeliveryChannel = "email" | "sms" | "logs";
|
|
128
|
+
export interface ReportDeliveryRecord {
|
|
129
|
+
channel: ReportDeliveryChannel;
|
|
130
|
+
ok: boolean;
|
|
131
|
+
status?: number;
|
|
132
|
+
id?: string;
|
|
133
|
+
error?: string;
|
|
134
|
+
}
|
|
135
|
+
export interface ReportEmailChannelConfig {
|
|
136
|
+
apiUrl?: string;
|
|
137
|
+
from?: string;
|
|
138
|
+
to?: string | string[];
|
|
139
|
+
subject?: string;
|
|
140
|
+
providerId?: string;
|
|
141
|
+
}
|
|
142
|
+
export interface ReportSmsChannelConfig {
|
|
143
|
+
apiUrl?: string;
|
|
144
|
+
from?: string;
|
|
145
|
+
to?: string | string[];
|
|
146
|
+
}
|
|
147
|
+
export interface ReportLogsChannelConfig {
|
|
148
|
+
apiUrl?: string;
|
|
149
|
+
projectId?: string;
|
|
150
|
+
environment?: string;
|
|
151
|
+
service?: string;
|
|
152
|
+
}
|
|
153
|
+
export interface ReportScheduleChannels {
|
|
154
|
+
email?: boolean | ReportEmailChannelConfig;
|
|
155
|
+
sms?: boolean | ReportSmsChannelConfig;
|
|
156
|
+
logs?: boolean | ReportLogsChannelConfig;
|
|
157
|
+
}
|
|
158
|
+
export interface ReportSchedule {
|
|
159
|
+
id: string;
|
|
160
|
+
name: string;
|
|
161
|
+
enabled: boolean;
|
|
162
|
+
intervalSeconds: number;
|
|
163
|
+
nextRunAt: string;
|
|
164
|
+
lastRunAt: string | null;
|
|
165
|
+
subject: string | null;
|
|
166
|
+
channels: ReportScheduleChannels;
|
|
167
|
+
createdAt: string;
|
|
168
|
+
updatedAt: string;
|
|
169
|
+
}
|
|
170
|
+
export interface CreateReportScheduleInput {
|
|
171
|
+
name: string;
|
|
172
|
+
intervalSeconds: number;
|
|
173
|
+
nextRunAt?: string;
|
|
174
|
+
enabled?: boolean;
|
|
175
|
+
subject?: string | null;
|
|
176
|
+
channels: ReportScheduleChannels;
|
|
177
|
+
}
|
|
178
|
+
export type UpdateReportScheduleInput = Partial<CreateReportScheduleInput>;
|
|
179
|
+
export interface ReportRun {
|
|
180
|
+
id: string;
|
|
181
|
+
scheduleId: string | null;
|
|
182
|
+
status: ReportRunStatus;
|
|
183
|
+
startedAt: string;
|
|
184
|
+
finishedAt: string;
|
|
185
|
+
deliveries: ReportDeliveryRecord[];
|
|
186
|
+
error: string | null;
|
|
187
|
+
reportJson: Record<string, unknown> | null;
|
|
188
|
+
}
|
|
189
|
+
export interface ListReportRunsOptions {
|
|
190
|
+
scheduleId?: string;
|
|
191
|
+
limit?: number;
|
|
192
|
+
}
|
|
193
|
+
export interface AuditEvent {
|
|
194
|
+
id: string;
|
|
195
|
+
action: string;
|
|
196
|
+
resourceType: string | null;
|
|
197
|
+
resourceId: string | null;
|
|
198
|
+
message: string | null;
|
|
199
|
+
metadata: Record<string, unknown>;
|
|
200
|
+
actor: string | null;
|
|
201
|
+
createdAt: string;
|
|
202
|
+
}
|
|
203
|
+
export interface RecordAuditEventInput {
|
|
204
|
+
action: string;
|
|
205
|
+
resourceType?: string | null;
|
|
206
|
+
resourceId?: string | null;
|
|
207
|
+
message?: string | null;
|
|
208
|
+
metadata?: Record<string, unknown>;
|
|
209
|
+
actor?: string | null;
|
|
210
|
+
createdAt?: string;
|
|
211
|
+
}
|
|
212
|
+
export interface ListAuditEventsOptions {
|
|
213
|
+
resourceType?: string;
|
|
214
|
+
resourceId?: string;
|
|
215
|
+
limit?: number;
|
|
216
|
+
}
|
|
125
217
|
export type CheckEvidence = BrowserPageEvidence;
|
|
126
218
|
export interface BrowserPageEvidence {
|
|
127
219
|
kind: "browser_page";
|