From 117d146294f5a8b415e77a2f7f99c5d90a988ef1 Mon Sep 17 00:00:00 2001 From: Thibault Le Ouay Date: Thu, 16 Jul 2026 11:23:33 +0200 Subject: [PATCH] api: new endpoint for page component (#2384) --- .../status-page/__tests__/status-page.test.ts | 90 ++++ .../routes/rpc/handlers/status-page/index.ts | 15 + apps/server/static/openapi.yaml | 73 +++ .../openstatus/status_page/v1/service.proto | 20 + packages/proto/gen/openapi.yaml | 73 +++ .../openstatus/status_page/v1/service_pb.ts | 112 ++-- packages/proto/plan/PLAN.md | 497 ++++++++---------- 7 files changed, 586 insertions(+), 294 deletions(-) diff --git a/apps/server/src/routes/rpc/handlers/status-page/__tests__/status-page.test.ts b/apps/server/src/routes/rpc/handlers/status-page/__tests__/status-page.test.ts index f24c800a..400d8fab 100644 --- a/apps/server/src/routes/rpc/handlers/status-page/__tests__/status-page.test.ts +++ b/apps/server/src/routes/rpc/handlers/status-page/__tests__/status-page.test.ts @@ -543,6 +543,96 @@ describe("StatusPageService.GetStatusPage", () => { }); }); +describe("StatusPageService.GetPageComponent", () => { + test("returns component by ID", async () => { + const res = await connectRequest( + "GetPageComponent", + { id: String(testComponentId) }, + { "x-openstatus-key": "1" }, + ); + + expect(res.status).toBe(200); + + const data = await res.json(); + expect(data).toHaveProperty("component"); + expect(data.component.id).toBe(String(testComponentId)); + expect(data.component.name).toBe(`${TEST_PREFIX}-component`); + expect(data.component.pageId).toBe(String(testPageId)); + expect(data.component.type).toBe("PAGE_COMPONENT_TYPE_STATIC"); + expect(data.component.monitorId).toBe(""); + }); + + test("returns 401 when no auth key provided", async () => { + const res = await connectRequest("GetPageComponent", { + id: String(testComponentId), + }); + + expect(res.status).toBe(401); + }); + + test("returns 404 for non-existent component", async () => { + const res = await connectRequest( + "GetPageComponent", + { id: "99999" }, + { "x-openstatus-key": "1" }, + ); + + expect(res.status).toBe(404); + }); + + test("returns error when ID is empty", async () => { + const res = await connectRequest( + "GetPageComponent", + { id: "" }, + { "x-openstatus-key": "1" }, + ); + + expect(res.status).toBe(400); + }); + + test("returns 404 for component in different workspace", async () => { + const otherPage = await db + .insert(page) + .values({ + workspaceId: 2, + title: `${TEST_PREFIX}-other-workspace-component`, + slug: `${TEST_PREFIX}-other-workspace-component-slug`, + description: "Other workspace page", + customDomain: "", + }) + .returning() + .get(); + + const otherComponent = await db + .insert(pageComponent) + .values({ + workspaceId: 2, + pageId: otherPage.id, + type: "static", + name: `${TEST_PREFIX}-other-workspace-component`, + description: "Other workspace component", + order: 100, + }) + .returning() + .get(); + + try { + const res = await connectRequest( + "GetPageComponent", + { id: String(otherComponent.id) }, + { "x-openstatus-key": "1" }, + ); + + expect(res.status).toBe(404); + } finally { + await db + .delete(pageComponent) + .where(eq(pageComponent.id, otherComponent.id)); + await db.delete(page).where(eq(page.id, otherPage.id)); + } + }); +}); + describe("StatusPageService.ListStatusPages", () => { test("returns status pages for authenticated workspace", async () => { const res = await connectRequest( diff --git a/apps/server/src/routes/rpc/handlers/status-page/index.ts b/apps/server/src/routes/rpc/handlers/status-page/index.ts index 3122f0e8..f83ae4e3 100644 --- a/apps/server/src/routes/rpc/handlers/status-page/index.ts +++ b/apps/server/src/routes/rpc/handlers/status-page/index.ts @@ -1183,6 +1183,21 @@ export const statusPageServiceImpl: ServiceImpl = { }; }, + async getPageComponent(req, ctx) { + try { + const rpcCtx = getRpcContext(ctx); + const id = req.id?.trim(); + if (!id) throw pageComponentNotFoundError(req.id); + + const component = await getComponentById(Number(id), rpcCtx.workspace.id); + if (!component) throw pageComponentNotFoundError(id); + + return { component: dbComponentToProto(component) }; + } catch (err) { + toConnectError(err); + } + }, + // ========================================================================== // Component Groups // ========================================================================== diff --git a/apps/server/static/openapi.yaml b/apps/server/static/openapi.yaml index c5788f8d..9ad73d88 100644 --- a/apps/server/static/openapi.yaml +++ b/apps/server/static/openapi.yaml @@ -2955,6 +2955,27 @@ components: title: GetPageComponentDailySummaryResponse additionalProperties: false description: GetPageComponentDailySummaryResponse is the response containing per-component summaries. + openstatus.status_page.v1.GetPageComponentRequest: + type: object + properties: + id: + type: string + title: id + minLength: 1 + description: ID of the component to retrieve (required). + title: GetPageComponentRequest + additionalProperties: false + description: GetPageComponentRequest is the request to fetch a single component by ID. + openstatus.status_page.v1.GetPageComponentResponse: + type: object + properties: + component: + title: component + description: The requested component. + $ref: '#/components/schemas/openstatus.status_page.v1.PageComponent' + title: GetPageComponentResponse + additionalProperties: false + description: GetPageComponentResponse is the response containing the component. openstatus.status_page.v1.GetStatusPageContentRequest: type: object oneOf: @@ -5613,6 +5634,58 @@ paths: application/json: schema: $ref: '#/components/schemas/openstatus.status_page.v1.GetOverallStatusResponse' + /rpc/openstatus.status_page.v1.StatusPageService/GetPageComponent: + get: + tags: + - StatusPageService + summary: GetPageComponent + description: Returns a single status-page component by its ID, scoped to the authenticated workspace. Use this to resolve a component id to its name (and other fields) instead of fetching the whole page via GetStatusPageContent and filtering .components. + operationId: StatusPageService_GetPageComponent.get + parameters: + - name: message + in: query + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentRequest' + responses: + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentResponse' + post: + tags: + - StatusPageService + summary: GetPageComponent + description: Returns a single status-page component by its ID, scoped to the authenticated workspace. Use this to resolve a component id to its name (and other fields) instead of fetching the whole page via GetStatusPageContent and filtering .components. + operationId: StatusPageService_GetPageComponent + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentRequest' + required: true + responses: + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentResponse' /rpc/openstatus.status_page.v1.StatusPageService/GetPageComponentDailySummary: get: tags: diff --git a/packages/proto/api/openstatus/status_page/v1/service.proto b/packages/proto/api/openstatus/status_page/v1/service.proto index 985521e0..be90bb7e 100644 --- a/packages/proto/api/openstatus/status_page/v1/service.proto +++ b/packages/proto/api/openstatus/status_page/v1/service.proto @@ -46,6 +46,14 @@ service StatusPageService { // UpdateComponent updates an existing component. rpc UpdateComponent(UpdateComponentRequest) returns (UpdateComponentResponse); + // GetPageComponent retrieves a single component by ID. + rpc GetPageComponent(GetPageComponentRequest) returns (GetPageComponentResponse) { + option idempotency_level = NO_SIDE_EFFECTS; + option (gnostic.openapi.v3.operation) = { + description: "Returns a single status-page component by its ID, scoped to the authenticated workspace. Use this to resolve a component id to its name (and other fields) instead of fetching the whole page via GetStatusPageContent and filtering .components." + }; + } + // CreateComponentGroup creates a new component group. rpc CreateComponentGroup(CreateComponentGroupRequest) returns (CreateComponentGroupResponse); @@ -421,6 +429,18 @@ message UpdateComponentResponse { PageComponent component = 1; } +// GetPageComponentRequest is the request to fetch a single component by ID. +message GetPageComponentRequest { + // ID of the component to retrieve (required). + string id = 1 [(buf.validate.field).string.min_len = 1]; +} + +// GetPageComponentResponse is the response containing the component. +message GetPageComponentResponse { + // The requested component. + PageComponent component = 1; +} + // ============================================================================= // Component Group Messages // ============================================================================= diff --git a/packages/proto/gen/openapi.yaml b/packages/proto/gen/openapi.yaml index c5788f8d..9ad73d88 100644 --- a/packages/proto/gen/openapi.yaml +++ b/packages/proto/gen/openapi.yaml @@ -2955,6 +2955,27 @@ components: title: GetPageComponentDailySummaryResponse additionalProperties: false description: GetPageComponentDailySummaryResponse is the response containing per-component summaries. + openstatus.status_page.v1.GetPageComponentRequest: + type: object + properties: + id: + type: string + title: id + minLength: 1 + description: ID of the component to retrieve (required). + title: GetPageComponentRequest + additionalProperties: false + description: GetPageComponentRequest is the request to fetch a single component by ID. + openstatus.status_page.v1.GetPageComponentResponse: + type: object + properties: + component: + title: component + description: The requested component. + $ref: '#/components/schemas/openstatus.status_page.v1.PageComponent' + title: GetPageComponentResponse + additionalProperties: false + description: GetPageComponentResponse is the response containing the component. openstatus.status_page.v1.GetStatusPageContentRequest: type: object oneOf: @@ -5613,6 +5634,58 @@ paths: application/json: schema: $ref: '#/components/schemas/openstatus.status_page.v1.GetOverallStatusResponse' + /rpc/openstatus.status_page.v1.StatusPageService/GetPageComponent: + get: + tags: + - StatusPageService + summary: GetPageComponent + description: Returns a single status-page component by its ID, scoped to the authenticated workspace. Use this to resolve a component id to its name (and other fields) instead of fetching the whole page via GetStatusPageContent and filtering .components. + operationId: StatusPageService_GetPageComponent.get + parameters: + - name: message + in: query + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentRequest' + responses: + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentResponse' + post: + tags: + - StatusPageService + summary: GetPageComponent + description: Returns a single status-page component by its ID, scoped to the authenticated workspace. Use this to resolve a component id to its name (and other fields) instead of fetching the whole page via GetStatusPageContent and filtering .components. + operationId: StatusPageService_GetPageComponent + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentRequest' + required: true + responses: + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentResponse' /rpc/openstatus.status_page.v1.StatusPageService/GetPageComponentDailySummary: get: tags: diff --git a/packages/proto/gen/ts/openstatus/status_page/v1/service_pb.ts b/packages/proto/gen/ts/openstatus/status_page/v1/service_pb.ts index 16ca7846..583b1369 100644 --- a/packages/proto/gen/ts/openstatus/status_page/v1/service_pb.ts +++ b/packages/proto/gen/ts/openstatus/status_page/v1/service_pb.ts @@ -22,7 +22,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file openstatus/status_page/v1/service.proto. */ export const file_openstatus_status_page_v1_service: GenFile = /*@__PURE__*/ - fileDesc("CidvcGVuc3RhdHVzL3N0YXR1c19wYWdlL3YxL3NlcnZpY2UucHJvdG8SGW9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEivgcKF0NyZWF0ZVN0YXR1c1BhZ2VSZXF1ZXN0EjAKBXRpdGxlGAEgASgJQiG6RxQ6EhIQQWNtZSBDb3JwIFN0YXR1c7pIB3IFEAEYgAISIgoLZGVzY3JpcHRpb24YAiABKAlCCLpIBXIDGIAISACIAQESSQoEc2x1ZxgDIAEoCUI7ukcSOhASDm15LXN0YXR1cy1wYWdlukgjciEQARiAAjIaXlthLXowLTldKyg/Oi1bYS16MC05XSspKiQSOQoMaG9tZXBhZ2VfdXJsGAQgASgJQh66Rxs6GRIXaHR0cHM6Ly93d3cuZXhhbXBsZS5jb21IAYgBARIYCgtjb250YWN0X3VybBgFIAEoCUgCiAEBEj4KDmRlZmF1bHRfbG9jYWxlGAYgASgOMiEub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5Mb2NhbGVIA4gBARIyCgdsb2NhbGVzGAcgAygOMiEub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5Mb2NhbGUSGwoEaWNvbhgIIAEoCUIIukgFcgMYgAhIBIgBARIkCg1jdXN0b21fZG9tYWluGAkgASgJQgi6SAVyAxiAAkgFiAEBEjgKBXRoZW1lGAogASgOMiQub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5QYWdlVGhlbWVIBogBARJDCgthY2Nlc3NfdHlwZRgLIAEoDjIpLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuUGFnZUFjY2Vzc1R5cGVIB4gBARIhCghwYXNzd29yZBgMIAEoCUIKukgHcgUQARiAAkgIiAEBEhoKEmF1dGhfZW1haWxfZG9tYWlucxgNIAMoCRIYCgthbGxvd19pbmRleBgOIAEoCEgJiAEBEh4KEWFsbG93ZWRfaXBfcmFuZ2VzGA8gASgJSAqIAQESQQoMY3VzdG9tX3RoZW1lGBAgASgLMiYub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5DdXN0b21UaGVtZUgLiAEBQg4KDF9kZXNjcmlwdGlvbkIPCg1faG9tZXBhZ2VfdXJsQg4KDF9jb250YWN0X3VybEIRCg9fZGVmYXVsdF9sb2NhbGVCBwoFX2ljb25CEAoOX2N1c3RvbV9kb21haW5CCAoGX3RoZW1lQg4KDF9hY2Nlc3NfdHlwZUILCglfcGFzc3dvcmRCDgoMX2FsbG93X2luZGV4QhQKEl9hbGxvd2VkX2lwX3Jhbmdlc0IPCg1fY3VzdG9tX3RoZW1lIlYKGENyZWF0ZVN0YXR1c1BhZ2VSZXNwb25zZRI6CgtzdGF0dXNfcGFnZRgBIAEoCzIlLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuU3RhdHVzUGFnZSIrChRHZXRTdGF0dXNQYWdlUmVxdWVzdBITCgJpZBgBIAEoCUIHukgEcgIQASJTChVHZXRTdGF0dXNQYWdlUmVzcG9uc2USOgoLc3RhdHVzX3BhZ2UYASABKAsyJS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlN0YXR1c1BhZ2UiagoWTGlzdFN0YXR1c1BhZ2VzUmVxdWVzdBIdCgVsaW1pdBgBIAEoBUIJukgGGgQYZCgBSACIAQESHAoGb2Zmc2V0GAIgASgFQge6SAQaAigASAGIAQFCCAoGX2xpbWl0QgkKB19vZmZzZXQicQoXTGlzdFN0YXR1c1BhZ2VzUmVzcG9uc2USQgoMc3RhdHVzX3BhZ2VzGAEgAygLMiwub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5TdGF0dXNQYWdlU3VtbWFyeRISCgp0b3RhbF9zaXplGAIgASgFIqQHChdVcGRhdGVTdGF0dXNQYWdlUmVxdWVzdBITCgJpZBgBIAEoCUIHukgEcgIQARIeCgV0aXRsZRgCIAEoCUIKukgHcgUQARiAAkgAiAEBEiIKC2Rlc2NyaXB0aW9uGAMgASgJQgi6SAVyAxiACEgBiAEBEjkKBHNsdWcYBCABKAlCJrpII3IhEAEYgAIyGl5bYS16MC05XSsoPzotW2EtejAtOV0rKSokSAKIAQESGQoMaG9tZXBhZ2VfdXJsGAUgASgJSAOIAQESGAoLY29udGFjdF91cmwYBiABKAlIBIgBARI+Cg5kZWZhdWx0X2xvY2FsZRgHIAEoDjIhLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuTG9jYWxlSAWIAQESMgoHbG9jYWxlcxgIIAMoDjIhLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuTG9jYWxlEhsKBGljb24YCSABKAlCCLpIBXIDGIAISAaIAQESJAoNY3VzdG9tX2RvbWFpbhgKIAEoCUIIukgFcgMYgAJIB4gBARI4CgV0aGVtZRgLIAEoDjIkLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuUGFnZVRoZW1lSAiIAQESQwoLYWNjZXNzX3R5cGUYDCABKA4yKS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlBhZ2VBY2Nlc3NUeXBlSAmIAQESIQoIcGFzc3dvcmQYDSABKAlCCrpIB3IFEAEYgAJICogBARIaChJhdXRoX2VtYWlsX2RvbWFpbnMYDiADKAkSGAoLYWxsb3dfaW5kZXgYDyABKAhIC4gBARIeChFhbGxvd2VkX2lwX3JhbmdlcxgQIAEoCUgMiAEBEkEKDGN1c3RvbV90aGVtZRgRIAEoCzImLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuQ3VzdG9tVGhlbWVIDYgBAUIICgZfdGl0bGVCDgoMX2Rlc2NyaXB0aW9uQgcKBV9zbHVnQg8KDV9ob21lcGFnZV91cmxCDgoMX2NvbnRhY3RfdXJsQhEKD19kZWZhdWx0X2xvY2FsZUIHCgVfaWNvbkIQCg5fY3VzdG9tX2RvbWFpbkIICgZfdGhlbWVCDgoMX2FjY2Vzc190eXBlQgsKCV9wYXNzd29yZEIOCgxfYWxsb3dfaW5kZXhCFAoSX2FsbG93ZWRfaXBfcmFuZ2VzQg8KDV9jdXN0b21fdGhlbWUiVgoYVXBkYXRlU3RhdHVzUGFnZVJlc3BvbnNlEjoKC3N0YXR1c19wYWdlGAEgASgLMiUub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5TdGF0dXNQYWdlIi4KF0RlbGV0ZVN0YXR1c1BhZ2VSZXF1ZXN0EhMKAmlkGAEgASgJQge6SARyAhABIisKGERlbGV0ZVN0YXR1c1BhZ2VSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIIu8BChpBZGRNb25pdG9yQ29tcG9uZW50UmVxdWVzdBIYCgdwYWdlX2lkGAEgASgJQge6SARyAhABEhsKCm1vbml0b3JfaWQYAiABKAlCB7pIBHICEAESGwoEbmFtZRgDIAEoCUIIukgFcgMYgAJIAIgBARIiCgtkZXNjcmlwdGlvbhgEIAEoCUIIukgFcgMYgAhIAYgBARISCgVvcmRlchgFIAEoBUgCiAEBEhUKCGdyb3VwX2lkGAYgASgJSAOIAQFCBwoFX25hbWVCDgoMX2Rlc2NyaXB0aW9uQggKBl9vcmRlckILCglfZ3JvdXBfaWQiWgobQWRkTW9uaXRvckNvbXBvbmVudFJlc3BvbnNlEjsKCWNvbXBvbmVudBgBIAEoCzIoLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuUGFnZUNvbXBvbmVudCLFAQoZQWRkU3RhdGljQ29tcG9uZW50UmVxdWVzdBIYCgdwYWdlX2lkGAEgASgJQge6SARyAhABEhgKBG5hbWUYAiABKAlCCrpIB3IFEAEYgAISIgoLZGVzY3JpcHRpb24YAyABKAlCCLpIBXIDGIAISACIAQESEgoFb3JkZXIYBCABKAVIAYgBARIVCghncm91cF9pZBgFIAEoCUgCiAEBQg4KDF9kZXNjcmlwdGlvbkIICgZfb3JkZXJCCwoJX2dyb3VwX2lkIlkKGkFkZFN0YXRpY0NvbXBvbmVudFJlc3BvbnNlEjsKCWNvbXBvbmVudBgBIAEoCzIoLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuUGFnZUNvbXBvbmVudCItChZSZW1vdmVDb21wb25lbnRSZXF1ZXN0EhMKAmlkGAEgASgJQge6SARyAhABIioKF1JlbW92ZUNvbXBvbmVudFJlc3BvbnNlEg8KB3N1Y2Nlc3MYASABKAgi8wEKFlVwZGF0ZUNvbXBvbmVudFJlcXVlc3QSEwoCaWQYASABKAlCB7pIBHICEAESGwoEbmFtZRgCIAEoCUIIukgFcgMYgAJIAIgBARIiCgtkZXNjcmlwdGlvbhgDIAEoCUIIukgFcgMYgAhIAYgBARISCgVvcmRlchgEIAEoBUgCiAEBEhUKCGdyb3VwX2lkGAUgASgJSAOIAQESGAoLZ3JvdXBfb3JkZXIYBiABKAVIBIgBAUIHCgVfbmFtZUIOCgxfZGVzY3JpcHRpb25CCAoGX29yZGVyQgsKCV9ncm91cF9pZEIOCgxfZ3JvdXBfb3JkZXIiVgoXVXBkYXRlQ29tcG9uZW50UmVzcG9uc2USOwoJY29tcG9uZW50GAEgASgLMigub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5QYWdlQ29tcG9uZW50In0KG0NyZWF0ZUNvbXBvbmVudEdyb3VwUmVxdWVzdBIYCgdwYWdlX2lkGAEgASgJQge6SARyAhABEhgKBG5hbWUYAiABKAlCCrpIB3IFEAEYgAISGQoMZGVmYXVsdF9vcGVuGAMgASgISACIAQFCDwoNX2RlZmF1bHRfb3BlbiJcChxDcmVhdGVDb21wb25lbnRHcm91cFJlc3BvbnNlEjwKBWdyb3VwGAEgASgLMi0ub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5QYWdlQ29tcG9uZW50R3JvdXAiMgobRGVsZXRlQ29tcG9uZW50R3JvdXBSZXF1ZXN0EhMKAmlkGAEgASgJQge6SARyAhABIi8KHERlbGV0ZUNvbXBvbmVudEdyb3VwUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCKGAQobVXBkYXRlQ29tcG9uZW50R3JvdXBSZXF1ZXN0EhMKAmlkGAEgASgJQge6SARyAhABEh0KBG5hbWUYAiABKAlCCrpIB3IFEAEYgAJIAIgBARIZCgxkZWZhdWx0X29wZW4YAyABKAhIAYgBAUIHCgVfbmFtZUIPCg1fZGVmYXVsdF9vcGVuIlwKHFVwZGF0ZUNvbXBvbmVudEdyb3VwUmVzcG9uc2USPAoFZ3JvdXAYASABKAsyLS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlBhZ2VDb21wb25lbnRHcm91cCJhChZTdWJzY3JpYmVUb1BhZ2VSZXF1ZXN0EhgKB3BhZ2VfaWQYASABKAlCB7pIBHICEAESLQoFZW1haWwYAiABKAlCHrpHFDoSEhB1c2VyQGV4YW1wbGUuY29tukgEcgJgASJYChdTdWJzY3JpYmVUb1BhZ2VSZXNwb25zZRI9CgpzdWJzY3JpYmVyGAEgASgLMikub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5QYWdlU3Vic2NyaWJlciImCgxFbWFpbENoYW5uZWwSFgoFZW1haWwYASABKAlCB7pIBHICYAEiOwoUV2ViaG9va0NoYW5uZWxIZWFkZXISFAoDa2V5GAEgASgJQge6SARyAhABEg0KBXZhbHVlGAIgASgJInYKDldlYmhvb2tDaGFubmVsEiIKC3dlYmhvb2tfdXJsGAEgASgJQg26SApyCBABGIAQiAEBEkAKB2hlYWRlcnMYAiADKAsyLy5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLldlYmhvb2tDaGFubmVsSGVhZGVyIpACCh1DcmVhdGVQYWdlU3Vic2NyaXB0aW9uUmVxdWVzdBIYCgdwYWdlX2lkGAEgASgJQge6SARyAhABEhsKBG5hbWUYAiABKAlCCLpIBXIDGP8BSAGIAQESFQoNY29tcG9uZW50X2lkcxgDIAMoCRJACg1lbWFpbF9jaGFubmVsGAogASgLMicub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5FbWFpbENoYW5uZWxIABJECg93ZWJob29rX2NoYW5uZWwYCyABKAsyKS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLldlYmhvb2tDaGFubmVsSABCEAoHY2hhbm5lbBIFukgCCAFCBwoFX25hbWUiXwoeQ3JlYXRlUGFnZVN1YnNjcmlwdGlvblJlc3BvbnNlEj0KCnN1YnNjcmliZXIYASABKAsyKS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlBhZ2VTdWJzY3JpYmVyImMKGlVuc3Vic2NyaWJlRnJvbVBhZ2VSZXF1ZXN0EhgKB3BhZ2VfaWQYASABKAlCB7pIBHICEAESDwoFZW1haWwYAiABKAlIABIMCgJpZBgDIAEoCUgAQgwKCmlkZW50aWZpZXIiLgobVW5zdWJzY3JpYmVGcm9tUGFnZVJlc3BvbnNlEg8KB3N1Y2Nlc3MYASABKAgiwAEKFkxpc3RTdWJzY3JpYmVyc1JlcXVlc3QSGAoHcGFnZV9pZBgBIAEoCUIHukgEcgIQARIdCgVsaW1pdBgCIAEoBUIJukgGGgQYZCgBSACIAQESHAoGb2Zmc2V0GAMgASgFQge6SAQaAigASAGIAQESIQoUaW5jbHVkZV91bnN1YnNjcmliZWQYBCABKAhIAogBAUIICgZfbGltaXRCCQoHX29mZnNldEIXChVfaW5jbHVkZV91bnN1YnNjcmliZWQibQoXTGlzdFN1YnNjcmliZXJzUmVzcG9uc2USPgoLc3Vic2NyaWJlcnMYASADKAsyKS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlBhZ2VTdWJzY3JpYmVyEhIKCnRvdGFsX3NpemUYAiABKAUiSQobR2V0U3RhdHVzUGFnZUNvbnRlbnRSZXF1ZXN0EgwKAmlkGAEgASgJSAASDgoEc2x1ZxgCIAEoCUgAQgwKCmlkZW50aWZpZXIi3wIKHEdldFN0YXR1c1BhZ2VDb250ZW50UmVzcG9uc2USOgoLc3RhdHVzX3BhZ2UYASABKAsyJS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlN0YXR1c1BhZ2USPAoKY29tcG9uZW50cxgCIAMoCzIoLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuUGFnZUNvbXBvbmVudBI9CgZncm91cHMYAyADKAsyLS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlBhZ2VDb21wb25lbnRHcm91cBJBCg5zdGF0dXNfcmVwb3J0cxgEIAMoCzIpLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5TdGF0dXNSZXBvcnQSQwoMbWFpbnRlbmFuY2VzGAUgAygLMi0ub3BlbnN0YXR1cy5tYWludGVuYW5jZS52MS5NYWludGVuYW5jZVN1bW1hcnkiRQoXR2V0T3ZlcmFsbFN0YXR1c1JlcXVlc3QSDAoCaWQYASABKAlIABIOCgRzbHVnGAIgASgJSABCDAoKaWRlbnRpZmllciJhCg9Db21wb25lbnRTdGF0dXMSFAoMY29tcG9uZW50X2lkGAEgASgJEjgKBnN0YXR1cxgCIAEoDjIoLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuT3ZlcmFsbFN0YXR1cyKkAQoYR2V0T3ZlcmFsbFN0YXR1c1Jlc3BvbnNlEkAKDm92ZXJhbGxfc3RhdHVzGAEgASgOMigub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5PdmVyYWxsU3RhdHVzEkYKEmNvbXBvbmVudF9zdGF0dXNlcxgCIAMoCzIqLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuQ29tcG9uZW50U3RhdHVzIjMKHEdldFN0YXR1c1BhZ2VPdmVydmlld1JlcXVlc3QSEwoCaWQYASABKAlCB7pIBHICEAEirwQKHUdldFN0YXR1c1BhZ2VPdmVydmlld1Jlc3BvbnNlEjoKC3N0YXR1c19wYWdlGAEgASgLMiUub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5TdGF0dXNQYWdlEkMKDWNvbmZpZ3VyYXRpb24YAiABKAsyLC5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlBhZ2VDb25maWd1cmF0aW9uEjwKCmNvbXBvbmVudHMYAyADKAsyKC5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlBhZ2VDb21wb25lbnQSPQoGZ3JvdXBzGAQgAygLMi0ub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5QYWdlQ29tcG9uZW50R3JvdXASQQoOc3RhdHVzX3JlcG9ydHMYBSADKAsyKS5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuU3RhdHVzUmVwb3J0EkMKDG1haW50ZW5hbmNlcxgGIAMoCzItLm9wZW5zdGF0dXMubWFpbnRlbmFuY2UudjEuTWFpbnRlbmFuY2VTdW1tYXJ5EkAKDm92ZXJhbGxfc3RhdHVzGAcgASgOMigub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5PdmVyYWxsU3RhdHVzEkYKEmNvbXBvbmVudF9zdGF0dXNlcxgIIAMoCzIqLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuQ29tcG9uZW50U3RhdHVzIp0BCiNHZXRQYWdlQ29tcG9uZW50RGFpbHlTdW1tYXJ5UmVxdWVzdBIMCgJpZBgBIAEoCUgAEg4KBHNsdWcYAiABKAlIABIjCg1jb21wb25lbnRfaWRzGAMgAygJQgy6SAmSAQYiBHICEAESHAoEZGF5cxgEIAEoBUIJukgGGgQYLSgBSAGIAQFCDAoKaWRlbnRpZmllckIHCgVfZGF5cyLuAQoSQ29tcG9uZW50RGF5QnVja2V0EgsKA2RheRgBIAEoCRINCgVjb3VudBgCIAEoAxIKCgJvaxgDIAEoAxIQCghkZWdyYWRlZBgEIAEoAxINCgVlcnJvchgFIAEoAxI9CgZzdGF0dXMYBiABKA4yLS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkNvbXBvbmVudERheVN0YXR1cxJFCgZpbXBhY3QYByABKA4yMC5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuUGFnZUNvbXBvbmVudEltcGFjdEgAiAEBQgkKB19pbXBhY3QioAIKDkNvbXBvbmVudEV2ZW50EgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSOwoEdHlwZRgDIAEoDjItLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuQ29tcG9uZW50RXZlbnRUeXBlEj8KBnN0YXR1cxgEIAEoDjIvLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuQ29tcG9uZW50RXZlbnRTdGF0dXMSDAoEZnJvbRgFIAEoCRIPCgJ0bxgGIAEoCUgAiAEBEkUKBmltcGFjdBgHIAEoDjIwLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5QYWdlQ29tcG9uZW50SW1wYWN0SAGIAQFCBQoDX3RvQgkKB19pbXBhY3QingIKGVBhZ2VDb21wb25lbnREYWlseVN1bW1hcnkSFAoMY29tcG9uZW50X2lkGAEgASgJEjoKBHR5cGUYAiABKA4yLC5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlBhZ2VDb21wb25lbnRUeXBlEhcKCm1vbml0b3JfaWQYAyABKAlIAIgBARIMCgRuYW1lGAQgASgJEj4KB2J1Y2tldHMYBSADKAsyLS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkNvbXBvbmVudERheUJ1Y2tldBI5CgZldmVudHMYBiADKAsyKS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkNvbXBvbmVudEV2ZW50Qg0KC19tb25pdG9yX2lkInAKJEdldFBhZ2VDb21wb25lbnREYWlseVN1bW1hcnlSZXNwb25zZRJICgpjb21wb25lbnRzGAEgAygLMjQub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5QYWdlQ29tcG9uZW50RGFpbHlTdW1tYXJ5KugBChJDb21wb25lbnREYXlTdGF0dXMSJAogQ09NUE9ORU5UX0RBWV9TVEFUVVNfVU5TUEVDSUZJRUQQABIkCiBDT01QT05FTlRfREFZX1NUQVRVU19PUEVSQVRJT05BTBABEiEKHUNPTVBPTkVOVF9EQVlfU1RBVFVTX0RFR1JBREVEEAISHQoZQ09NUE9ORU5UX0RBWV9TVEFUVVNfRE9XThADEiQKIENPTVBPTkVOVF9EQVlfU1RBVFVTX01BSU5URU5BTkNFEAQSHgoaQ09NUE9ORU5UX0RBWV9TVEFUVVNfRU1QVFkQBSqkAQoSQ29tcG9uZW50RXZlbnRUeXBlEiQKIENPTVBPTkVOVF9FVkVOVF9UWVBFX1VOU1BFQ0lGSUVEEAASJAogQ09NUE9ORU5UX0VWRU5UX1RZUEVfTUFJTlRFTkFOQ0UQARIhCh1DT01QT05FTlRfRVZFTlRfVFlQRV9JTkNJREVOVBACEh8KG0NPTVBPTkVOVF9FVkVOVF9UWVBFX1JFUE9SVBADKtQBChRDb21wb25lbnRFdmVudFN0YXR1cxImCiJDT01QT05FTlRfRVZFTlRfU1RBVFVTX1VOU1BFQ0lGSUVEEAASJgoiQ09NUE9ORU5UX0VWRU5UX1NUQVRVU19PUEVSQVRJT05BTBABEiMKH0NPTVBPTkVOVF9FVkVOVF9TVEFUVVNfREVHUkFERUQQAhIfChtDT01QT05FTlRfRVZFTlRfU1RBVFVTX0RPV04QAxImCiJDT01QT05FTlRfRVZFTlRfU1RBVFVTX01BSU5URU5BTkNFEAQyxCcKEVN0YXR1c1BhZ2VTZXJ2aWNlEnsKEENyZWF0ZVN0YXR1c1BhZ2USMi5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkNyZWF0ZVN0YXR1c1BhZ2VSZXF1ZXN0GjMub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5DcmVhdGVTdGF0dXNQYWdlUmVzcG9uc2USdwoNR2V0U3RhdHVzUGFnZRIvLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuR2V0U3RhdHVzUGFnZVJlcXVlc3QaMC5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkdldFN0YXR1c1BhZ2VSZXNwb25zZSIDkAIBEn0KD0xpc3RTdGF0dXNQYWdlcxIxLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuTGlzdFN0YXR1c1BhZ2VzUmVxdWVzdBoyLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuTGlzdFN0YXR1c1BhZ2VzUmVzcG9uc2UiA5ACARJ7ChBVcGRhdGVTdGF0dXNQYWdlEjIub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5VcGRhdGVTdGF0dXNQYWdlUmVxdWVzdBozLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuVXBkYXRlU3RhdHVzUGFnZVJlc3BvbnNlEnsKEERlbGV0ZVN0YXR1c1BhZ2USMi5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkRlbGV0ZVN0YXR1c1BhZ2VSZXF1ZXN0GjMub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5EZWxldGVTdGF0dXNQYWdlUmVzcG9uc2UShAEKE0FkZE1vbml0b3JDb21wb25lbnQSNS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkFkZE1vbml0b3JDb21wb25lbnRSZXF1ZXN0GjYub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5BZGRNb25pdG9yQ29tcG9uZW50UmVzcG9uc2USgQEKEkFkZFN0YXRpY0NvbXBvbmVudBI0Lm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuQWRkU3RhdGljQ29tcG9uZW50UmVxdWVzdBo1Lm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuQWRkU3RhdGljQ29tcG9uZW50UmVzcG9uc2USeAoPUmVtb3ZlQ29tcG9uZW50EjEub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5SZW1vdmVDb21wb25lbnRSZXF1ZXN0GjIub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5SZW1vdmVDb21wb25lbnRSZXNwb25zZRJ4Cg9VcGRhdGVDb21wb25lbnQSMS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlVwZGF0ZUNvbXBvbmVudFJlcXVlc3QaMi5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlVwZGF0ZUNvbXBvbmVudFJlc3BvbnNlEocBChRDcmVhdGVDb21wb25lbnRHcm91cBI2Lm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuQ3JlYXRlQ29tcG9uZW50R3JvdXBSZXF1ZXN0Gjcub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5DcmVhdGVDb21wb25lbnRHcm91cFJlc3BvbnNlEocBChREZWxldGVDb21wb25lbnRHcm91cBI2Lm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuRGVsZXRlQ29tcG9uZW50R3JvdXBSZXF1ZXN0Gjcub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5EZWxldGVDb21wb25lbnRHcm91cFJlc3BvbnNlEocBChRVcGRhdGVDb21wb25lbnRHcm91cBI2Lm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuVXBkYXRlQ29tcG9uZW50R3JvdXBSZXF1ZXN0Gjcub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5VcGRhdGVDb21wb25lbnRHcm91cFJlc3BvbnNlErkECg9TdWJzY3JpYmVUb1BhZ2USMS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlN1YnNjcmliZVRvUGFnZVJlcXVlc3QaMi5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlN1YnNjcmliZVRvUGFnZVJlc3BvbnNlIr4Duke6Axq3A0VuZC11c2VyIGVtYWlsIHNlbGYtc2lnbnVwIHdpdGggZG91YmxlIG9wdC1pbiB2ZXJpZmljYXRpb24uIEEgdmVyaWZpY2F0aW9uIGVtYWlsIGlzIHNlbnQgYW5kIHRoZSBzdWJzY3JpcHRpb24gYWN0aXZhdGVzIG9ubHkgYWZ0ZXIgdGhlIHJlY2lwaWVudCBjb25maXJtcy4gSWYgdGhlIGVtYWlsIHdhcyBwcmV2aW91c2x5IHVuc3Vic2NyaWJlZCwgdGhlIGV4aXN0aW5nIHJvdyBpcyByZWFjdGl2YXRlZCBpbnN0ZWFkIG9mIGEgZHVwbGljYXRlIGJlaW5nIGNyZWF0ZWQuIFVzZSB0aGlzIGZvciBzdWJzY3JpYmVyLWluaXRpYXRlZCBzaWdudXBzIG9uIHRoZSBwdWJsaWMgc3RhdHVzIHBhZ2U7IHVzZSBDcmVhdGVQYWdlU3Vic2NyaXB0aW9uIGZvciBvcGVyYXRvci1pbml0aWF0ZWQgKHZlbmRvcikgc3Vic2NyaXB0aW9ucyB0aGF0IHNob3VsZCBza2lwIHZlcmlmaWNhdGlvbi4S/QQKFkNyZWF0ZVBhZ2VTdWJzY3JpcHRpb24SOC5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkNyZWF0ZVBhZ2VTdWJzY3JpcHRpb25SZXF1ZXN0Gjkub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5DcmVhdGVQYWdlU3Vic2NyaXB0aW9uUmVzcG9uc2Ui7QO6R+kDGuYDT3BlcmF0b3ItYWRkZWQgc3Vic2NyaWJlciAoZW1haWwgb3Igd2ViaG9vaykgd2l0aCBubyB2ZXJpZmljYXRpb24gZmxvdyDigJQgdGhlIHBhcnRuZXIgc3RhcnRzIHJlY2VpdmluZyBub3RpZmljYXRpb25zIGltbWVkaWF0ZWx5LiBTdXBwb3J0cyBTbGFjayBhbmQgRGlzY29yZCB3ZWJob29rIFVSTHMgaW4gYWRkaXRpb24gdG8gZW1haWwuIEEgbWFuYWdlbWVudCB0b2tlbiBpcyBzdGlsbCBnZW5lcmF0ZWQgc28gdGhlIHN1YnNjcmliZXIgY2FuIHNlbGYtbWFuYWdlICh1cGRhdGUgc2NvcGUsIHVuc3Vic2NyaWJlKSB3aXRob3V0IG9wZXJhdG9yIGludm9sdmVtZW50LiBVc2UgdGhpcyBmb3IgdmVuZG9yL3BhcnRuZXIgaW50ZWdyYXRpb25zIHdoZXJlIGNvbnNlbnQgaXMgZXN0YWJsaXNoZWQgb3V0LW9mLWJhbmQ7IHVzZSBTdWJzY3JpYmVUb1BhZ2UgZm9yIHN1YnNjcmliZXItaW5pdGlhdGVkIHNpZ251cHMgdGhhdCByZXF1aXJlIGRvdWJsZSBvcHQtaW4uEoQBChNVbnN1YnNjcmliZUZyb21QYWdlEjUub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5VbnN1YnNjcmliZUZyb21QYWdlUmVxdWVzdBo2Lm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuVW5zdWJzY3JpYmVGcm9tUGFnZVJlc3BvbnNlEn0KD0xpc3RTdWJzY3JpYmVycxIxLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuTGlzdFN1YnNjcmliZXJzUmVxdWVzdBoyLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuTGlzdFN1YnNjcmliZXJzUmVzcG9uc2UiA5ACARLAAwoUR2V0U3RhdHVzUGFnZUNvbnRlbnQSNi5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkdldFN0YXR1c1BhZ2VDb250ZW50UmVxdWVzdBo3Lm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuR2V0U3RhdHVzUGFnZUNvbnRlbnRSZXNwb25zZSK2ApACAbpHrwIarAJSZXR1cm5zIHRoZSBmdWxsIGNvbnRlbnQgb2YgYSBzdGF0dXMgcGFnZSBpbmNsdWRpbmcgaXRzIGNvbXBvbmVudHMsIGNvbXBvbmVudCBncm91cHMsIGFjdGl2ZSBzdGF0dXMgcmVwb3J0cywgYW5kIHNjaGVkdWxlZCBtYWludGVuYW5jZXMuIFN1cHBvcnRzIHR3byBhY2Nlc3MgcGF0aHM6IGJ5IElEIChyZXF1aXJlcyBhdXRoZW50aWNhdGlvbiwgd29ya3NwYWNlLXNjb3BlZCkgb3IgYnkgc2x1ZyAocHVibGljIGFjY2VzcywgcmVxdWlyZXMgdGhlIHBhZ2UgdG8gYmUgcHVibGlzaGVkIHdpdGggYWNjZXNzX3R5cGU9UFVCTElDKS4SqgMKEEdldE92ZXJhbGxTdGF0dXMSMi5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkdldE92ZXJhbGxTdGF0dXNSZXF1ZXN0GjMub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5HZXRPdmVyYWxsU3RhdHVzUmVzcG9uc2UirAKQAgG6R6UCGqICUmV0dXJucyB0aGUgb3ZlcmFsbCBzdGF0dXMgb2YgYSBzdGF0dXMgcGFnZSBhbG9uZyB3aXRoIGluZGl2aWR1YWwgY29tcG9uZW50IHN0YXR1c2VzLiBUaGUgb3ZlcmFsbCBzdGF0dXMgaXMgY29tcHV0ZWQgZnJvbSBhY3RpdmUgc3RhdHVzIHJlcG9ydHMgYW5kIG1haW50ZW5hbmNlcyB3aXRoIHRoZSBmb2xsb3dpbmcgcHJpb3JpdHk6IGRlZ3JhZGVkIChmcm9tIGFjdGl2ZSBzdGF0dXMgcmVwb3J0cykgPiBtYWludGVuYW5jZSAoZnJvbSBhY3RpdmUgbWFpbnRlbmFuY2Ugd2luZG93cykgPiBvcGVyYXRpb25hbC4SmAQKFUdldFN0YXR1c1BhZ2VPdmVydmlldxI3Lm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuR2V0U3RhdHVzUGFnZU92ZXJ2aWV3UmVxdWVzdBo4Lm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuR2V0U3RhdHVzUGFnZU92ZXJ2aWV3UmVzcG9uc2UiiwOQAgG6R4QDGoEDUmV0dXJucyBldmVyeXRoaW5nIGFib3V0IGEgc2luZ2xlIHN0YXR1cyBwYWdlIGluIG9uZSBhdXRoZW50aWNhdGVkIGNhbGw6IHRoZSBwYWdlLCBpdHMgcmljaCByZW5kZXJpbmcgY29uZmlndXJhdGlvbiwgY29tcG9uZW50cywgY29tcG9uZW50IGdyb3VwcywgYWN0aXZlL3JlY2VudCBzdGF0dXMgcmVwb3J0cywgbWFpbnRlbmFuY2VzLCBhbmQgdGhlIGNvbXB1dGVkIG92ZXJhbGwgKyBwZXItY29tcG9uZW50IHN0YXR1c2VzLiBXb3Jrc3BhY2Utc2NvcGVkIGJ5IGlkOyB0aGVyZSBpcyBubyBwdWJsaWMgc2x1ZyBhY2Nlc3MgcGF0aC4gRG9lcyBub3QgaW5jbHVkZSB1cHRpbWUgdGltZS1zZXJpZXMg4oCUIHVzZSBHZXRQYWdlQ29tcG9uZW50RGFpbHlTdW1tYXJ5IGZvciB0aGF0LhLVBAocR2V0UGFnZUNvbXBvbmVudERhaWx5U3VtbWFyeRI+Lm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuR2V0UGFnZUNvbXBvbmVudERhaWx5U3VtbWFyeVJlcXVlc3QaPy5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkdldFBhZ2VDb21wb25lbnREYWlseVN1bW1hcnlSZXNwb25zZSKzA5ACAbpHrAMaqQNSZXR1cm5zIHBlci1jb21wb25lbnQgZGFpbHkgc3RhdHVzIGJ1Y2tldHMgKG9rL2RlZ3JhZGVkL2Vycm9yL2NvdW50IHBsdXMgYSByZXNvbHZlZCBzdGF0dXMpIG1lcmdlZCB3aXRoIHRoZSBpbmNpZGVudCwgbWFpbnRlbmFuY2UsIGFuZCBzdGF0dXMtcmVwb3J0IHRpbWVsaW5lIG92ZXIgdGhlIGxhc3QgTiBkYXlzIChtYXggNDUpLiBTdWl0YWJsZSBhcyBhIHNpbmdsZSBzb3VyY2Ugb2YgdHJ1dGggZm9yIHN0YXR1cy1iYXIgYW5kIHVwdGltZS1jYWxlbmRhciByZW5kZXJpbmcuIFN1cHBvcnRzIHR3byBhY2Nlc3MgcGF0aHM6IGJ5IGlkIChhdXRoZW50aWNhdGVkLCB3b3Jrc3BhY2Utc2NvcGVkKSBvciBieSBzbHVnIChwdWJsaWMgYWNjZXNzLCByZXF1aXJlcyB0aGUgcGFnZSB0byBiZSBwdWJsaXNoZWQgd2l0aCBhY2Nlc3NfdHlwZT1QVUJMSUMpLkJaWlhnaXRodWIuY29tL29wZW5zdGF0dXNocS9vcGVuc3RhdHVzL3BhY2thZ2VzL3Byb3RvL29wZW5zdGF0dXMvc3RhdHVzX3BhZ2UvdjE7c3RhdHVzcGFnZXYxYgZwcm90bzM", [file_buf_validate_validate, file_gnostic_openapi_v3_annotations, file_openstatus_maintenance_v1_maintenance, file_openstatus_status_page_v1_page_component, file_openstatus_status_page_v1_page_subscriber, file_openstatus_status_page_v1_status_page, file_openstatus_status_report_v1_status_report]); + fileDesc("CidvcGVuc3RhdHVzL3N0YXR1c19wYWdlL3YxL3NlcnZpY2UucHJvdG8SGW9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEivgcKF0NyZWF0ZVN0YXR1c1BhZ2VSZXF1ZXN0EjAKBXRpdGxlGAEgASgJQiG6RxQ6EhIQQWNtZSBDb3JwIFN0YXR1c7pIB3IFEAEYgAISIgoLZGVzY3JpcHRpb24YAiABKAlCCLpIBXIDGIAISACIAQESSQoEc2x1ZxgDIAEoCUI7ukcSOhASDm15LXN0YXR1cy1wYWdlukgjciEQARiAAjIaXlthLXowLTldKyg/Oi1bYS16MC05XSspKiQSOQoMaG9tZXBhZ2VfdXJsGAQgASgJQh66Rxs6GRIXaHR0cHM6Ly93d3cuZXhhbXBsZS5jb21IAYgBARIYCgtjb250YWN0X3VybBgFIAEoCUgCiAEBEj4KDmRlZmF1bHRfbG9jYWxlGAYgASgOMiEub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5Mb2NhbGVIA4gBARIyCgdsb2NhbGVzGAcgAygOMiEub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5Mb2NhbGUSGwoEaWNvbhgIIAEoCUIIukgFcgMYgAhIBIgBARIkCg1jdXN0b21fZG9tYWluGAkgASgJQgi6SAVyAxiAAkgFiAEBEjgKBXRoZW1lGAogASgOMiQub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5QYWdlVGhlbWVIBogBARJDCgthY2Nlc3NfdHlwZRgLIAEoDjIpLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuUGFnZUFjY2Vzc1R5cGVIB4gBARIhCghwYXNzd29yZBgMIAEoCUIKukgHcgUQARiAAkgIiAEBEhoKEmF1dGhfZW1haWxfZG9tYWlucxgNIAMoCRIYCgthbGxvd19pbmRleBgOIAEoCEgJiAEBEh4KEWFsbG93ZWRfaXBfcmFuZ2VzGA8gASgJSAqIAQESQQoMY3VzdG9tX3RoZW1lGBAgASgLMiYub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5DdXN0b21UaGVtZUgLiAEBQg4KDF9kZXNjcmlwdGlvbkIPCg1faG9tZXBhZ2VfdXJsQg4KDF9jb250YWN0X3VybEIRCg9fZGVmYXVsdF9sb2NhbGVCBwoFX2ljb25CEAoOX2N1c3RvbV9kb21haW5CCAoGX3RoZW1lQg4KDF9hY2Nlc3NfdHlwZUILCglfcGFzc3dvcmRCDgoMX2FsbG93X2luZGV4QhQKEl9hbGxvd2VkX2lwX3Jhbmdlc0IPCg1fY3VzdG9tX3RoZW1lIlYKGENyZWF0ZVN0YXR1c1BhZ2VSZXNwb25zZRI6CgtzdGF0dXNfcGFnZRgBIAEoCzIlLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuU3RhdHVzUGFnZSIrChRHZXRTdGF0dXNQYWdlUmVxdWVzdBITCgJpZBgBIAEoCUIHukgEcgIQASJTChVHZXRTdGF0dXNQYWdlUmVzcG9uc2USOgoLc3RhdHVzX3BhZ2UYASABKAsyJS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlN0YXR1c1BhZ2UiagoWTGlzdFN0YXR1c1BhZ2VzUmVxdWVzdBIdCgVsaW1pdBgBIAEoBUIJukgGGgQYZCgBSACIAQESHAoGb2Zmc2V0GAIgASgFQge6SAQaAigASAGIAQFCCAoGX2xpbWl0QgkKB19vZmZzZXQicQoXTGlzdFN0YXR1c1BhZ2VzUmVzcG9uc2USQgoMc3RhdHVzX3BhZ2VzGAEgAygLMiwub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5TdGF0dXNQYWdlU3VtbWFyeRISCgp0b3RhbF9zaXplGAIgASgFIqQHChdVcGRhdGVTdGF0dXNQYWdlUmVxdWVzdBITCgJpZBgBIAEoCUIHukgEcgIQARIeCgV0aXRsZRgCIAEoCUIKukgHcgUQARiAAkgAiAEBEiIKC2Rlc2NyaXB0aW9uGAMgASgJQgi6SAVyAxiACEgBiAEBEjkKBHNsdWcYBCABKAlCJrpII3IhEAEYgAIyGl5bYS16MC05XSsoPzotW2EtejAtOV0rKSokSAKIAQESGQoMaG9tZXBhZ2VfdXJsGAUgASgJSAOIAQESGAoLY29udGFjdF91cmwYBiABKAlIBIgBARI+Cg5kZWZhdWx0X2xvY2FsZRgHIAEoDjIhLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuTG9jYWxlSAWIAQESMgoHbG9jYWxlcxgIIAMoDjIhLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuTG9jYWxlEhsKBGljb24YCSABKAlCCLpIBXIDGIAISAaIAQESJAoNY3VzdG9tX2RvbWFpbhgKIAEoCUIIukgFcgMYgAJIB4gBARI4CgV0aGVtZRgLIAEoDjIkLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuUGFnZVRoZW1lSAiIAQESQwoLYWNjZXNzX3R5cGUYDCABKA4yKS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlBhZ2VBY2Nlc3NUeXBlSAmIAQESIQoIcGFzc3dvcmQYDSABKAlCCrpIB3IFEAEYgAJICogBARIaChJhdXRoX2VtYWlsX2RvbWFpbnMYDiADKAkSGAoLYWxsb3dfaW5kZXgYDyABKAhIC4gBARIeChFhbGxvd2VkX2lwX3JhbmdlcxgQIAEoCUgMiAEBEkEKDGN1c3RvbV90aGVtZRgRIAEoCzImLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuQ3VzdG9tVGhlbWVIDYgBAUIICgZfdGl0bGVCDgoMX2Rlc2NyaXB0aW9uQgcKBV9zbHVnQg8KDV9ob21lcGFnZV91cmxCDgoMX2NvbnRhY3RfdXJsQhEKD19kZWZhdWx0X2xvY2FsZUIHCgVfaWNvbkIQCg5fY3VzdG9tX2RvbWFpbkIICgZfdGhlbWVCDgoMX2FjY2Vzc190eXBlQgsKCV9wYXNzd29yZEIOCgxfYWxsb3dfaW5kZXhCFAoSX2FsbG93ZWRfaXBfcmFuZ2VzQg8KDV9jdXN0b21fdGhlbWUiVgoYVXBkYXRlU3RhdHVzUGFnZVJlc3BvbnNlEjoKC3N0YXR1c19wYWdlGAEgASgLMiUub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5TdGF0dXNQYWdlIi4KF0RlbGV0ZVN0YXR1c1BhZ2VSZXF1ZXN0EhMKAmlkGAEgASgJQge6SARyAhABIisKGERlbGV0ZVN0YXR1c1BhZ2VSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIIu8BChpBZGRNb25pdG9yQ29tcG9uZW50UmVxdWVzdBIYCgdwYWdlX2lkGAEgASgJQge6SARyAhABEhsKCm1vbml0b3JfaWQYAiABKAlCB7pIBHICEAESGwoEbmFtZRgDIAEoCUIIukgFcgMYgAJIAIgBARIiCgtkZXNjcmlwdGlvbhgEIAEoCUIIukgFcgMYgAhIAYgBARISCgVvcmRlchgFIAEoBUgCiAEBEhUKCGdyb3VwX2lkGAYgASgJSAOIAQFCBwoFX25hbWVCDgoMX2Rlc2NyaXB0aW9uQggKBl9vcmRlckILCglfZ3JvdXBfaWQiWgobQWRkTW9uaXRvckNvbXBvbmVudFJlc3BvbnNlEjsKCWNvbXBvbmVudBgBIAEoCzIoLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuUGFnZUNvbXBvbmVudCLFAQoZQWRkU3RhdGljQ29tcG9uZW50UmVxdWVzdBIYCgdwYWdlX2lkGAEgASgJQge6SARyAhABEhgKBG5hbWUYAiABKAlCCrpIB3IFEAEYgAISIgoLZGVzY3JpcHRpb24YAyABKAlCCLpIBXIDGIAISACIAQESEgoFb3JkZXIYBCABKAVIAYgBARIVCghncm91cF9pZBgFIAEoCUgCiAEBQg4KDF9kZXNjcmlwdGlvbkIICgZfb3JkZXJCCwoJX2dyb3VwX2lkIlkKGkFkZFN0YXRpY0NvbXBvbmVudFJlc3BvbnNlEjsKCWNvbXBvbmVudBgBIAEoCzIoLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuUGFnZUNvbXBvbmVudCItChZSZW1vdmVDb21wb25lbnRSZXF1ZXN0EhMKAmlkGAEgASgJQge6SARyAhABIioKF1JlbW92ZUNvbXBvbmVudFJlc3BvbnNlEg8KB3N1Y2Nlc3MYASABKAgi8wEKFlVwZGF0ZUNvbXBvbmVudFJlcXVlc3QSEwoCaWQYASABKAlCB7pIBHICEAESGwoEbmFtZRgCIAEoCUIIukgFcgMYgAJIAIgBARIiCgtkZXNjcmlwdGlvbhgDIAEoCUIIukgFcgMYgAhIAYgBARISCgVvcmRlchgEIAEoBUgCiAEBEhUKCGdyb3VwX2lkGAUgASgJSAOIAQESGAoLZ3JvdXBfb3JkZXIYBiABKAVIBIgBAUIHCgVfbmFtZUIOCgxfZGVzY3JpcHRpb25CCAoGX29yZGVyQgsKCV9ncm91cF9pZEIOCgxfZ3JvdXBfb3JkZXIiVgoXVXBkYXRlQ29tcG9uZW50UmVzcG9uc2USOwoJY29tcG9uZW50GAEgASgLMigub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5QYWdlQ29tcG9uZW50Ii4KF0dldFBhZ2VDb21wb25lbnRSZXF1ZXN0EhMKAmlkGAEgASgJQge6SARyAhABIlcKGEdldFBhZ2VDb21wb25lbnRSZXNwb25zZRI7Cgljb21wb25lbnQYASABKAsyKC5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlBhZ2VDb21wb25lbnQifQobQ3JlYXRlQ29tcG9uZW50R3JvdXBSZXF1ZXN0EhgKB3BhZ2VfaWQYASABKAlCB7pIBHICEAESGAoEbmFtZRgCIAEoCUIKukgHcgUQARiAAhIZCgxkZWZhdWx0X29wZW4YAyABKAhIAIgBAUIPCg1fZGVmYXVsdF9vcGVuIlwKHENyZWF0ZUNvbXBvbmVudEdyb3VwUmVzcG9uc2USPAoFZ3JvdXAYASABKAsyLS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlBhZ2VDb21wb25lbnRHcm91cCIyChtEZWxldGVDb21wb25lbnRHcm91cFJlcXVlc3QSEwoCaWQYASABKAlCB7pIBHICEAEiLwocRGVsZXRlQ29tcG9uZW50R3JvdXBSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIIoYBChtVcGRhdGVDb21wb25lbnRHcm91cFJlcXVlc3QSEwoCaWQYASABKAlCB7pIBHICEAESHQoEbmFtZRgCIAEoCUIKukgHcgUQARiAAkgAiAEBEhkKDGRlZmF1bHRfb3BlbhgDIAEoCEgBiAEBQgcKBV9uYW1lQg8KDV9kZWZhdWx0X29wZW4iXAocVXBkYXRlQ29tcG9uZW50R3JvdXBSZXNwb25zZRI8CgVncm91cBgBIAEoCzItLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuUGFnZUNvbXBvbmVudEdyb3VwImEKFlN1YnNjcmliZVRvUGFnZVJlcXVlc3QSGAoHcGFnZV9pZBgBIAEoCUIHukgEcgIQARItCgVlbWFpbBgCIAEoCUIeukcUOhISEHVzZXJAZXhhbXBsZS5jb226SARyAmABIlgKF1N1YnNjcmliZVRvUGFnZVJlc3BvbnNlEj0KCnN1YnNjcmliZXIYASABKAsyKS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlBhZ2VTdWJzY3JpYmVyIiYKDEVtYWlsQ2hhbm5lbBIWCgVlbWFpbBgBIAEoCUIHukgEcgJgASI7ChRXZWJob29rQ2hhbm5lbEhlYWRlchIUCgNrZXkYASABKAlCB7pIBHICEAESDQoFdmFsdWUYAiABKAkidgoOV2ViaG9va0NoYW5uZWwSIgoLd2ViaG9va191cmwYASABKAlCDbpICnIIEAEYgBCIAQESQAoHaGVhZGVycxgCIAMoCzIvLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuV2ViaG9va0NoYW5uZWxIZWFkZXIikAIKHUNyZWF0ZVBhZ2VTdWJzY3JpcHRpb25SZXF1ZXN0EhgKB3BhZ2VfaWQYASABKAlCB7pIBHICEAESGwoEbmFtZRgCIAEoCUIIukgFcgMY/wFIAYgBARIVCg1jb21wb25lbnRfaWRzGAMgAygJEkAKDWVtYWlsX2NoYW5uZWwYCiABKAsyJy5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkVtYWlsQ2hhbm5lbEgAEkQKD3dlYmhvb2tfY2hhbm5lbBgLIAEoCzIpLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuV2ViaG9va0NoYW5uZWxIAEIQCgdjaGFubmVsEgW6SAIIAUIHCgVfbmFtZSJfCh5DcmVhdGVQYWdlU3Vic2NyaXB0aW9uUmVzcG9uc2USPQoKc3Vic2NyaWJlchgBIAEoCzIpLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuUGFnZVN1YnNjcmliZXIiYwoaVW5zdWJzY3JpYmVGcm9tUGFnZVJlcXVlc3QSGAoHcGFnZV9pZBgBIAEoCUIHukgEcgIQARIPCgVlbWFpbBgCIAEoCUgAEgwKAmlkGAMgASgJSABCDAoKaWRlbnRpZmllciIuChtVbnN1YnNjcmliZUZyb21QYWdlUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCLAAQoWTGlzdFN1YnNjcmliZXJzUmVxdWVzdBIYCgdwYWdlX2lkGAEgASgJQge6SARyAhABEh0KBWxpbWl0GAIgASgFQgm6SAYaBBhkKAFIAIgBARIcCgZvZmZzZXQYAyABKAVCB7pIBBoCKABIAYgBARIhChRpbmNsdWRlX3Vuc3Vic2NyaWJlZBgEIAEoCEgCiAEBQggKBl9saW1pdEIJCgdfb2Zmc2V0QhcKFV9pbmNsdWRlX3Vuc3Vic2NyaWJlZCJtChdMaXN0U3Vic2NyaWJlcnNSZXNwb25zZRI+CgtzdWJzY3JpYmVycxgBIAMoCzIpLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuUGFnZVN1YnNjcmliZXISEgoKdG90YWxfc2l6ZRgCIAEoBSJJChtHZXRTdGF0dXNQYWdlQ29udGVudFJlcXVlc3QSDAoCaWQYASABKAlIABIOCgRzbHVnGAIgASgJSABCDAoKaWRlbnRpZmllciLfAgocR2V0U3RhdHVzUGFnZUNvbnRlbnRSZXNwb25zZRI6CgtzdGF0dXNfcGFnZRgBIAEoCzIlLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuU3RhdHVzUGFnZRI8Cgpjb21wb25lbnRzGAIgAygLMigub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5QYWdlQ29tcG9uZW50Ej0KBmdyb3VwcxgDIAMoCzItLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuUGFnZUNvbXBvbmVudEdyb3VwEkEKDnN0YXR1c19yZXBvcnRzGAQgAygLMikub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLlN0YXR1c1JlcG9ydBJDCgxtYWludGVuYW5jZXMYBSADKAsyLS5vcGVuc3RhdHVzLm1haW50ZW5hbmNlLnYxLk1haW50ZW5hbmNlU3VtbWFyeSJFChdHZXRPdmVyYWxsU3RhdHVzUmVxdWVzdBIMCgJpZBgBIAEoCUgAEg4KBHNsdWcYAiABKAlIAEIMCgppZGVudGlmaWVyImEKD0NvbXBvbmVudFN0YXR1cxIUCgxjb21wb25lbnRfaWQYASABKAkSOAoGc3RhdHVzGAIgASgOMigub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5PdmVyYWxsU3RhdHVzIqQBChhHZXRPdmVyYWxsU3RhdHVzUmVzcG9uc2USQAoOb3ZlcmFsbF9zdGF0dXMYASABKA4yKC5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLk92ZXJhbGxTdGF0dXMSRgoSY29tcG9uZW50X3N0YXR1c2VzGAIgAygLMioub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5Db21wb25lbnRTdGF0dXMiMwocR2V0U3RhdHVzUGFnZU92ZXJ2aWV3UmVxdWVzdBITCgJpZBgBIAEoCUIHukgEcgIQASKvBAodR2V0U3RhdHVzUGFnZU92ZXJ2aWV3UmVzcG9uc2USOgoLc3RhdHVzX3BhZ2UYASABKAsyJS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlN0YXR1c1BhZ2USQwoNY29uZmlndXJhdGlvbhgCIAEoCzIsLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuUGFnZUNvbmZpZ3VyYXRpb24SPAoKY29tcG9uZW50cxgDIAMoCzIoLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuUGFnZUNvbXBvbmVudBI9CgZncm91cHMYBCADKAsyLS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlBhZ2VDb21wb25lbnRHcm91cBJBCg5zdGF0dXNfcmVwb3J0cxgFIAMoCzIpLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5TdGF0dXNSZXBvcnQSQwoMbWFpbnRlbmFuY2VzGAYgAygLMi0ub3BlbnN0YXR1cy5tYWludGVuYW5jZS52MS5NYWludGVuYW5jZVN1bW1hcnkSQAoOb3ZlcmFsbF9zdGF0dXMYByABKA4yKC5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLk92ZXJhbGxTdGF0dXMSRgoSY29tcG9uZW50X3N0YXR1c2VzGAggAygLMioub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5Db21wb25lbnRTdGF0dXMinQEKI0dldFBhZ2VDb21wb25lbnREYWlseVN1bW1hcnlSZXF1ZXN0EgwKAmlkGAEgASgJSAASDgoEc2x1ZxgCIAEoCUgAEiMKDWNvbXBvbmVudF9pZHMYAyADKAlCDLpICZIBBiIEcgIQARIcCgRkYXlzGAQgASgFQgm6SAYaBBgtKAFIAYgBAUIMCgppZGVudGlmaWVyQgcKBV9kYXlzIu4BChJDb21wb25lbnREYXlCdWNrZXQSCwoDZGF5GAEgASgJEg0KBWNvdW50GAIgASgDEgoKAm9rGAMgASgDEhAKCGRlZ3JhZGVkGAQgASgDEg0KBWVycm9yGAUgASgDEj0KBnN0YXR1cxgGIAEoDjItLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuQ29tcG9uZW50RGF5U3RhdHVzEkUKBmltcGFjdBgHIAEoDjIwLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5QYWdlQ29tcG9uZW50SW1wYWN0SACIAQFCCQoHX2ltcGFjdCKgAgoOQ29tcG9uZW50RXZlbnQSCgoCaWQYASABKAkSDAoEbmFtZRgCIAEoCRI7CgR0eXBlGAMgASgOMi0ub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5Db21wb25lbnRFdmVudFR5cGUSPwoGc3RhdHVzGAQgASgOMi8ub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5Db21wb25lbnRFdmVudFN0YXR1cxIMCgRmcm9tGAUgASgJEg8KAnRvGAYgASgJSACIAQESRQoGaW1wYWN0GAcgASgOMjAub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLlBhZ2VDb21wb25lbnRJbXBhY3RIAYgBAUIFCgNfdG9CCQoHX2ltcGFjdCKeAgoZUGFnZUNvbXBvbmVudERhaWx5U3VtbWFyeRIUCgxjb21wb25lbnRfaWQYASABKAkSOgoEdHlwZRgCIAEoDjIsLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuUGFnZUNvbXBvbmVudFR5cGUSFwoKbW9uaXRvcl9pZBgDIAEoCUgAiAEBEgwKBG5hbWUYBCABKAkSPgoHYnVja2V0cxgFIAMoCzItLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuQ29tcG9uZW50RGF5QnVja2V0EjkKBmV2ZW50cxgGIAMoCzIpLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuQ29tcG9uZW50RXZlbnRCDQoLX21vbml0b3JfaWQicAokR2V0UGFnZUNvbXBvbmVudERhaWx5U3VtbWFyeVJlc3BvbnNlEkgKCmNvbXBvbmVudHMYASADKAsyNC5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlBhZ2VDb21wb25lbnREYWlseVN1bW1hcnkq6AEKEkNvbXBvbmVudERheVN0YXR1cxIkCiBDT01QT05FTlRfREFZX1NUQVRVU19VTlNQRUNJRklFRBAAEiQKIENPTVBPTkVOVF9EQVlfU1RBVFVTX09QRVJBVElPTkFMEAESIQodQ09NUE9ORU5UX0RBWV9TVEFUVVNfREVHUkFERUQQAhIdChlDT01QT05FTlRfREFZX1NUQVRVU19ET1dOEAMSJAogQ09NUE9ORU5UX0RBWV9TVEFUVVNfTUFJTlRFTkFOQ0UQBBIeChpDT01QT05FTlRfREFZX1NUQVRVU19FTVBUWRAFKqQBChJDb21wb25lbnRFdmVudFR5cGUSJAogQ09NUE9ORU5UX0VWRU5UX1RZUEVfVU5TUEVDSUZJRUQQABIkCiBDT01QT05FTlRfRVZFTlRfVFlQRV9NQUlOVEVOQU5DRRABEiEKHUNPTVBPTkVOVF9FVkVOVF9UWVBFX0lOQ0lERU5UEAISHwobQ09NUE9ORU5UX0VWRU5UX1RZUEVfUkVQT1JUEAMq1AEKFENvbXBvbmVudEV2ZW50U3RhdHVzEiYKIkNPTVBPTkVOVF9FVkVOVF9TVEFUVVNfVU5TUEVDSUZJRUQQABImCiJDT01QT05FTlRfRVZFTlRfU1RBVFVTX09QRVJBVElPTkFMEAESIwofQ09NUE9ORU5UX0VWRU5UX1NUQVRVU19ERUdSQURFRBACEh8KG0NPTVBPTkVOVF9FVkVOVF9TVEFUVVNfRE9XThADEiYKIkNPTVBPTkVOVF9FVkVOVF9TVEFUVVNfTUFJTlRFTkFOQ0UQBDLAKgoRU3RhdHVzUGFnZVNlcnZpY2USewoQQ3JlYXRlU3RhdHVzUGFnZRIyLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuQ3JlYXRlU3RhdHVzUGFnZVJlcXVlc3QaMy5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkNyZWF0ZVN0YXR1c1BhZ2VSZXNwb25zZRJ3Cg1HZXRTdGF0dXNQYWdlEi8ub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5HZXRTdGF0dXNQYWdlUmVxdWVzdBowLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuR2V0U3RhdHVzUGFnZVJlc3BvbnNlIgOQAgESfQoPTGlzdFN0YXR1c1BhZ2VzEjEub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5MaXN0U3RhdHVzUGFnZXNSZXF1ZXN0GjIub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5MaXN0U3RhdHVzUGFnZXNSZXNwb25zZSIDkAIBEnsKEFVwZGF0ZVN0YXR1c1BhZ2USMi5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlVwZGF0ZVN0YXR1c1BhZ2VSZXF1ZXN0GjMub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5VcGRhdGVTdGF0dXNQYWdlUmVzcG9uc2USewoQRGVsZXRlU3RhdHVzUGFnZRIyLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuRGVsZXRlU3RhdHVzUGFnZVJlcXVlc3QaMy5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkRlbGV0ZVN0YXR1c1BhZ2VSZXNwb25zZRKEAQoTQWRkTW9uaXRvckNvbXBvbmVudBI1Lm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuQWRkTW9uaXRvckNvbXBvbmVudFJlcXVlc3QaNi5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkFkZE1vbml0b3JDb21wb25lbnRSZXNwb25zZRKBAQoSQWRkU3RhdGljQ29tcG9uZW50EjQub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5BZGRTdGF0aWNDb21wb25lbnRSZXF1ZXN0GjUub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5BZGRTdGF0aWNDb21wb25lbnRSZXNwb25zZRJ4Cg9SZW1vdmVDb21wb25lbnQSMS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlJlbW92ZUNvbXBvbmVudFJlcXVlc3QaMi5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlJlbW92ZUNvbXBvbmVudFJlc3BvbnNlEngKD1VwZGF0ZUNvbXBvbmVudBIxLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuVXBkYXRlQ29tcG9uZW50UmVxdWVzdBoyLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuVXBkYXRlQ29tcG9uZW50UmVzcG9uc2US+QIKEEdldFBhZ2VDb21wb25lbnQSMi5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkdldFBhZ2VDb21wb25lbnRSZXF1ZXN0GjMub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5HZXRQYWdlQ29tcG9uZW50UmVzcG9uc2Ui+wGQAgG6R/QBGvEBUmV0dXJucyBhIHNpbmdsZSBzdGF0dXMtcGFnZSBjb21wb25lbnQgYnkgaXRzIElELCBzY29wZWQgdG8gdGhlIGF1dGhlbnRpY2F0ZWQgd29ya3NwYWNlLiBVc2UgdGhpcyB0byByZXNvbHZlIGEgY29tcG9uZW50IGlkIHRvIGl0cyBuYW1lIChhbmQgb3RoZXIgZmllbGRzKSBpbnN0ZWFkIG9mIGZldGNoaW5nIHRoZSB3aG9sZSBwYWdlIHZpYSBHZXRTdGF0dXNQYWdlQ29udGVudCBhbmQgZmlsdGVyaW5nIC5jb21wb25lbnRzLhKHAQoUQ3JlYXRlQ29tcG9uZW50R3JvdXASNi5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkNyZWF0ZUNvbXBvbmVudEdyb3VwUmVxdWVzdBo3Lm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuQ3JlYXRlQ29tcG9uZW50R3JvdXBSZXNwb25zZRKHAQoURGVsZXRlQ29tcG9uZW50R3JvdXASNi5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkRlbGV0ZUNvbXBvbmVudEdyb3VwUmVxdWVzdBo3Lm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuRGVsZXRlQ29tcG9uZW50R3JvdXBSZXNwb25zZRKHAQoUVXBkYXRlQ29tcG9uZW50R3JvdXASNi5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlVwZGF0ZUNvbXBvbmVudEdyb3VwUmVxdWVzdBo3Lm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuVXBkYXRlQ29tcG9uZW50R3JvdXBSZXNwb25zZRK5BAoPU3Vic2NyaWJlVG9QYWdlEjEub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5TdWJzY3JpYmVUb1BhZ2VSZXF1ZXN0GjIub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5TdWJzY3JpYmVUb1BhZ2VSZXNwb25zZSK+A7pHugMatwNFbmQtdXNlciBlbWFpbCBzZWxmLXNpZ251cCB3aXRoIGRvdWJsZSBvcHQtaW4gdmVyaWZpY2F0aW9uLiBBIHZlcmlmaWNhdGlvbiBlbWFpbCBpcyBzZW50IGFuZCB0aGUgc3Vic2NyaXB0aW9uIGFjdGl2YXRlcyBvbmx5IGFmdGVyIHRoZSByZWNpcGllbnQgY29uZmlybXMuIElmIHRoZSBlbWFpbCB3YXMgcHJldmlvdXNseSB1bnN1YnNjcmliZWQsIHRoZSBleGlzdGluZyByb3cgaXMgcmVhY3RpdmF0ZWQgaW5zdGVhZCBvZiBhIGR1cGxpY2F0ZSBiZWluZyBjcmVhdGVkLiBVc2UgdGhpcyBmb3Igc3Vic2NyaWJlci1pbml0aWF0ZWQgc2lnbnVwcyBvbiB0aGUgcHVibGljIHN0YXR1cyBwYWdlOyB1c2UgQ3JlYXRlUGFnZVN1YnNjcmlwdGlvbiBmb3Igb3BlcmF0b3ItaW5pdGlhdGVkICh2ZW5kb3IpIHN1YnNjcmlwdGlvbnMgdGhhdCBzaG91bGQgc2tpcCB2ZXJpZmljYXRpb24uEv0EChZDcmVhdGVQYWdlU3Vic2NyaXB0aW9uEjgub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5DcmVhdGVQYWdlU3Vic2NyaXB0aW9uUmVxdWVzdBo5Lm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuQ3JlYXRlUGFnZVN1YnNjcmlwdGlvblJlc3BvbnNlIu0DukfpAxrmA09wZXJhdG9yLWFkZGVkIHN1YnNjcmliZXIgKGVtYWlsIG9yIHdlYmhvb2spIHdpdGggbm8gdmVyaWZpY2F0aW9uIGZsb3cg4oCUIHRoZSBwYXJ0bmVyIHN0YXJ0cyByZWNlaXZpbmcgbm90aWZpY2F0aW9ucyBpbW1lZGlhdGVseS4gU3VwcG9ydHMgU2xhY2sgYW5kIERpc2NvcmQgd2ViaG9vayBVUkxzIGluIGFkZGl0aW9uIHRvIGVtYWlsLiBBIG1hbmFnZW1lbnQgdG9rZW4gaXMgc3RpbGwgZ2VuZXJhdGVkIHNvIHRoZSBzdWJzY3JpYmVyIGNhbiBzZWxmLW1hbmFnZSAodXBkYXRlIHNjb3BlLCB1bnN1YnNjcmliZSkgd2l0aG91dCBvcGVyYXRvciBpbnZvbHZlbWVudC4gVXNlIHRoaXMgZm9yIHZlbmRvci9wYXJ0bmVyIGludGVncmF0aW9ucyB3aGVyZSBjb25zZW50IGlzIGVzdGFibGlzaGVkIG91dC1vZi1iYW5kOyB1c2UgU3Vic2NyaWJlVG9QYWdlIGZvciBzdWJzY3JpYmVyLWluaXRpYXRlZCBzaWdudXBzIHRoYXQgcmVxdWlyZSBkb3VibGUgb3B0LWluLhKEAQoTVW5zdWJzY3JpYmVGcm9tUGFnZRI1Lm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuVW5zdWJzY3JpYmVGcm9tUGFnZVJlcXVlc3QaNi5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLlVuc3Vic2NyaWJlRnJvbVBhZ2VSZXNwb25zZRJ9Cg9MaXN0U3Vic2NyaWJlcnMSMS5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkxpc3RTdWJzY3JpYmVyc1JlcXVlc3QaMi5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkxpc3RTdWJzY3JpYmVyc1Jlc3BvbnNlIgOQAgESwAMKFEdldFN0YXR1c1BhZ2VDb250ZW50EjYub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5HZXRTdGF0dXNQYWdlQ29udGVudFJlcXVlc3QaNy5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkdldFN0YXR1c1BhZ2VDb250ZW50UmVzcG9uc2UitgKQAgG6R68CGqwCUmV0dXJucyB0aGUgZnVsbCBjb250ZW50IG9mIGEgc3RhdHVzIHBhZ2UgaW5jbHVkaW5nIGl0cyBjb21wb25lbnRzLCBjb21wb25lbnQgZ3JvdXBzLCBhY3RpdmUgc3RhdHVzIHJlcG9ydHMsIGFuZCBzY2hlZHVsZWQgbWFpbnRlbmFuY2VzLiBTdXBwb3J0cyB0d28gYWNjZXNzIHBhdGhzOiBieSBJRCAocmVxdWlyZXMgYXV0aGVudGljYXRpb24sIHdvcmtzcGFjZS1zY29wZWQpIG9yIGJ5IHNsdWcgKHB1YmxpYyBhY2Nlc3MsIHJlcXVpcmVzIHRoZSBwYWdlIHRvIGJlIHB1Ymxpc2hlZCB3aXRoIGFjY2Vzc190eXBlPVBVQkxJQykuEqoDChBHZXRPdmVyYWxsU3RhdHVzEjIub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5HZXRPdmVyYWxsU3RhdHVzUmVxdWVzdBozLm9wZW5zdGF0dXMuc3RhdHVzX3BhZ2UudjEuR2V0T3ZlcmFsbFN0YXR1c1Jlc3BvbnNlIqwCkAIBukelAhqiAlJldHVybnMgdGhlIG92ZXJhbGwgc3RhdHVzIG9mIGEgc3RhdHVzIHBhZ2UgYWxvbmcgd2l0aCBpbmRpdmlkdWFsIGNvbXBvbmVudCBzdGF0dXNlcy4gVGhlIG92ZXJhbGwgc3RhdHVzIGlzIGNvbXB1dGVkIGZyb20gYWN0aXZlIHN0YXR1cyByZXBvcnRzIGFuZCBtYWludGVuYW5jZXMgd2l0aCB0aGUgZm9sbG93aW5nIHByaW9yaXR5OiBkZWdyYWRlZCAoZnJvbSBhY3RpdmUgc3RhdHVzIHJlcG9ydHMpID4gbWFpbnRlbmFuY2UgKGZyb20gYWN0aXZlIG1haW50ZW5hbmNlIHdpbmRvd3MpID4gb3BlcmF0aW9uYWwuEpgEChVHZXRTdGF0dXNQYWdlT3ZlcnZpZXcSNy5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkdldFN0YXR1c1BhZ2VPdmVydmlld1JlcXVlc3QaOC5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkdldFN0YXR1c1BhZ2VPdmVydmlld1Jlc3BvbnNlIosDkAIBukeEAxqBA1JldHVybnMgZXZlcnl0aGluZyBhYm91dCBhIHNpbmdsZSBzdGF0dXMgcGFnZSBpbiBvbmUgYXV0aGVudGljYXRlZCBjYWxsOiB0aGUgcGFnZSwgaXRzIHJpY2ggcmVuZGVyaW5nIGNvbmZpZ3VyYXRpb24sIGNvbXBvbmVudHMsIGNvbXBvbmVudCBncm91cHMsIGFjdGl2ZS9yZWNlbnQgc3RhdHVzIHJlcG9ydHMsIG1haW50ZW5hbmNlcywgYW5kIHRoZSBjb21wdXRlZCBvdmVyYWxsICsgcGVyLWNvbXBvbmVudCBzdGF0dXNlcy4gV29ya3NwYWNlLXNjb3BlZCBieSBpZDsgdGhlcmUgaXMgbm8gcHVibGljIHNsdWcgYWNjZXNzIHBhdGguIERvZXMgbm90IGluY2x1ZGUgdXB0aW1lIHRpbWUtc2VyaWVzIOKAlCB1c2UgR2V0UGFnZUNvbXBvbmVudERhaWx5U3VtbWFyeSBmb3IgdGhhdC4S1QQKHEdldFBhZ2VDb21wb25lbnREYWlseVN1bW1hcnkSPi5vcGVuc3RhdHVzLnN0YXR1c19wYWdlLnYxLkdldFBhZ2VDb21wb25lbnREYWlseVN1bW1hcnlSZXF1ZXN0Gj8ub3BlbnN0YXR1cy5zdGF0dXNfcGFnZS52MS5HZXRQYWdlQ29tcG9uZW50RGFpbHlTdW1tYXJ5UmVzcG9uc2UiswOQAgG6R6wDGqkDUmV0dXJucyBwZXItY29tcG9uZW50IGRhaWx5IHN0YXR1cyBidWNrZXRzIChvay9kZWdyYWRlZC9lcnJvci9jb3VudCBwbHVzIGEgcmVzb2x2ZWQgc3RhdHVzKSBtZXJnZWQgd2l0aCB0aGUgaW5jaWRlbnQsIG1haW50ZW5hbmNlLCBhbmQgc3RhdHVzLXJlcG9ydCB0aW1lbGluZSBvdmVyIHRoZSBsYXN0IE4gZGF5cyAobWF4IDQ1KS4gU3VpdGFibGUgYXMgYSBzaW5nbGUgc291cmNlIG9mIHRydXRoIGZvciBzdGF0dXMtYmFyIGFuZCB1cHRpbWUtY2FsZW5kYXIgcmVuZGVyaW5nLiBTdXBwb3J0cyB0d28gYWNjZXNzIHBhdGhzOiBieSBpZCAoYXV0aGVudGljYXRlZCwgd29ya3NwYWNlLXNjb3BlZCkgb3IgYnkgc2x1ZyAocHVibGljIGFjY2VzcywgcmVxdWlyZXMgdGhlIHBhZ2UgdG8gYmUgcHVibGlzaGVkIHdpdGggYWNjZXNzX3R5cGU9UFVCTElDKS5CWlpYZ2l0aHViLmNvbS9vcGVuc3RhdHVzaHEvb3BlbnN0YXR1cy9wYWNrYWdlcy9wcm90by9vcGVuc3RhdHVzL3N0YXR1c19wYWdlL3YxO3N0YXR1c3BhZ2V2MWIGcHJvdG8z", [file_buf_validate_validate, file_gnostic_openapi_v3_annotations, file_openstatus_maintenance_v1_maintenance, file_openstatus_status_page_v1_page_component, file_openstatus_status_page_v1_page_subscriber, file_openstatus_status_page_v1_status_page, file_openstatus_status_report_v1_status_report]); /** * CreateStatusPageRequest is the request to create a new status page. @@ -733,6 +733,48 @@ export type UpdateComponentResponse = Message<"openstatus.status_page.v1.UpdateC export const UpdateComponentResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_openstatus_status_page_v1_service, 17); +/** + * GetPageComponentRequest is the request to fetch a single component by ID. + * + * @generated from message openstatus.status_page.v1.GetPageComponentRequest + */ +export type GetPageComponentRequest = Message<"openstatus.status_page.v1.GetPageComponentRequest"> & { + /** + * ID of the component to retrieve (required). + * + * @generated from field: string id = 1; + */ + id: string; +}; + +/** + * Describes the message openstatus.status_page.v1.GetPageComponentRequest. + * Use `create(GetPageComponentRequestSchema)` to create a new message. + */ +export const GetPageComponentRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_status_page_v1_service, 18); + +/** + * GetPageComponentResponse is the response containing the component. + * + * @generated from message openstatus.status_page.v1.GetPageComponentResponse + */ +export type GetPageComponentResponse = Message<"openstatus.status_page.v1.GetPageComponentResponse"> & { + /** + * The requested component. + * + * @generated from field: openstatus.status_page.v1.PageComponent component = 1; + */ + component?: PageComponent | undefined; +}; + +/** + * Describes the message openstatus.status_page.v1.GetPageComponentResponse. + * Use `create(GetPageComponentResponseSchema)` to create a new message. + */ +export const GetPageComponentResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_status_page_v1_service, 19); + /** * CreateComponentGroupRequest is the request to create a new component group. * @@ -766,7 +808,7 @@ export type CreateComponentGroupRequest = Message<"openstatus.status_page.v1.Cre * Use `create(CreateComponentGroupRequestSchema)` to create a new message. */ export const CreateComponentGroupRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 18); + messageDesc(file_openstatus_status_page_v1_service, 20); /** * CreateComponentGroupResponse is the response after creating a component group. @@ -787,7 +829,7 @@ export type CreateComponentGroupResponse = Message<"openstatus.status_page.v1.Cr * Use `create(CreateComponentGroupResponseSchema)` to create a new message. */ export const CreateComponentGroupResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 19); + messageDesc(file_openstatus_status_page_v1_service, 21); /** * DeleteComponentGroupRequest is the request to delete a component group. @@ -808,7 +850,7 @@ export type DeleteComponentGroupRequest = Message<"openstatus.status_page.v1.Del * Use `create(DeleteComponentGroupRequestSchema)` to create a new message. */ export const DeleteComponentGroupRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 20); + messageDesc(file_openstatus_status_page_v1_service, 22); /** * DeleteComponentGroupResponse is the response after deleting a component group. @@ -829,7 +871,7 @@ export type DeleteComponentGroupResponse = Message<"openstatus.status_page.v1.De * Use `create(DeleteComponentGroupResponseSchema)` to create a new message. */ export const DeleteComponentGroupResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 21); + messageDesc(file_openstatus_status_page_v1_service, 23); /** * UpdateComponentGroupRequest is the request to update a component group. @@ -864,7 +906,7 @@ export type UpdateComponentGroupRequest = Message<"openstatus.status_page.v1.Upd * Use `create(UpdateComponentGroupRequestSchema)` to create a new message. */ export const UpdateComponentGroupRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 22); + messageDesc(file_openstatus_status_page_v1_service, 24); /** * UpdateComponentGroupResponse is the response after updating a component group. @@ -885,7 +927,7 @@ export type UpdateComponentGroupResponse = Message<"openstatus.status_page.v1.Up * Use `create(UpdateComponentGroupResponseSchema)` to create a new message. */ export const UpdateComponentGroupResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 23); + messageDesc(file_openstatus_status_page_v1_service, 25); /** * SubscribeToPageRequest is the request to subscribe an email to a status page. @@ -913,7 +955,7 @@ export type SubscribeToPageRequest = Message<"openstatus.status_page.v1.Subscrib * Use `create(SubscribeToPageRequestSchema)` to create a new message. */ export const SubscribeToPageRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 24); + messageDesc(file_openstatus_status_page_v1_service, 26); /** * SubscribeToPageResponse is the response after subscribing to a status page. @@ -934,7 +976,7 @@ export type SubscribeToPageResponse = Message<"openstatus.status_page.v1.Subscri * Use `create(SubscribeToPageResponseSchema)` to create a new message. */ export const SubscribeToPageResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 25); + messageDesc(file_openstatus_status_page_v1_service, 27); /** * EmailChannel carries the email-channel fields for CreatePageSubscription. @@ -955,7 +997,7 @@ export type EmailChannel = Message<"openstatus.status_page.v1.EmailChannel"> & { * Use `create(EmailChannelSchema)` to create a new message. */ export const EmailChannelSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 26); + messageDesc(file_openstatus_status_page_v1_service, 28); /** * WebhookChannelHeader is a single custom HTTP header. @@ -979,7 +1021,7 @@ export type WebhookChannelHeader = Message<"openstatus.status_page.v1.WebhookCha * Use `create(WebhookChannelHeaderSchema)` to create a new message. */ export const WebhookChannelHeaderSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 27); + messageDesc(file_openstatus_status_page_v1_service, 29); /** * WebhookChannel carries the webhook-channel fields for CreatePageSubscription. @@ -1007,7 +1049,7 @@ export type WebhookChannel = Message<"openstatus.status_page.v1.WebhookChannel"> * Use `create(WebhookChannelSchema)` to create a new message. */ export const WebhookChannelSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 28); + messageDesc(file_openstatus_status_page_v1_service, 30); /** * CreatePageSubscriptionRequest is the request to add a vendor-managed subscriber to a status page. @@ -1061,7 +1103,7 @@ export type CreatePageSubscriptionRequest = Message<"openstatus.status_page.v1.C * Use `create(CreatePageSubscriptionRequestSchema)` to create a new message. */ export const CreatePageSubscriptionRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 29); + messageDesc(file_openstatus_status_page_v1_service, 31); /** * CreatePageSubscriptionResponse is the response after creating a vendor-managed subscription. @@ -1082,7 +1124,7 @@ export type CreatePageSubscriptionResponse = Message<"openstatus.status_page.v1. * Use `create(CreatePageSubscriptionResponseSchema)` to create a new message. */ export const CreatePageSubscriptionResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 30); + messageDesc(file_openstatus_status_page_v1_service, 32); /** * UnsubscribeFromPageRequest is the request to unsubscribe from a status page. @@ -1126,7 +1168,7 @@ export type UnsubscribeFromPageRequest = Message<"openstatus.status_page.v1.Unsu * Use `create(UnsubscribeFromPageRequestSchema)` to create a new message. */ export const UnsubscribeFromPageRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 31); + messageDesc(file_openstatus_status_page_v1_service, 33); /** * UnsubscribeFromPageResponse is the response after unsubscribing from a status page. @@ -1147,7 +1189,7 @@ export type UnsubscribeFromPageResponse = Message<"openstatus.status_page.v1.Uns * Use `create(UnsubscribeFromPageResponseSchema)` to create a new message. */ export const UnsubscribeFromPageResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 32); + messageDesc(file_openstatus_status_page_v1_service, 34); /** * ListSubscribersRequest is the request to list subscribers of a status page. @@ -1189,7 +1231,7 @@ export type ListSubscribersRequest = Message<"openstatus.status_page.v1.ListSubs * Use `create(ListSubscribersRequestSchema)` to create a new message. */ export const ListSubscribersRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 33); + messageDesc(file_openstatus_status_page_v1_service, 35); /** * ListSubscribersResponse is the response containing status page subscribers. @@ -1217,7 +1259,7 @@ export type ListSubscribersResponse = Message<"openstatus.status_page.v1.ListSub * Use `create(ListSubscribersResponseSchema)` to create a new message. */ export const ListSubscribersResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 34); + messageDesc(file_openstatus_status_page_v1_service, 36); /** * GetStatusPageContentRequest is the request to get the full content of a status page. @@ -1254,7 +1296,7 @@ export type GetStatusPageContentRequest = Message<"openstatus.status_page.v1.Get * Use `create(GetStatusPageContentRequestSchema)` to create a new message. */ export const GetStatusPageContentRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 35); + messageDesc(file_openstatus_status_page_v1_service, 37); /** * GetStatusPageContentResponse is the response containing the full status page content. @@ -1303,7 +1345,7 @@ export type GetStatusPageContentResponse = Message<"openstatus.status_page.v1.Ge * Use `create(GetStatusPageContentResponseSchema)` to create a new message. */ export const GetStatusPageContentResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 36); + messageDesc(file_openstatus_status_page_v1_service, 38); /** * GetOverallStatusRequest is the request to get the overall status of a status page. @@ -1340,7 +1382,7 @@ export type GetOverallStatusRequest = Message<"openstatus.status_page.v1.GetOver * Use `create(GetOverallStatusRequestSchema)` to create a new message. */ export const GetOverallStatusRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 37); + messageDesc(file_openstatus_status_page_v1_service, 39); /** * ComponentStatus represents the status of a single component. @@ -1368,7 +1410,7 @@ export type ComponentStatus = Message<"openstatus.status_page.v1.ComponentStatus * Use `create(ComponentStatusSchema)` to create a new message. */ export const ComponentStatusSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 38); + messageDesc(file_openstatus_status_page_v1_service, 40); /** * GetOverallStatusResponse is the response containing the overall status and individual component statuses. @@ -1396,7 +1438,7 @@ export type GetOverallStatusResponse = Message<"openstatus.status_page.v1.GetOve * Use `create(GetOverallStatusResponseSchema)` to create a new message. */ export const GetOverallStatusResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 39); + messageDesc(file_openstatus_status_page_v1_service, 41); /** * GetStatusPageOverviewRequest requests the full overview of a status page by id. @@ -1417,7 +1459,7 @@ export type GetStatusPageOverviewRequest = Message<"openstatus.status_page.v1.Ge * Use `create(GetStatusPageOverviewRequestSchema)` to create a new message. */ export const GetStatusPageOverviewRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 40); + messageDesc(file_openstatus_status_page_v1_service, 42); /** * GetStatusPageOverviewResponse bundles all data for a single status page. @@ -1487,7 +1529,7 @@ export type GetStatusPageOverviewResponse = Message<"openstatus.status_page.v1.G * Use `create(GetStatusPageOverviewResponseSchema)` to create a new message. */ export const GetStatusPageOverviewResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 41); + messageDesc(file_openstatus_status_page_v1_service, 43); /** * GetPageComponentDailySummaryRequest is the request for per-component daily summaries. @@ -1538,7 +1580,7 @@ export type GetPageComponentDailySummaryRequest = Message<"openstatus.status_pag * Use `create(GetPageComponentDailySummaryRequestSchema)` to create a new message. */ export const GetPageComponentDailySummaryRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 42); + messageDesc(file_openstatus_status_page_v1_service, 44); /** * ComponentDayBucket is one day of status data for a component. @@ -1601,7 +1643,7 @@ export type ComponentDayBucket = Message<"openstatus.status_page.v1.ComponentDay * Use `create(ComponentDayBucketSchema)` to create a new message. */ export const ComponentDayBucketSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 43); + messageDesc(file_openstatus_status_page_v1_service, 45); /** * ComponentEvent is a single incident / maintenance / report affecting a component. @@ -1664,7 +1706,7 @@ export type ComponentEvent = Message<"openstatus.status_page.v1.ComponentEvent"> * Use `create(ComponentEventSchema)` to create a new message. */ export const ComponentEventSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 44); + messageDesc(file_openstatus_status_page_v1_service, 46); /** * PageComponentDailySummary is the per-day buckets + event timeline for one component. @@ -1720,7 +1762,7 @@ export type PageComponentDailySummary = Message<"openstatus.status_page.v1.PageC * Use `create(PageComponentDailySummarySchema)` to create a new message. */ export const PageComponentDailySummarySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 45); + messageDesc(file_openstatus_status_page_v1_service, 47); /** * GetPageComponentDailySummaryResponse is the response containing per-component summaries. @@ -1741,7 +1783,7 @@ export type GetPageComponentDailySummaryResponse = Message<"openstatus.status_pa * Use `create(GetPageComponentDailySummaryResponseSchema)` to create a new message. */ export const GetPageComponentDailySummaryResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_openstatus_status_page_v1_service, 46); + messageDesc(file_openstatus_status_page_v1_service, 48); /** * ComponentDayStatus is the resolved status of a component on a given day. @@ -1954,6 +1996,16 @@ export const StatusPageService: GenService<{ input: typeof UpdateComponentRequestSchema; output: typeof UpdateComponentResponseSchema; }, + /** + * GetPageComponent retrieves a single component by ID. + * + * @generated from rpc openstatus.status_page.v1.StatusPageService.GetPageComponent + */ + getPageComponent: { + methodKind: "unary"; + input: typeof GetPageComponentRequestSchema; + output: typeof GetPageComponentResponseSchema; + }, /** * CreateComponentGroup creates a new component group. * diff --git a/packages/proto/plan/PLAN.md b/packages/proto/plan/PLAN.md index bc94b76a..e4dd1947 100644 --- a/packages/proto/plan/PLAN.md +++ b/packages/proto/plan/PLAN.md @@ -1,307 +1,276 @@ -# Status Report Proto Implementation Plan +# Plan: `GetPageComponent` — resolve a page component by ID -## Overview +## Motivation -Create proto definitions for Status Report CRUD operations, following the existing patterns in `packages/proto/api/openstatus/monitor/v1/`. +Customer (Philipp Honsel, netgo) is on the **new `/rpc` Connect API** (C# +backend, plain HTTP + JSON, no SDK). Their concrete pain: -## Directory Structure +> **API Gap — Component Name Resolution.** They fetch the *entire* status-page +> content **daily** just to build a component-`id` → `name` map, because there +> is no way to resolve a single component by id. Confirmed as a gap. -``` -packages/proto/api/openstatus/status_report/ -└── v1/ - ├── status_report.proto # Message definitions - └── service.proto # Service and RPC definitions -``` - -## Database Schema Reference - -From `packages/db/src/schema/status_reports/status_reports.ts`: - -### StatusReport Table -| Field | Type | Constraints | -|-------|------|-------------| -| id | integer | primary key | -| status | enum | "investigating", "identified", "monitoring", "resolved" | -| title | text(256) | not null | -| workspaceId | integer | foreign key | -| pageId | integer | foreign key (cascade delete) | -| createdAt | timestamp | default now | -| updatedAt | timestamp | default now | - -### StatusReportUpdate Table -| Field | Type | Constraints | -|-------|------|-------------| -| id | integer | primary key | -| status | enum | same as above | -| date | timestamp | not null | -| message | text | not null | -| statusReportId | integer | foreign key, not null (cascade delete) | -| createdAt | timestamp | default now | -| updatedAt | timestamp | default now | - -### Relationships -- StatusReport has many StatusReportUpdates -- StatusReport belongs to Workspace -- StatusReport can be linked to multiple PageComponents (via `statusReportsToPageComponents` junction table) -- Legacy: StatusReport belongs to Page (deprecated, use page components instead) -- Legacy: StatusReport can be linked to Monitors (via `monitorsToStatusReport`, deprecated) - ---- - -## Proto Definitions - -### File 1: `status_report.proto` - -```protobuf -syntax = "proto3"; - -package openstatus.status_report.v1; +Today the only read path for a `PageComponent` is +`StatusPageService/GetStatusPageContent`, digging the component out of the +`.components` array. Add a direct single-component read. -option go_package = "github.com/openstatushq/openstatus/packages/proto/openstatus/status_report/v1;statusreportv1"; +The customer imagines a RESTful `GET /status-page-component/{id}`; the actual +call on this API is a Connect unary POST (they already call +`.../GetStatusPageContent` the same way): -// StatusReportStatus represents the current state of a status report. -enum StatusReportStatus { - STATUS_REPORT_STATUS_UNSPECIFIED = 0; - STATUS_REPORT_STATUS_INVESTIGATING = 1; - STATUS_REPORT_STATUS_IDENTIFIED = 2; - STATUS_REPORT_STATUS_MONITORING = 3; - STATUS_REPORT_STATUS_RESOLVED = 4; -} - -// StatusReportUpdate represents a single update entry in a status report timeline. -message StatusReportUpdate { - // Unique identifier for the update. - string id = 1; - - // Status at the time of this update. - StatusReportStatus status = 2; - - // Timestamp when this update occurred (RFC 3339 format). - string date = 3; +``` +POST https://api.openstatus.dev/rpc/openstatus.status_page.v1.StatusPageService/GetPageComponent +x-openstatus-key: +content-type: application/json - // Message describing the update. - string message = 4; +{ "id": "1777074467622" } +``` - // Timestamp when the update was created (RFC 3339 format). - string created_at = 5; +Response — the existing `PageComponent` shape (includes `name`, which is what +they need, plus everything else): + +```json +{ + "component": { + "id": "1777074467622", + "pageId": "5076", + "name": "system", + "type": "PAGE_COMPONENT_TYPE_MONITOR", + "monitorId": "10360", + "order": 0, + "groupId": "", + "groupOrder": 0, + "description": "", + "createdAt": "2026-06-29T11:35:21.000Z", + "updatedAt": "2026-07-01T09:36:06.000Z" + } } +``` -// StatusReportSummary represents metadata for a status report (used in list responses). -message StatusReportSummary { - // Unique identifier for the status report. - string id = 1; - - // Current status of the report. - StatusReportStatus status = 2; - - // Title of the status report. - string title = 3; - - // IDs of affected page components. - repeated string page_component_ids = 4; - - // Timestamp when the report was created (RFC 3339 format). - string created_at = 5; +## Decisions (settled) - // Timestamp when the report was last updated (RFC 3339 format). - string updated_at = 6; -} +| Aspect | Decision | Rationale | +|--------|----------|-----------| +| **Access model** | Auth-only, workspace-scoped, request is `{ id }`. Cross-workspace id → 404. No public slug path. | Matches `GetStatusPage`/`GetMonitor`; customer calls with an API key. | +| **Response** | Bare `PageComponent`, existing message untouched. No `page_component.proto` edit. | Already carries `name` + everything; smallest change. | +| **RPC name** | `GetPageComponent` (not `GetPageComponentById`). | House convention: `GetMonitor`, `GetStatusPage`, `GetMaintenance`, `GetNotification`, `GetStatusReport` — none use a `ById` suffix. Tell the customer the final path. | +| **Implementation** | Inline handler, reuse existing `getComponentById` + `dbComponentToProto` + `pageComponentNotFoundError`. No new service files. | This handler already reads components inline (e.g. `updateComponent`). Read → no audit, no `requireScope`. | +| **Malformed id** | Non-numeric → `NaN` → lookup miss → **404** (fall-through). Empty string → **400** from the `min_len=1` validator. | Identical to every sibling `Get*`; no bespoke validation branch. | +| **Tests** | Full 5-case mirror of the `GetStatusPage` block. | Consistency; reuses existing seed + `connectRequest` helper. | +| **Transports** | Connect/REST `/rpc` API only. No MCP tool, no tRPC procedure. | Customer need is API-only; MCP already has `listPageComponentsTool`; dashboard reads via its own tRPC. | -// StatusReport represents an incident or maintenance report with full details. -message StatusReport { - // Unique identifier for the status report. - string id = 1; +Non-breaking: additive RPC + messages, passes buf `FILE` breaking rule. - // Current status of the report. - StatusReportStatus status = 2; - - // Title of the status report. - string title = 3; +--- - // IDs of affected page components. - repeated string page_component_ids = 4; +## 1. Proto — `api/openstatus/status_page/v1/service.proto` - // Timeline of updates for this report (only included in GetStatusReport). - repeated StatusReportUpdate updates = 5; +No new message *types* (`PageComponent` in `page_component.proto` is reused). - // Timestamp when the report was created (RFC 3339 format). - string created_at = 6; +Add the RPC in the **Component Management** block of `StatusPageService`, after +`UpdateComponent`: - // Timestamp when the report was last updated (RFC 3339 format). - string updated_at = 7; +```protobuf +// GetPageComponent retrieves a single component by ID. +rpc GetPageComponent(GetPageComponentRequest) returns (GetPageComponentResponse) { + option idempotency_level = NO_SIDE_EFFECTS; + option (gnostic.openapi.v3.operation) = { + description: "Returns a single status-page component by its ID, scoped to the authenticated workspace. Use this to resolve a component id to its name (and other fields) instead of fetching the whole page via GetStatusPageContent and filtering .components." + }; } ``` -### File 2: `service.proto` +Add the messages in the **Component Management Messages** section: ```protobuf -syntax = "proto3"; - -package openstatus.status_report.v1; - -import "buf/validate/validate.proto"; -import "openstatus/status_report/v1/status_report.proto"; - -option go_package = "github.com/openstatushq/openstatus/packages/proto/openstatus/status_report/v1;statusreportv1"; - -// StatusReportService provides CRUD operations for status reports. -service StatusReportService { - // CreateStatusReport creates a new status report. - rpc CreateStatusReport(CreateStatusReportRequest) returns (CreateStatusReportResponse); - - // GetStatusReport retrieves a specific status report by ID (includes full update timeline). - rpc GetStatusReport(GetStatusReportRequest) returns (GetStatusReportResponse); - - // ListStatusReports returns all status reports for the workspace (metadata only). - rpc ListStatusReports(ListStatusReportsRequest) returns (ListStatusReportsResponse); - - // UpdateStatusReport updates the metadata of a status report (title, page, monitors). - rpc UpdateStatusReport(UpdateStatusReportRequest) returns (UpdateStatusReportResponse); - - // DeleteStatusReport removes a status report and all its updates. - rpc DeleteStatusReport(DeleteStatusReportRequest) returns (DeleteStatusReportResponse); - - // AddStatusReportUpdate adds a new update to an existing status report timeline. - rpc AddStatusReportUpdate(AddStatusReportUpdateRequest) returns (AddStatusReportUpdateResponse); -} - -// --- Create Status Report --- - -message CreateStatusReportRequest { - // Title of the status report (required). - string title = 1 [(buf.validate.field).string.min_len = 1]; - - // Initial status (required). - StatusReportStatus status = 2 [(buf.validate.field).enum.defined_only = true]; - - // Initial message describing the incident (required). - string message = 3 [(buf.validate.field).string.min_len = 1]; - - // Date when the event occurred (RFC 3339 format, required). - string date = 4 [(buf.validate.field).string.min_len = 1]; - - // Page component IDs to associate with this report (required). - repeated string page_component_ids = 5 [(buf.validate.field).repeated.min_items = 1]; -} - -message CreateStatusReportResponse { - // The created status report. - StatusReport status_report = 1; -} - -// --- Get Status Report --- - -message GetStatusReportRequest { - // ID of the status report to retrieve (required). +// GetPageComponentRequest is the request to fetch a single component by ID. +message GetPageComponentRequest { + // ID of the component to retrieve (required). string id = 1 [(buf.validate.field).string.min_len = 1]; } -message GetStatusReportResponse { - // The requested status report. - StatusReport status_report = 1; +// GetPageComponentResponse is the response containing the component. +message GetPageComponentResponse { + // The requested component. + PageComponent component = 1; } +``` -// --- List Status Reports --- - -message ListStatusReportsRequest { - // Maximum number of reports to return (1-100, defaults to 50). - optional int32 limit = 1 [(buf.validate.field).int32 = { - gte: 1 - lte: 100 - }]; - - // Number of reports to skip for pagination (defaults to 0). - optional int32 offset = 2 [(buf.validate.field).int32.gte = 0]; - - // Filter by status (optional). If empty, returns all statuses. - repeated StatusReportStatus statuses = 3; -} - -message ListStatusReportsResponse { - // List of status reports (metadata only, use GetStatusReport for full details). - repeated StatusReportSummary status_reports = 1; - - // Total number of reports matching the filter. - int32 total_size = 2; -} - -// --- Update Status Report --- - -message UpdateStatusReportRequest { - // ID of the status report to update (required). - string id = 1 [(buf.validate.field).string.min_len = 1]; - - // New title for the report (optional). - optional string title = 2; +## 2. Regenerate + sync OpenAPI - // New list of page component IDs (optional, replaces existing list). - repeated string page_component_ids = 3; -} +From `packages/proto`: -message UpdateStatusReportResponse { - // The updated status report. - StatusReport status_report = 1; -} +```sh +pnpm buf:lint # STANDARD lint must pass +pnpm buf:ts # regenerate gen/ts (server consumes this) +pnpm buf:openapi # refresh gen/openapi.yaml (NO_SIDE_EFFECTS → allow-get) +``` -// --- Delete Status Report --- +Then the **required manual sync** (server serves the static copy via Scalar at +`/openapi`; there is no automated copy script — `static/openapi.yaml` was last +updated by hand alongside the `GetStatusPageOverview` PR #2308): -message DeleteStatusReportRequest { - // ID of the status report to delete (required). - string id = 1 [(buf.validate.field).string.min_len = 1]; -} +```sh +cp packages/proto/gen/openapi.yaml apps/server/static/openapi.yaml +``` -message DeleteStatusReportResponse { - // Whether the deletion was successful. - bool success = 1; -} +Commit the regenerated `gen/ts/**`, `gen/openapi.yaml`, and the updated +`apps/server/static/openapi.yaml` together. -// --- Add Status Report Update --- +## 3. Handler — `apps/server/src/routes/rpc/handlers/status-page/index.ts` -message AddStatusReportUpdateRequest { - // ID of the status report to update (required). - string status_report_id = 1 [(buf.validate.field).string.min_len = 1]; +All dependencies already exist in this file: `getComponentById(id, workspaceId)` +(~line 277), `dbComponentToProto` (imported), `pageComponentNotFoundError` +(imported, resolves to `Code.NotFound` → HTTP 404). - // New status for the report (required). - StatusReportStatus status = 2 [(buf.validate.field).enum.defined_only = true]; +Add to `statusPageServiceImpl`, in the Component Management block: - // Message describing what changed (required). - string message = 3 [(buf.validate.field).string.min_len = 1]; +```ts +async getPageComponent(req, ctx) { + try { + const rpcCtx = getRpcContext(ctx); + const id = req.id?.trim(); + if (!id) throw pageComponentNotFoundError(req.id); - // Optional date for the update. Defaults to current time if not provided. - optional string date = 4; -} + const component = await getComponentById(Number(id), rpcCtx.workspace.id); + if (!component) throw pageComponentNotFoundError(id); -message AddStatusReportUpdateResponse { - // The updated status report with the new update included. - StatusReport status_report = 1; -} + return { component: dbComponentToProto(component) }; + } catch (err) { + toConnectError(err); + } +}, ``` ---- - -## Questions Resolved - -Based on the database schema: - -1. **Status Report Structure**: Uses `id`, `status`, `title`, `workspaceId`, `createdAt`, `updatedAt` -2. **Updates**: Separate table `status_report_update` with timeline entries (id, status, date, message, statusReportId) -3. **Status Values**: `investigating`, `identified`, `monitoring`, `resolved` -4. **Relationships**: Links to PageComponents (many-to-many via `statusReportsToPageComponents` junction table) +`getComponentById` filters by `workspaceId`, so a foreign-workspace id returns +404 with no info leak. The generated `ServiceImpl` +type now *requires* this method — a missing impl fails the build. + +## 4. Tests — `apps/server/src/routes/rpc/handlers/status-page/__tests__/status-page.test.ts` + +New `describe("StatusPageService.GetPageComponent")` block mirroring +`GetStatusPage`, using the existing `connectRequest(method, body, headers)` +helper and the already-seeded `${TEST_PREFIX}-component` (workspace 1): + +| Test | Input | Assert | +|------|-------|--------| +| returns component by ID | `{ id: }`, key `"1"` | 200; body `component.name`/`id`/`pageId`/`type`/`monitorId` correct | +| 401 when no auth key | `{ id: ... }`, no key | 401 | +| 404 for non-existent component | `{ id: "99999" }`, key `"1"` | 404 | +| error when ID is empty | `{ id: "" }`, key `"1"` | 400 (min_len validator) | +| 404 for component in different workspace | insert component in workspace 2, key `"1"` | 404 (cleanup in `finally`) | + +## 5. Verification + +1. `pnpm buf:lint && pnpm buf:ts && pnpm buf:openapi` + the `cp` sync. +2. Typecheck `apps/server` (the `ServiceImpl` type enforces the new method). +3. Run the status-page RPC test suite (5 new cases green). +4. Manual smoke: + ```sh + curl -X POST https:///rpc/openstatus.status_page.v1.StatusPageService/GetPageComponent \ + -H "x-openstatus-key: " -H "content-type: application/json" \ + -d '{"id":""}' + ``` +5. Reply to the customer with the exact POST call above (their `GET /path/{id}` + idea maps to this `/rpc` POST they already use for `GetStatusPageContent`). + +## Files touched + +| File | Change | +|------|--------| +| `packages/proto/api/openstatus/status_page/v1/service.proto` | +1 RPC, +2 messages | +| `packages/proto/gen/ts/**`, `packages/proto/gen/openapi.yaml` | regenerated | +| `apps/server/static/openapi.yaml` | synced copy of `gen/openapi.yaml` | +| `apps/server/src/routes/rpc/handlers/status-page/index.ts` | +1 handler method | +| `apps/server/src/routes/rpc/handlers/status-page/__tests__/status-page.test.ts` | +1 describe block, 5 tests | + +## Explicitly out of scope + +- No `page_component.proto` / message changes (bare component is enough). +- No enrichment (monitor status, group name, computed status). +- No new service verb (inline handler reuse). +- No MCP tool, no tRPC procedure, no legacy v1 REST alias. +- No public slug access path. --- -## Implementation Notes - -1. **Workspace ID**: Not included in request/response as it should be derived from the authenticated context (API key belongs to workspace) -2. **Timestamps**: Use RFC 3339 format strings for interoperability -3. **Pagination**: Uses offset-based pagination with `limit` and `offset` parameters -4. **Validation**: Use `buf.validate` for field validation - ---- - -## Decisions Made - -1. **Delete Operation**: Yes - `DeleteStatusReport` RPC added (cascade deletes all updates) -2. **Update Status Report**: Yes - `UpdateStatusReport` RPC added to modify title/page/monitors without creating a timeline entry -3. **Filtering**: `ListStatusReports` supports filtering by status only (via `statuses` field) -4. **Include Updates**: `ListStatusReports` returns `StatusReportSummary` (metadata only), `GetStatusReport` returns full `StatusReport` with update timeline +## Implementation Checklist + +Ordered by phase. Each phase gates the next (proto must lint before codegen; +codegen must land before the handler typechecks). Checkboxes are individual, +verifiable tasks. + +### Phase 0 — Pre-flight + +- [x] Confirm working dir clean except the expected `M PLAN.md` (jj `@`). +- [x] Confirm `buf` runnable: `packages/proto/node_modules/.bin/buf --version` (≥1.63). *(1.69.0)* +- [x] Re-read the target files before editing so edits apply cleanly: + - [x] `packages/proto/api/openstatus/status_page/v1/service.proto` + - [x] `apps/server/src/routes/rpc/handlers/status-page/index.ts` + - [x] `apps/server/src/routes/rpc/handlers/status-page/__tests__/status-page.test.ts` +- [x] Note the seeded fixture name/id used by the test suite (`${TEST_PREFIX}-component`, workspace 1) for the success-case assertion. *(static component, order 100)* + +### Phase 1 — Proto definition + +- [x] In `service.proto`, add the `GetPageComponent` RPC inside the **Component Management** block of `StatusPageService`, right after `UpdateComponent`. + - [x] Set `option idempotency_level = NO_SIDE_EFFECTS;`. + - [x] Add the `(gnostic.openapi.v3.operation)` description (id→name resolution wording). +- [x] In the **Component Management Messages** section, add `GetPageComponentRequest` with `string id = 1 [(buf.validate.field).string.min_len = 1];`. +- [x] Add `GetPageComponentResponse` with `PageComponent component = 1;`. +- [x] Confirm no new imports needed (`PageComponent`, `buf/validate`, gnostic annotations already imported by this file). +- [x] Do **not** touch `page_component.proto` (bare message reused). + +### Phase 2 — Codegen + OpenAPI sync + +- [x] `cd packages/proto && pnpm buf:lint` → status_page path clean (exit 0). *(pre-existing camelCase violations remain in the untouched `internal/private_location` module — not introduced here.)* +- [x] `pnpm buf:ts` → regenerates `gen/ts/**`. + - [x] Verify `GetPageComponentRequest` / `GetPageComponentResponse` appear in `gen/ts/openstatus/status_page/v1/service_pb.ts`. + - [x] Verify `GetPageComponent` is added to the `StatusPageService` definition. +- [x] `pnpm buf:openapi` → regenerates `gen/openapi.yaml`. + - [x] Verify the new operation is present and, given `NO_SIDE_EFFECTS`, is GET-allowed (`allow-get`). +- [x] `cp packages/proto/gen/openapi.yaml apps/server/static/openapi.yaml` (required manual sync). +- [x] `diff -q packages/proto/gen/openapi.yaml apps/server/static/openapi.yaml` → IDENTICAL. + +### Phase 3 — Handler implementation + +- [x] In `status-page/index.ts`, add the `getPageComponent` method to `statusPageServiceImpl`, in the Component Management block. + - [x] `getRpcContext(ctx)` → workspace. + - [x] `req.id?.trim()`; empty → `pageComponentNotFoundError(req.id)`. + - [x] `getComponentById(Number(id), rpcCtx.workspace.id)`; miss → `pageComponentNotFoundError(id)`. + - [x] Return `{ component: dbComponentToProto(component) }`. + - [x] Wrap in `try/catch` → `toConnectError(err)` (mirror sibling `Get*` handlers). +- [x] Confirm no new imports required (`getComponentById` is a local helper; `dbComponentToProto`, `pageComponentNotFoundError`, `getRpcContext`, `toConnectError` already imported). +- [x] Confirm no service-layer / audit / `requireScope` additions (read-only path). + +### Phase 4 — Tests + +- [x] Add `describe("StatusPageService.GetPageComponent", …)` to `status-page.test.ts`, near the `GetStatusPage` block. +- [x] Test: **returns component by ID** — fetch seeded `${TEST_PREFIX}-component` (key `"1"`) → 200; assert `component.id`, `name`, `pageId`, `type`, `monitorId`. +- [x] Test: **401 when no auth key** — no `x-openstatus-key` → 401. +- [x] Test: **404 for non-existent component** — `{ id: "99999" }`, key `"1"` → 404. +- [x] Test: **400 when ID is empty** — `{ id: "" }`, key `"1"` → 400 (validator). +- [x] Test: **404 for component in different workspace** — insert a `pageComponent` in workspace 2, query with key `"1"` → 404; clean up in `finally`. +- [x] Mirror the existing block's `connectRequest(method, body, headers)` usage and any `beforeAll`/`afterAll` cleanup conventions (TEST_PREFIX). + +### Phase 5 — Verification (non-invasive, no DB writes) + +- [x] `cd packages/proto && pnpm buf:lint` clean for `status_page` (re-run after all edits). Also oxfmt `--check` + oxlint clean on both changed TS files. +- [x] Typecheck `apps/server` (`pnpm check`) → passes; the generated `ServiceImpl` now *requires* `getPageComponent`, so a missing/mis-typed impl fails here. Test file + `packages/proto` also typecheck. +- [x] Do **not** run `pnpm migrate`/`pnpm seed`/`pnpm test` locally (integration tests run in CI on its ephemeral DB). +- [x] Sanity-review the diff surface matches the "Files touched" table (reverted incidental `deno.lock` churn from `deno check`; no other stray files). + +### Phase 6 — Wrap-up + +- [x] Leave all edits in the jj working-copy change `@` — no `jj new`/describe/bookmark, no commit, no push. +- [x] Summarize the diff back to the user (files + line counts). +- [x] Draft the customer reply (do not send): the exact + `POST /rpc/openstatus.status_page.v1.StatusPageService/GetPageComponent` + call with `{ "id": "…" }`, noting it's the same `/rpc` POST pattern they + already use for `GetStatusPageContent`, and that the response `component.name` + resolves the id→name mapping. *(drafted in the final summary)* + +### Definition of done + +- [x] `service.proto` updated; `gen/ts/**` + `gen/openapi.yaml` + `apps/server/static/openapi.yaml` regenerated and in sync. +- [x] `getPageComponent` handler implemented; server typechecks. +- [x] 5 tests added (running deferred to CI). +- [x] buf lint clean (status_page); nothing committed; PLAN.md retained. -- 2.51.2