@openlup/platform 0.29.0 → 0.30.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.
Files changed (121) hide show
  1. package/CHANGELOG.md +150 -138
  2. package/README.md +80 -3
  3. package/dist/composition/nodeHost.d.ts +1 -1
  4. package/dist/composition/nodeHost.js +3 -2
  5. package/dist/lifecycle/appUpgrade.d.ts +52 -0
  6. package/dist/lifecycle/appUpgrade.js +180 -0
  7. package/dist/lifecycle/index.d.ts +2 -0
  8. package/dist/lifecycle/index.js +2 -0
  9. package/dist/lifecycle/postgresInstallQualification.d.ts +51 -0
  10. package/dist/lifecycle/postgresInstallQualification.js +74 -0
  11. package/dist/migrations/postgresRunner.d.ts +9 -0
  12. package/dist/migrations/postgresRunner.js +17 -10
  13. package/dist/operations/adminAlertsSchemas.d.ts +153 -0
  14. package/dist/operations/adminAlertsSchemas.js +69 -0
  15. package/dist/operations/adminAlertsView.d.ts +73 -0
  16. package/dist/operations/adminAlertsView.js +184 -0
  17. package/dist/operations/alertPagingPolicy.d.ts +15 -0
  18. package/dist/operations/alertPagingPolicy.js +43 -0
  19. package/dist/operations/alertThrottlePolicy.d.ts +3 -0
  20. package/dist/operations/alertThrottlePolicy.js +18 -0
  21. package/dist/operations/clients.d.ts +17 -0
  22. package/dist/operations/clients.js +32 -0
  23. package/dist/operations/contracts/admin.d.ts +93 -0
  24. package/dist/operations/contracts/admin.js +1 -0
  25. package/dist/operations/contracts/adminAlertsContracts.d.ts +40 -0
  26. package/dist/operations/contracts/adminAlertsContracts.js +1 -0
  27. package/dist/operations/contracts/alertHumanContext.d.ts +7 -0
  28. package/dist/operations/contracts/alertHumanContext.js +1 -0
  29. package/dist/operations/contracts/index.d.ts +6 -0
  30. package/dist/operations/contracts/index.js +6 -0
  31. package/dist/operations/contracts/observabilityContracts.d.ts +41 -0
  32. package/dist/operations/contracts/observabilityContracts.js +1 -0
  33. package/dist/operations/contracts/observabilityPorts.d.ts +18 -0
  34. package/dist/operations/contracts/observabilityPorts.js +1 -0
  35. package/dist/operations/contracts/ports.d.ts +19 -0
  36. package/dist/operations/contracts/ports.js +1 -0
  37. package/dist/operations/errors.d.ts +6 -0
  38. package/dist/operations/errors.js +6 -0
  39. package/dist/operations/index.d.ts +9 -0
  40. package/dist/operations/index.js +9 -0
  41. package/dist/operations/schemas.d.ts +138 -0
  42. package/dist/operations/schemas.js +104 -0
  43. package/dist/operations/server/adminAlertsHandlers.d.ts +16 -0
  44. package/dist/operations/server/adminAlertsHandlers.js +35 -0
  45. package/dist/operations/server/adminPipelineHandlers.d.ts +12 -0
  46. package/dist/operations/server/adminPipelineHandlers.js +48 -0
  47. package/dist/operations/server/adminSettingsHandlers.d.ts +14 -0
  48. package/dist/operations/server/adminSettingsHandlers.js +82 -0
  49. package/dist/operations/server/controlPlaneHandler.d.ts +7 -0
  50. package/dist/operations/server/controlPlaneHandler.js +54 -0
  51. package/dist/operations/server/controlPlaneStore.d.ts +12 -0
  52. package/dist/operations/server/controlPlaneStore.js +138 -0
  53. package/dist/operations/server/controlPlaneWatchdog.d.ts +7 -0
  54. package/dist/operations/server/controlPlaneWatchdog.js +20 -0
  55. package/dist/operations/server/index.d.ts +12 -0
  56. package/dist/operations/server/index.js +12 -0
  57. package/dist/operations/server/migrations.d.ts +14 -0
  58. package/dist/operations/server/migrations.js +7 -0
  59. package/dist/operations/server/platformWatchdogDecision.d.ts +34 -0
  60. package/dist/operations/server/platformWatchdogDecision.js +71 -0
  61. package/dist/operations/server/platformWatchdogDeliveryPolicy.d.ts +10 -0
  62. package/dist/operations/server/platformWatchdogDeliveryPolicy.js +53 -0
  63. package/dist/operations/server/platformWatchdogQueue.d.ts +11 -0
  64. package/dist/operations/server/platformWatchdogQueue.js +94 -0
  65. package/dist/operations/server/platformWatchdogService.d.ts +47 -0
  66. package/dist/operations/server/platformWatchdogService.js +105 -0
  67. package/dist/operations/validation.d.ts +2 -0
  68. package/dist/operations/validation.js +3 -0
  69. package/dist/portability/filesystemObjects.d.ts +12 -0
  70. package/dist/portability/filesystemObjects.js +191 -0
  71. package/dist/portability/index.d.ts +5 -0
  72. package/dist/portability/index.js +4 -0
  73. package/dist/portability/objects.d.ts +63 -0
  74. package/dist/portability/objects.js +138 -0
  75. package/dist/portability/postgresRows.d.ts +64 -0
  76. package/dist/portability/postgresRows.js +182 -0
  77. package/dist/scheduler/advisoryLock.js +20 -2
  78. package/dist/scheduler/nodeScheduler.d.ts +2 -2
  79. package/dist/scheduler/nodeScheduler.js +35 -6
  80. package/package.json +27 -2
  81. package/release-gates.json +73 -7
  82. package/sql/20260814010000_platform_control_plane.sql +286 -0
  83. package/src/composition/nodeHost.ts +4 -3
  84. package/src/lifecycle/appUpgrade.ts +186 -0
  85. package/src/lifecycle/index.ts +19 -0
  86. package/src/lifecycle/postgresInstallQualification.ts +125 -0
  87. package/src/migrations/postgresRunner.ts +24 -9
  88. package/src/operations/adminAlertsSchemas.ts +78 -0
  89. package/src/operations/adminAlertsView.ts +238 -0
  90. package/src/operations/alertPagingPolicy.ts +50 -0
  91. package/src/operations/alertThrottlePolicy.ts +22 -0
  92. package/src/operations/clients.ts +44 -0
  93. package/src/operations/contracts/admin.ts +24 -0
  94. package/src/operations/contracts/adminAlertsContracts.ts +13 -0
  95. package/src/operations/contracts/alertHumanContext.ts +17 -0
  96. package/src/operations/contracts/index.ts +6 -0
  97. package/src/operations/contracts/observabilityContracts.ts +40 -0
  98. package/src/operations/contracts/observabilityPorts.ts +30 -0
  99. package/src/operations/contracts/ports.ts +22 -0
  100. package/src/operations/errors.ts +6 -0
  101. package/src/operations/index.ts +9 -0
  102. package/src/operations/schemas.ts +122 -0
  103. package/src/operations/server/adminAlertsHandlers.ts +61 -0
  104. package/src/operations/server/adminPipelineHandlers.ts +73 -0
  105. package/src/operations/server/adminSettingsHandlers.ts +114 -0
  106. package/src/operations/server/controlPlaneHandler.ts +56 -0
  107. package/src/operations/server/controlPlaneStore.ts +150 -0
  108. package/src/operations/server/controlPlaneWatchdog.ts +28 -0
  109. package/src/operations/server/index.ts +12 -0
  110. package/src/operations/server/migrations.ts +7 -0
  111. package/src/operations/server/platformWatchdogDecision.ts +93 -0
  112. package/src/operations/server/platformWatchdogDeliveryPolicy.ts +92 -0
  113. package/src/operations/server/platformWatchdogQueue.ts +96 -0
  114. package/src/operations/server/platformWatchdogService.ts +209 -0
  115. package/src/operations/validation.ts +3 -0
  116. package/src/portability/filesystemObjects.ts +138 -0
  117. package/src/portability/index.ts +12 -0
  118. package/src/portability/objects.ts +160 -0
  119. package/src/portability/postgresRows.ts +169 -0
  120. package/src/scheduler/advisoryLock.ts +20 -5
  121. package/src/scheduler/nodeScheduler.ts +28 -8
@@ -0,0 +1,6 @@
1
+ export class PlatformControlPlaneConflictError extends Error {
2
+ name = "PlatformControlPlaneConflictError";
3
+ }
4
+ export class PlatformControlPlaneUnavailableError extends Error {
5
+ name = "PlatformControlPlaneUnavailableError";
6
+ }
@@ -0,0 +1,9 @@
1
+ /** Browser-safe operational contracts, validation and operator projections. */
2
+ export * from "./contracts/index.js";
3
+ export * from "./schemas.js";
4
+ export * from "./adminAlertsSchemas.js";
5
+ export * from "./adminAlertsView.js";
6
+ export * from "./alertPagingPolicy.js";
7
+ export * from "./alertThrottlePolicy.js";
8
+ export * from "./errors.js";
9
+ export * from "./clients.js";
@@ -0,0 +1,9 @@
1
+ /** Browser-safe operational contracts, validation and operator projections. */
2
+ export * from "./contracts/index.js";
3
+ export * from "./schemas.js";
4
+ export * from "./adminAlertsSchemas.js";
5
+ export * from "./adminAlertsView.js";
6
+ export * from "./alertPagingPolicy.js";
7
+ export * from "./alertThrottlePolicy.js";
8
+ export * from "./errors.js";
9
+ export * from "./clients.js";
@@ -0,0 +1,138 @@
1
+ import { z } from "./validation.js";
2
+ export declare const adminPipelineTesterSchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ status: z.ZodString;
5
+ email_sequence_step: z.ZodNullable<z.ZodNumber>;
6
+ email_sequence_paused: z.ZodNullable<z.ZodBoolean>;
7
+ }, z.core.$strip>;
8
+ export declare const adminPipelineEmailTemplateSchema: z.ZodObject<{
9
+ id: z.ZodString;
10
+ name: z.ZodString;
11
+ sequence_order: z.ZodNullable<z.ZodNumber>;
12
+ }, z.core.$strip>;
13
+ export declare const adminPipelineEmailEventSchema: z.ZodObject<{
14
+ event_type: z.ZodString;
15
+ }, z.core.$strip>;
16
+ export declare const adminPipelineReadRequestSchema: z.ZodObject<{}, z.core.$strict>;
17
+ export declare const adminPipelineReadResponseSchema: z.ZodObject<{
18
+ testers: z.ZodArray<z.ZodObject<{
19
+ id: z.ZodString;
20
+ status: z.ZodString;
21
+ email_sequence_step: z.ZodNullable<z.ZodNumber>;
22
+ email_sequence_paused: z.ZodNullable<z.ZodBoolean>;
23
+ }, z.core.$strip>>;
24
+ templates: z.ZodArray<z.ZodObject<{
25
+ id: z.ZodString;
26
+ name: z.ZodString;
27
+ sequence_order: z.ZodNullable<z.ZodNumber>;
28
+ }, z.core.$strip>>;
29
+ emailSendCount: z.ZodNumber;
30
+ emailEvents: z.ZodArray<z.ZodObject<{
31
+ event_type: z.ZodString;
32
+ }, z.core.$strip>>;
33
+ }, z.core.$strip>;
34
+ export declare const adminPipelineDhlTrackingRefreshRequestSchema: z.ZodObject<{}, z.core.$strict>;
35
+ export declare const adminPipelineDhlTrackingRefreshResponseSchema: z.ZodObject<{
36
+ checked: z.ZodNumber;
37
+ updated: z.ZodNumber;
38
+ }, z.core.$strip>;
39
+ export declare const adminSettingsReadRequestSchema: z.ZodObject<{}, z.core.$strict>;
40
+ export declare const adminSettingsAdminUserSchema: z.ZodObject<{
41
+ id: z.ZodString;
42
+ email: z.ZodNullable<z.ZodString>;
43
+ role: z.ZodNullable<z.ZodString>;
44
+ }, z.core.$strip>;
45
+ export declare const adminSettingsReadResponseSchema: z.ZodObject<{
46
+ settings: z.ZodRecord<z.ZodString, z.ZodUnknown>;
47
+ adminUsers: z.ZodArray<z.ZodObject<{
48
+ id: z.ZodString;
49
+ email: z.ZodNullable<z.ZodString>;
50
+ role: z.ZodNullable<z.ZodString>;
51
+ }, z.core.$strip>>;
52
+ }, z.core.$strip>;
53
+ export declare const adminSettingWriteValueSchema: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
54
+ export declare const adminSettingsUpdateRequestSchema: z.ZodObject<{
55
+ key: z.ZodString;
56
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>;
57
+ }, z.core.$strip>;
58
+ export declare const adminSettingsUpdateResponseSchema: z.ZodObject<{
59
+ key: z.ZodString;
60
+ saved: z.ZodLiteral<true>;
61
+ }, z.core.$strip>;
62
+ export declare const platformControlPlaneReadRequestSchema: z.ZodObject<{}, z.core.$strict>;
63
+ export declare const platformControlPlaneReadResponseSchema: z.ZodObject<{
64
+ controls: z.ZodArray<z.ZodObject<{
65
+ controlKey: z.ZodString;
66
+ enabled: z.ZodBoolean;
67
+ revision: z.ZodNumber;
68
+ updatedAt: z.ZodString;
69
+ }, z.core.$strip>>;
70
+ jobs: z.ZodArray<z.ZodObject<{
71
+ jobName: z.ZodString;
72
+ enabled: z.ZodBoolean;
73
+ activeDriver: z.ZodEnum<{
74
+ operator: "operator";
75
+ scheduler: "scheduler";
76
+ worker: "worker";
77
+ }>;
78
+ lastStatus: z.ZodNullable<z.ZodEnum<{
79
+ failed: "failed";
80
+ skipped: "skipped";
81
+ success: "success";
82
+ running: "running";
83
+ }>>;
84
+ lastStartedAt: z.ZodNullable<z.ZodString>;
85
+ lastFinishedAt: z.ZodNullable<z.ZodString>;
86
+ lastSuccessAt: z.ZodNullable<z.ZodString>;
87
+ }, z.core.$strip>>;
88
+ alerts: z.ZodArray<z.ZodObject<{
89
+ dedupeKey: z.ZodString;
90
+ severity: z.ZodEnum<{
91
+ p0: "p0";
92
+ p1: "p1";
93
+ p2: "p2";
94
+ p3: "p3";
95
+ }>;
96
+ status: z.ZodEnum<{
97
+ open: "open";
98
+ resolved: "resolved";
99
+ acknowledged: "acknowledged";
100
+ }>;
101
+ owner: z.ZodString;
102
+ title: z.ZodString;
103
+ firstSeenAt: z.ZodString;
104
+ lastSeenAt: z.ZodString;
105
+ resolvedAt: z.ZodNullable<z.ZodString>;
106
+ }, z.core.$strip>>;
107
+ }, z.core.$strip>;
108
+ export declare const platformControlPlaneMutationSchema: z.ZodUnion<readonly [z.ZodObject<{
109
+ action: z.ZodLiteral<"set-control">;
110
+ idempotencyKey: z.ZodString;
111
+ controlKey: z.ZodString;
112
+ enabled: z.ZodBoolean;
113
+ }, z.core.$strict>, z.ZodObject<{
114
+ action: z.ZodLiteral<"record-heartbeat">;
115
+ idempotencyKey: z.ZodString;
116
+ health: z.ZodEnum<{
117
+ failed: "failed";
118
+ degraded: "degraded";
119
+ healthy: "healthy";
120
+ }>;
121
+ firingCount: z.ZodNumber;
122
+ maxSeverity: z.ZodNullable<z.ZodEnum<{
123
+ p0: "p0";
124
+ p1: "p1";
125
+ p2: "p2";
126
+ p3: "p3";
127
+ }>>;
128
+ promotionReady: z.ZodBoolean;
129
+ }, z.core.$strict>]>;
130
+ export declare const platformControlPlaneMutationResponseSchema: z.ZodObject<{
131
+ action: z.ZodEnum<{
132
+ "set-control": "set-control";
133
+ "record-heartbeat": "record-heartbeat";
134
+ }>;
135
+ replayed: z.ZodBoolean;
136
+ revision: z.ZodNullable<z.ZodNumber>;
137
+ recordedAt: z.ZodString;
138
+ }, z.core.$strip>;
@@ -0,0 +1,104 @@
1
+ import { z } from "./validation.js";
2
+ export const adminPipelineTesterSchema = z.object({
3
+ id: z.string().min(1),
4
+ status: z.string().min(1),
5
+ email_sequence_step: z.number().int().min(0).nullable(),
6
+ email_sequence_paused: z.boolean().nullable(),
7
+ });
8
+ export const adminPipelineEmailTemplateSchema = z.object({
9
+ id: z.string().min(1),
10
+ name: z.string().min(1),
11
+ sequence_order: z.number().int().min(0).nullable(),
12
+ });
13
+ export const adminPipelineEmailEventSchema = z.object({
14
+ event_type: z.string().min(1),
15
+ });
16
+ export const adminPipelineReadRequestSchema = z.object({}).strict();
17
+ export const adminPipelineReadResponseSchema = z.object({
18
+ testers: z.array(adminPipelineTesterSchema),
19
+ templates: z.array(adminPipelineEmailTemplateSchema),
20
+ emailSendCount: z.number().int().min(0),
21
+ emailEvents: z.array(adminPipelineEmailEventSchema),
22
+ });
23
+ export const adminPipelineDhlTrackingRefreshRequestSchema = z.object({}).strict();
24
+ export const adminPipelineDhlTrackingRefreshResponseSchema = z.object({
25
+ checked: z.number().int().min(0),
26
+ updated: z.number().int().min(0),
27
+ });
28
+ export const adminSettingsReadRequestSchema = z.object({}).strict();
29
+ export const adminSettingsAdminUserSchema = z.object({
30
+ id: z.string().min(1),
31
+ email: z.string().nullable(),
32
+ role: z.string().nullable(),
33
+ });
34
+ export const adminSettingsReadResponseSchema = z.object({
35
+ settings: z.record(z.string(), z.unknown()),
36
+ adminUsers: z.array(adminSettingsAdminUserSchema),
37
+ });
38
+ export const adminSettingWriteValueSchema = z.union([
39
+ z.string(),
40
+ z.number(),
41
+ z.boolean(),
42
+ z.null(),
43
+ ]);
44
+ export const adminSettingsUpdateRequestSchema = z.object({
45
+ key: z.string().trim().min(1),
46
+ value: adminSettingWriteValueSchema,
47
+ });
48
+ export const adminSettingsUpdateResponseSchema = z.object({
49
+ key: z.string().min(1),
50
+ saved: z.literal(true),
51
+ });
52
+ const platformControlKeySchema = z.string().trim().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/);
53
+ const platformOperationKeySchema = z.string().trim().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/);
54
+ const platformTimestampSchema = z.string().datetime({ offset: true });
55
+ export const platformControlPlaneReadRequestSchema = z.object({}).strict();
56
+ export const platformControlPlaneReadResponseSchema = z.object({
57
+ controls: z.array(z.object({
58
+ controlKey: platformControlKeySchema,
59
+ enabled: z.boolean(),
60
+ revision: z.number().int().min(1),
61
+ updatedAt: platformTimestampSchema,
62
+ })),
63
+ jobs: z.array(z.object({
64
+ jobName: z.string().min(1),
65
+ enabled: z.boolean(),
66
+ activeDriver: z.enum(["worker", "scheduler", "operator"]),
67
+ lastStatus: z.enum(["running", "success", "failed", "skipped"]).nullable(),
68
+ lastStartedAt: platformTimestampSchema.nullable(),
69
+ lastFinishedAt: platformTimestampSchema.nullable(),
70
+ lastSuccessAt: platformTimestampSchema.nullable(),
71
+ })),
72
+ alerts: z.array(z.object({
73
+ dedupeKey: z.string().min(1),
74
+ severity: z.enum(["p0", "p1", "p2", "p3"]),
75
+ status: z.enum(["open", "acknowledged", "resolved"]),
76
+ owner: z.string().min(1),
77
+ title: z.string().min(1),
78
+ firstSeenAt: platformTimestampSchema,
79
+ lastSeenAt: platformTimestampSchema,
80
+ resolvedAt: platformTimestampSchema.nullable(),
81
+ })),
82
+ });
83
+ export const platformControlPlaneMutationSchema = z.union([
84
+ z.object({
85
+ action: z.literal("set-control"),
86
+ idempotencyKey: platformOperationKeySchema,
87
+ controlKey: platformControlKeySchema,
88
+ enabled: z.boolean(),
89
+ }).strict(),
90
+ z.object({
91
+ action: z.literal("record-heartbeat"),
92
+ idempotencyKey: platformOperationKeySchema,
93
+ health: z.enum(["healthy", "degraded", "failed"]),
94
+ firingCount: z.number().int().min(0),
95
+ maxSeverity: z.enum(["p0", "p1", "p2", "p3"]).nullable(),
96
+ promotionReady: z.boolean(),
97
+ }).strict(),
98
+ ]);
99
+ export const platformControlPlaneMutationResponseSchema = z.object({
100
+ action: z.enum(["set-control", "record-heartbeat"]),
101
+ replayed: z.boolean(),
102
+ revision: z.number().int().min(1).nullable(),
103
+ recordedAt: platformTimestampSchema,
104
+ });
@@ -0,0 +1,16 @@
1
+ import { type BffResponderOptions } from "../../bff/index.js";
2
+ import type { HttpRequest, HttpResponse } from "../../http/index.js";
3
+ import type { PlatformAlertLedgerRow, WatchdogHeartbeatRow } from "../index.js";
4
+ import type { OperationsAuthorizationResult } from "../contracts/index.js";
5
+ type AuthorizeAdmin = () => Promise<OperationsAuthorizationResult>;
6
+ export interface AdminAlertsReadPort {
7
+ listLiveAlerts(): Promise<PlatformAlertLedgerRow[]>;
8
+ readWatchdogHeartbeat(): Promise<WatchdogHeartbeatRow>;
9
+ }
10
+ export interface AdminAlertsOverviewDeps extends BffResponderOptions {
11
+ readPort: AdminAlertsReadPort;
12
+ authorizeAdmin: AuthorizeAdmin;
13
+ now?: () => Date;
14
+ }
15
+ export declare function createAdminAlertsOverviewHandler({ readPort, authorizeAdmin, now, responseRequestId, }: AdminAlertsOverviewDeps): (req: HttpRequest, res: HttpResponse) => Promise<void>;
16
+ export {};
@@ -0,0 +1,35 @@
1
+ import { createBffResponder } from "../../bff/index.js";
2
+ import { adminAlertsOverviewResponseSchema } from "../index.js";
3
+ import { buildAlertsOverview } from "../index.js";
4
+ export function createAdminAlertsOverviewHandler({ readPort, authorizeAdmin, now = () => new Date(), responseRequestId, }) {
5
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = createBffResponder({ responseRequestId });
6
+ return async function handler(req, res) {
7
+ if (req.method !== "GET") {
8
+ sendMethodNotAllowed(res, ["GET"]);
9
+ return;
10
+ }
11
+ const authorization = await authorizeAdmin();
12
+ if (authorization.ok === false) {
13
+ sendBffError(res, authorization.code, authorization.message);
14
+ return;
15
+ }
16
+ try {
17
+ // Heartbeat and alerts are read together: an empty alert list is only
18
+ // "healthy" if the watchdog actually reported recently.
19
+ const [alerts, heartbeat] = await Promise.all([
20
+ readPort.listLiveAlerts(),
21
+ readPort.readWatchdogHeartbeat(),
22
+ ]);
23
+ const overview = buildAlertsOverview(alerts, heartbeat, now());
24
+ const response = adminAlertsOverviewResponseSchema.safeParse(overview);
25
+ if (!response.success) {
26
+ sendBffError(res, "INVALID_RESPONSE", "Admin alerts overview returned invalid response");
27
+ return;
28
+ }
29
+ sendBffSuccess(res, response.data);
30
+ }
31
+ catch {
32
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin alerts overview failed");
33
+ }
34
+ };
35
+ }
@@ -0,0 +1,12 @@
1
+ import { type BffResponderOptions } from "../../bff/index.js";
2
+ import type { HttpRequest, HttpResponse } from "../../http/index.js";
3
+ import type { AdminPipelineReadPort } from "../contracts/index.js";
4
+ import type { OperationsAuthorizationResult } from "../contracts/index.js";
5
+ interface AdminPipelineBaseDeps extends BffResponderOptions {
6
+ authorizeAdmin: (req: HttpRequest) => Promise<OperationsAuthorizationResult>;
7
+ }
8
+ export interface AdminPipelineReadDeps extends AdminPipelineBaseDeps {
9
+ pipelinePort: Pick<AdminPipelineReadPort, "readPipeline">;
10
+ }
11
+ export declare function createAdminPipelineReadHandler({ pipelinePort, authorizeAdmin, responseRequestId, }: AdminPipelineReadDeps): (req: HttpRequest, res: HttpResponse) => Promise<void>;
12
+ export {};
@@ -0,0 +1,48 @@
1
+ import { createBffResponder } from "../../bff/index.js";
2
+ import { adminPipelineReadRequestSchema, adminPipelineReadResponseSchema, } from "../index.js";
3
+ export function createAdminPipelineReadHandler({ pipelinePort, authorizeAdmin, responseRequestId, }) {
4
+ const responder = createBffResponder({ responseRequestId });
5
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = responder;
6
+ return async function handler(req, res) {
7
+ if (req.method !== "GET") {
8
+ sendMethodNotAllowed(res, ["GET"]);
9
+ return;
10
+ }
11
+ const authorization = await readAuthorization(req, res, authorizeAdmin, responder);
12
+ if (!authorization)
13
+ return;
14
+ const request = adminPipelineReadRequestSchema.safeParse(req.query);
15
+ if (!request.success) {
16
+ sendBffError(res, "BAD_REQUEST", "Invalid admin pipeline read request", {
17
+ details: request.error.flatten(),
18
+ });
19
+ return;
20
+ }
21
+ try {
22
+ const result = await pipelinePort.readPipeline(request.data);
23
+ const response = adminPipelineReadResponseSchema.safeParse(result);
24
+ if (!response.success) {
25
+ sendBffError(res, "INVALID_RESPONSE", "Admin pipeline read returned invalid response");
26
+ return;
27
+ }
28
+ sendBffSuccess(res, response.data);
29
+ }
30
+ catch {
31
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin pipeline read failed");
32
+ }
33
+ };
34
+ }
35
+ async function readAuthorization(req, res, authorizeAdmin, { sendBffError }) {
36
+ try {
37
+ const authorization = await authorizeAdmin(req);
38
+ if (authorization.ok === false) {
39
+ sendBffError(res, authorization.code, authorization.message);
40
+ return null;
41
+ }
42
+ return true;
43
+ }
44
+ catch {
45
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin authorization failed");
46
+ return null;
47
+ }
48
+ }
@@ -0,0 +1,14 @@
1
+ import { type BffResponderOptions } from "../../bff/index.js";
2
+ import type { HttpRequest, HttpResponse } from "../../http/index.js";
3
+ import type { AdminSettingsOperationsPort } from "../contracts/index.js";
4
+ import type { OperationsAuthorizationResult } from "../contracts/index.js";
5
+ export interface AdminSettingsReadDeps extends BffResponderOptions {
6
+ settingsPort: Pick<AdminSettingsOperationsPort, "readSettings">;
7
+ authorizeAdmin: (req: HttpRequest) => Promise<OperationsAuthorizationResult>;
8
+ }
9
+ export interface AdminSettingsUpdateDeps extends BffResponderOptions {
10
+ settingsPort: Pick<AdminSettingsOperationsPort, "updateSetting">;
11
+ authorizeAdmin: (req: HttpRequest) => Promise<OperationsAuthorizationResult>;
12
+ }
13
+ export declare function createAdminSettingsReadHandler({ settingsPort, authorizeAdmin, responseRequestId, }: AdminSettingsReadDeps): (req: HttpRequest, res: HttpResponse) => Promise<void>;
14
+ export declare function createAdminSettingsUpdateHandler({ settingsPort, authorizeAdmin, responseRequestId, }: AdminSettingsUpdateDeps): (req: HttpRequest, res: HttpResponse) => Promise<void>;
@@ -0,0 +1,82 @@
1
+ import { createBffResponder } from "../../bff/index.js";
2
+ import { adminSettingsReadRequestSchema, adminSettingsReadResponseSchema, adminSettingsUpdateRequestSchema, adminSettingsUpdateResponseSchema, } from "../index.js";
3
+ export function createAdminSettingsReadHandler({ settingsPort, authorizeAdmin, responseRequestId, }) {
4
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = createBffResponder({ responseRequestId });
5
+ return async function handler(req, res) {
6
+ if (req.method !== "GET") {
7
+ sendMethodNotAllowed(res, ["GET"]);
8
+ return;
9
+ }
10
+ let authorization;
11
+ try {
12
+ authorization = await authorizeAdmin(req);
13
+ }
14
+ catch {
15
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin authorization failed");
16
+ return;
17
+ }
18
+ if (authorization.ok === false) {
19
+ sendBffError(res, authorization.code, authorization.message);
20
+ return;
21
+ }
22
+ const request = adminSettingsReadRequestSchema.safeParse(req.query);
23
+ if (!request.success) {
24
+ sendBffError(res, "BAD_REQUEST", "Invalid admin settings read request", {
25
+ details: request.error.flatten(),
26
+ });
27
+ return;
28
+ }
29
+ try {
30
+ const result = await settingsPort.readSettings(request.data);
31
+ const response = adminSettingsReadResponseSchema.safeParse(result);
32
+ if (!response.success) {
33
+ sendBffError(res, "INVALID_RESPONSE", "Admin settings read returned invalid response");
34
+ return;
35
+ }
36
+ sendBffSuccess(res, response.data);
37
+ }
38
+ catch {
39
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin settings read failed");
40
+ }
41
+ };
42
+ }
43
+ export function createAdminSettingsUpdateHandler({ settingsPort, authorizeAdmin, responseRequestId, }) {
44
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = createBffResponder({ responseRequestId });
45
+ return async function handler(req, res) {
46
+ if (req.method !== "POST") {
47
+ sendMethodNotAllowed(res, ["POST"]);
48
+ return;
49
+ }
50
+ let authorization;
51
+ try {
52
+ authorization = await authorizeAdmin(req);
53
+ }
54
+ catch {
55
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin authorization failed");
56
+ return;
57
+ }
58
+ if (authorization.ok === false) {
59
+ sendBffError(res, authorization.code, authorization.message);
60
+ return;
61
+ }
62
+ const request = adminSettingsUpdateRequestSchema.safeParse(req.body);
63
+ if (!request.success) {
64
+ sendBffError(res, "BAD_REQUEST", "Invalid admin settings update request", {
65
+ details: request.error.flatten(),
66
+ });
67
+ return;
68
+ }
69
+ try {
70
+ const result = await settingsPort.updateSetting(request.data);
71
+ const response = adminSettingsUpdateResponseSchema.safeParse(result);
72
+ if (!response.success) {
73
+ sendBffError(res, "INVALID_RESPONSE", "Admin settings update returned invalid response");
74
+ return;
75
+ }
76
+ sendBffSuccess(res, response.data);
77
+ }
78
+ catch {
79
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Admin settings update failed");
80
+ }
81
+ };
82
+ }
@@ -0,0 +1,7 @@
1
+ import { type BffResponderOptions } from "../../bff/index.js";
2
+ import type { HttpRequest, HttpResponse } from "../../http/index.js";
3
+ import type { PlatformControlPlanePort } from "../contracts/index.js";
4
+ export declare function createPlatformControlPlaneHandler(deps: {
5
+ port: PlatformControlPlanePort;
6
+ authorizeAdmin: () => Promise<boolean>;
7
+ } & BffResponderOptions): (req: HttpRequest, res: HttpResponse) => Promise<void>;
@@ -0,0 +1,54 @@
1
+ import { createBffResponder } from "../../bff/index.js";
2
+ import { platformControlPlaneMutationResponseSchema, platformControlPlaneMutationSchema, platformControlPlaneReadResponseSchema } from "../index.js";
3
+ import { PlatformControlPlaneConflictError } from "../index.js";
4
+ export function createPlatformControlPlaneHandler(deps) {
5
+ const { sendBffError, sendBffSuccess, sendMethodNotAllowed } = createBffResponder(deps);
6
+ return async (req, res) => {
7
+ if (!(await deps.authorizeAdmin())) {
8
+ sendBffError(res, "UNAUTHORIZED", "Admin session required");
9
+ return;
10
+ }
11
+ try {
12
+ if (req.method === "GET") {
13
+ const parsed = platformControlPlaneReadResponseSchema.safeParse(await deps.port.readControlPlane({}));
14
+ if (!parsed.success)
15
+ return invalidResponse(res, sendBffError);
16
+ sendBffSuccess(res, parsed.data);
17
+ return;
18
+ }
19
+ if (req.method === "POST") {
20
+ const parsed = platformControlPlaneMutationSchema.safeParse(body(req.body));
21
+ if (!parsed.success) {
22
+ sendBffError(res, "BAD_REQUEST", "Invalid platform control-plane request");
23
+ return;
24
+ }
25
+ const response = platformControlPlaneMutationResponseSchema.safeParse(await deps.port.mutateControlPlane(parsed.data));
26
+ if (!response.success)
27
+ return invalidResponse(res, sendBffError);
28
+ sendBffSuccess(res, response.data);
29
+ return;
30
+ }
31
+ sendMethodNotAllowed(res, ["GET", "POST"]);
32
+ }
33
+ catch (error) {
34
+ if (error instanceof PlatformControlPlaneConflictError) {
35
+ sendBffError(res, "CONFLICT", error.message);
36
+ return;
37
+ }
38
+ sendBffError(res, "UPSTREAM_UNAVAILABLE", "Platform control plane unavailable");
39
+ }
40
+ };
41
+ }
42
+ function body(value) {
43
+ if (typeof value !== "string")
44
+ return value;
45
+ try {
46
+ return JSON.parse(value);
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ }
52
+ function invalidResponse(res, sendBffError) {
53
+ sendBffError(res, "INVALID_RESPONSE", "Platform control plane returned invalid response");
54
+ }
@@ -0,0 +1,12 @@
1
+ import { type PlatformControlPlanePort } from "../contracts/index.js";
2
+ import type { PgQueryExecutor } from "../../data/index.js";
3
+ export declare const platformControlPlaneSql: {
4
+ readonly active: string;
5
+ readonly setControl: string;
6
+ readonly heartbeat: string;
7
+ readonly controls: string;
8
+ readonly jobs: string;
9
+ readonly alerts: string;
10
+ };
11
+ export declare function createPlatformControlPlanePort(client: PgQueryExecutor, operatorId: string): PlatformControlPlanePort;
12
+ export declare function isPlatformControlOperatorActive(client: PgQueryExecutor, principalId: string): Promise<boolean>;