Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve(integrations/slack): sentry alert on error #6537

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

jasonkuhrt
Copy link
Member

@jasonkuhrt jasonkuhrt commented Feb 19, 2025

Background

https://linear.app/the-guild/issue/CONSOLE-1077/sentry-alert-on-failure

Description

Solution has two parts.

Part 1, generic helpers:

  • Typed errors
  • Generic fetch JSON helper
  • Generic Slack API helper

Part 2, Slack Integration component:

  • New error classes for all cases
  • For each case, create error, send to Sentry, with some context, reuse error for log output and http response text.

Things I left out of this PR:

  • Automatic mapping of an error to sentry event, namely context
  • A messageChain property on our error classes containing the message chain
  • A contextChain property on our error classes containing the message and context chain

Those ideas would maximize captured context of errors sent to sentry events and pino. But I felt it would go too far in this PR to do that.

Checklist

  • Testing

Copy link
Contributor

coderabbitai bot commented Feb 19, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Enhanced Slack integration now offers a smoother and more secure OAuth2 sign‐in experience, making it easier for users to connect their Slack account.
  • Improvements

    • Upgraded network request handling and error messaging delivers more reliable connectivity and clearer feedback during external integrations, ensuring a seamless user experience.

Walkthrough

This pull request introduces a structured approach to JSON fetching and error handling by adding a new asynchronous fetchJson function with integrated Zod schema validation and dedicated error classes. It also enhances the centralized error management system with new contextual and aggregate error classes, adds a helper function for type checking, and expands the public API. Additionally, a new Slack API namespace is provided with OAuth2 authorization URL creation and access request functions, and the Slack integration logic in the server module is refactored with improved error handling and clearer naming conventions.

Changes

File(s) Change Summary
packages/web/app/src/lib/fetch-json.ts Added an asynchronous fetchJson function to fetch JSON data with error handling, schema validation via Zod, and multiple error classes (request and response errors) defined within a dedicated namespace.
packages/web/app/src/lib/kit/{errors.ts, helpers.ts, index_.ts} Introduced new error handling abstractions including ContextualError and TypedAggregateError in the Errors namespace, added the oneOf function with its OneOfCheck type alias, and expanded exports to include errors.
packages/web/app/src/lib/slack-api.ts Added a new SlackAPI namespace featuring OAuth2 authorization URL creation (createOauth2AuthorizeUrl), an OAuth2 access response schema (OAuth2AccessResult), an access payload interface, and the requestOauth2Access method.
packages/web/app/src/server/slack.ts Refactored Slack integration by incorporating new Slack-specific error classes, updating callback parameter handling, utilizing the new SlackAPI utilities for OAuth2 flows, and renaming organizationSlug to organizationId.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant fetchJson
    participant FetchAPI as "fetch()"
    participant JSONParser as "JSON.parse()"
    participant ZodValidator as "Zod.safeParse()"

    Caller->>fetchJson: Call fetchJson(url, requestInit, schema)
    fetchJson->>FetchAPI: Execute fetch(url, requestInit)
    alt Fetch network error (TypeError/DOMException)
        FetchAPI-->>fetchJson: Return error
        fetchJson-->>Caller: Return corresponding FetchJsonRequest* error
    else Fetch successful
        FetchAPI-->>fetchJson: Return Response
        fetchJson->>JSONParser: Parse response as JSON
        alt JSON parse error (SyntaxError/TypeError/DOMException)
            JSONParser-->>fetchJson: Return error
            fetchJson-->>Caller: Return corresponding FetchJsonResponse* error
        else JSON parsed successfully
            alt Schema provided
                fetchJson->>ZodValidator: Validate JSON via safeParse
                alt Validation fails
                    ZodValidator-->>fetchJson: Validation error
                    fetchJson-->>Caller: Return FetchJsonResponseSchemaError
                else Validation succeeds
                    ZodValidator-->>fetchJson: Return validated data
                    fetchJson-->>Caller: Return validated JSON data
                end
            else No schema provided
                fetchJson-->>Caller: Return JSON data
            end
        end
    end
Loading
sequenceDiagram
    participant User
    participant Server
    participant SlackAPI
    participant FetchJson as "fetchJson"
    participant Slack as "Slack OAuth Endpoint"

    User->>Server: Initiate Slack connect request
    Server->>SlackAPI: createOauth2AuthorizeUrl({state, clientId, redirectUrl, scopes})
    SlackAPI-->>Server: Return OAuth2 URL
    Server->>User: Redirect to OAuth2 URL
    User->>Slack: Complete OAuth2 authorization (return code)
    Slack->>Server: Provide authorization code via callback
    Server->>SlackAPI: requestOauth2Access(payload with code, clientId, clientSecret)
    SlackAPI->>FetchJson: POST request to Slack OAuth endpoint
    FetchJson-->>SlackAPI: Return JSON response or error
    SlackAPI-->>Server: Return OAuth2AccessResult (access token or error)
    Server->>User: Finalize connection or report error
Loading

Possibly Related PRs


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
hive 5.1.2-alpha-20250219211522-45759d68bcd46566b1c5c1fae5fdcbee805c4f90 npm ↗︎ unpkg ↗︎

Copy link
Contributor

📚 Storybook Deployment

The latest changes are available as preview in: https://e01e7d34.hive-storybook.pages.dev

Comment on lines 36 to 156
slackResult instanceof FetchJsonErrors.FetchJsonRequestTypeError
) {
const error = new SlackIntegrationErrors.SlackAPIRequestError(
{ organizationId },
slackResult,
);
Sentry.captureException(error, {
level: 'error',
tags: sentryTagsRouteLevel,
extra: error.context,
});
throw error;
}

if (!slackResponseResult.success) {
req.log.error('Error parsing data from Slack API (orgId=%s)', organizationSlug);
req.log.error(slackResponseResult.error.toString());
void res.status(400).send('Failed to parse the response from Slack API');
if (slackResult instanceof FetchJsonErrors.FetchJsonResponseError) {
const error = new SlackIntegrationErrors.SlackDefectResponseError(
{ organizationId },
slackResult,
);
Sentry.captureException(error, {
level: 'warning',
tags: sentryTagsRouteLevel,
extra: {
...error.context,
cause: slackResult.cause.message,
},
});
req.log.warn(error.context, error.message);
void res.status(400).send(error.message);
return;
}

if (!slackResponseResult.data.ok) {
req.log.error('Failed to retrieve access token from Slack API (orgId=%s)', organizationSlug);
req.log.error(slackResponseResult.data.error);
void res.status(400).send(slackResponseResult.data.error);
if (!slackResult.ok) {
const error = new SlackIntegrationErrors.TokenRetrieveError({
organizationId,
slackErrorMessage: slackResult.error,
});
Sentry.captureException(error, {
level: 'warning',
tags: sentryTagsRouteLevel,
extra: error.context,
});
req.log.warn(error.context, error.message);
void res.status(400).send(slackResult.error);
return;
}

const token = slackResponseResult.data.access_token;

const result = await graphqlRequest({
const resultGraphql = await graphqlRequest({
url: env.graphqlPublicEndpoint,
headers: {
...req.headers,
'content-type': 'application/json',
'graphql-client-name': 'Hive App',
'graphql-client-version': env.release,
},
operationName: 'SlackIntegration_addSlackIntegration',
document: SlackIntegration_addSlackIntegration,
variables: {
input: {
organizationSlug,
token,
organizationSlug: organizationId,
token: slackResult.access_token,
},
},
});

if (result.errors) {
req.log.error('Failed setting slack token (orgId=%s)', organizationSlug);
for (const error of result.errors) {
req.log.error(error);
if (resultGraphql.errors) {
const resultGraphqlAggError = new Kit.Errors.TypedAggregateError([...resultGraphql.errors]);
const error = new SlackIntegrationErrors.APIRequestError(
{ organizationId },
resultGraphqlAggError,
);
req.log.error(error.context, error.message);
// todo: add base error type with contextChain property
for (const errorCause of error.cause.errors) {
req.log.error(errorCause);
}
throw new Error('Failed setting slack token.');
throw error;
}

void res.redirect(`/${organizationSlug}/view/settings`);
void res.redirect(`/${organizationId}/view/settings`);
});

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
authorization
, but is not rate-limited.

Copilot Autofix AI 25 days ago

To fix the problem, we need to introduce rate limiting to the route handler that performs the OAuth2 authorization. The best way to achieve this is by using a rate-limiting middleware. Since the code uses Fastify, we can use the fastify-rate-limit plugin to limit the number of requests to the /api/slack/callback endpoint.

  1. Install the fastify-rate-limit plugin.
  2. Register the plugin with the Fastify instance.
  3. Apply the rate limiting to the specific route handler.
Suggested changeset 2
packages/web/app/src/server/slack.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/web/app/src/server/slack.ts b/packages/web/app/src/server/slack.ts
--- a/packages/web/app/src/server/slack.ts
+++ b/packages/web/app/src/server/slack.ts
@@ -10,2 +10,3 @@
 import { graphqlRequest } from './utils';
+import fastifyRateLimit from 'fastify-rate-limit';
 
@@ -22,2 +23,6 @@
 export function connectSlack(server: FastifyInstance) {
+  server.register(fastifyRateLimit, {
+    max: 100, // maximum of 100 requests
+    timeWindow: '15 minutes' // per 15 minutes
+  });
   // ----------------------------------
@@ -35,3 +40,3 @@
 
-  server.get('/api/slack/callback', async (req, res) => {
+  server.get('/api/slack/callback', { config: { rateLimit: { max: 5, timeWindow: '1 minute' } } }, async (req, res) => {
     const sentryTagsRouteLevel = {
EOF
@@ -10,2 +10,3 @@
import { graphqlRequest } from './utils';
import fastifyRateLimit from 'fastify-rate-limit';

@@ -22,2 +23,6 @@
export function connectSlack(server: FastifyInstance) {
server.register(fastifyRateLimit, {
max: 100, // maximum of 100 requests
timeWindow: '15 minutes' // per 15 minutes
});
// ----------------------------------
@@ -35,3 +40,3 @@

server.get('/api/slack/callback', async (req, res) => {
server.get('/api/slack/callback', { config: { rateLimit: { max: 5, timeWindow: '1 minute' } } }, async (req, res) => {
const sentryTagsRouteLevel = {
packages/web/app/package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/web/app/package.json b/packages/web/app/package.json
--- a/packages/web/app/package.json
+++ b/packages/web/app/package.json
@@ -142,2 +142,5 @@
     ]
+  },
+  "dependencies": {
+    "fastify-rate-limit": "^5.9.0"
   }
EOF
@@ -142,2 +142,5 @@
]
},
"dependencies": {
"fastify-rate-limit": "^5.9.0"
}
This fix introduces these dependencies
Package Version Security advisories
fastify-rate-limit (npm) 5.9.0 None
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This suggestion is interesting. I wonder if we should throttle people eventually..

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
packages/web/app/src/lib/slack-api.ts (1)

64-68: Consider replacing querystring.stringify with URLSearchParams for improved readability and modern Node.js compatibility.

While querystring still works, Node.js documentation encourages using URLSearchParams as the newer, more standard API. This can simplify migration and keep code consistent with the rest of the file's usage of URL* objects.

Example snippet:

-body: stringify({
-  client_id: payload.clientId,
-  client_secret: payload.clientSecret,
-  code: payload.code,
-}),
+const formData = new URLSearchParams({
+  client_id: payload.clientId,
+  client_secret: payload.clientSecret,
+  code: payload.code,
+});
+body: formData.toString(),
packages/web/app/src/lib/kit/helpers.ts (1)

12-23: Consider optimizing the implementation and improving error messages.

The function is well-typed and provides good type safety. However, consider these improvements:

  1. Use Array.some() for a more functional and potentially more efficient implementation.
  2. Include the actual type of the value in the error message for better debugging.

Consider this implementation:

 export const oneOf = <type extends readonly unknown[]>(
   ...guards: OneOfCheck<type>
 ): ((value: unknown) => type[number]) => {
   return (value: unknown) => {
-    for (const guard of guards) {
-      if (guard(value)) {
-        return value;
-      }
-    }
-    throw new Error(`Unexpected value received by oneOf: ${value}`);
+    if (guards.some(guard => guard(value))) {
+      return value;
+    }
+    throw new Error(
+      `Unexpected value received by oneOf: ${value} (type: ${typeof value})`
+    );
   };
 };
packages/web/app/src/lib/kit/errors.ts (1)

24-32: Consider adding utility methods for error aggregation.

The TypedAggregateError class provides good type safety but could benefit from utility methods for common operations.

Consider adding these utility methods:

 export class TypedAggregateError<$Error extends Error> extends AggregateError {
   constructor(
     public errors: $Error[],
     message?: string,
   ) {
     super(errors, message);
     this.name = this.constructor.name;
   }
+  public hasErrors(): boolean {
+    return this.errors.length > 0;
+  }
+
+  public getErrorMessages(): string[] {
+    return this.errors.map(error => error.message);
+  }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21572b9 and 45759d6.

📒 Files selected for processing (6)
  • packages/web/app/src/lib/fetch-json.ts (1 hunks)
  • packages/web/app/src/lib/kit/errors.ts (1 hunks)
  • packages/web/app/src/lib/kit/helpers.ts (1 hunks)
  • packages/web/app/src/lib/kit/index_.ts (1 hunks)
  • packages/web/app/src/lib/slack-api.ts (1 hunks)
  • packages/web/app/src/server/slack.ts (4 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/web/app/src/lib/kit/index_.ts
🧰 Additional context used
🪛 Biome (1.9.4)
packages/web/app/src/lib/fetch-json.ts

[error] 78-78: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: typescript / typecheck
  • GitHub Check: storybook-preview / deployment
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: alpha / cli-artifacts
  • GitHub Check: code-style / eslint-and-prettier
🔇 Additional comments (4)
packages/web/app/src/server/slack.ts (3)

64-65: Potential missing validation for the organization ID.

The state field is parsed as a generic string and used as organizationId. Ensure it's validated (for example, checking existence or matching the expected format) to avoid malicious or invalid values.


185-192: Scopes delimited by commas in createOauth2AuthorizeUrl may cause Slack OAuth issues.

Because SlackAPI.createOauth2AuthorizeUrl currently uses comma-separated scopes, be aware that Slack typically expects them to be space-separated. This may lead to a mismatch in the authorization request unless Slack has changed its format.


201-246: Solid error-handling approach.

The namespace-based error classes provide clear, structured errors. This consistent pattern helps maintain robust error management across the Slack integration.

packages/web/app/src/lib/kit/helpers.ts (1)

25-27: LGTM!

The type alias is well-designed, using TypeScript's mapped types to correctly constrain the guard functions to their corresponding types in the tuple.

}) => {
const url = new URL('https://slack.com/oauth/v2/authorize');
const searchParams = new URLSearchParams({
scope: parameters.scopes.join(','),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Slack scopes should be space-separated instead of comma-separated.

According to Slack's documentation, the scope parameter expects space-separated scopes rather than comma-separated. Using commas may cause request failures or unexpected behavior.

Apply this diff to fix the scopes parameter:

- scope: parameters.scopes.join(','),
+ scope: parameters.scopes.join(' '),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
scope: parameters.scopes.join(','),
scope: parameters.scopes.join(' '),


export class FetchJsonRequestNetworkError extends Kit.Errors.ContextualError<
'FetchJsonRequestNetworkError',
{},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid using {} as a type.

The lint rule recommends replacing {} with a more explicit shape or Record<string, never> to avoid implying any non-nullable value.

Proposed fix:

-export class FetchJsonRequestNetworkError extends Kit.Errors.ContextualError<
-  'FetchJsonRequestNetworkError',
-  {},
-  DOMException
- > {
+export class FetchJsonRequestNetworkError extends Kit.Errors.ContextualError<
+  'FetchJsonRequestNetworkError',
+  Record<string, never>,
+  DOMException
+> {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{},
export class FetchJsonRequestNetworkError extends Kit.Errors.ContextualError<
'FetchJsonRequestNetworkError',
Record<string, never>,
DOMException
> {
// ...rest of the class implementation
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 78-78: Don't use '{}' as a type.

Prefer explicitly define the object shape. '{}' means "any non-nullable value".

(lint/complexity/noBannedTypes)

Comment on lines +3 to +22
export abstract class ContextualError<
$Name extends string = string,
$Context extends object = object,
$Cause extends Error | undefined = Error | undefined,
> extends Error {
public name: $Name;
public context: $Context;
public cause: $Cause;
constructor(
...args: undefined extends $Cause
? [context: $Context, cause?: $Cause]
: [context: $Context, cause: $Cause]
) {
const [context, cause] = args;
super('Something went wrong.', { cause });
this.name = this.constructor.name as $Name;
this.context = context;
this.cause = cause as $Cause;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider enhancing the error message handling.

The ContextualError class provides a good foundation for contextual errors, but the default error message "Something went wrong." is too generic.

Consider these improvements:

  1. Add an abstract method for generating error messages:
 export abstract class ContextualError<
   $Name extends string = string,
   $Context extends object = object,
   $Cause extends Error | undefined = Error | undefined,
 > extends Error {
   public name: $Name;
   public context: $Context;
   public cause: $Cause;
+  protected abstract formatMessage(): string;
   constructor(
     ...args: undefined extends $Cause
       ? [context: $Context, cause?: $Cause]
       : [context: $Context, cause: $Cause]
   ) {
     const [context, cause] = args;
-    super('Something went wrong.', { cause });
+    super('', { cause });
     this.name = this.constructor.name as $Name;
     this.context = context;
     this.cause = cause as $Cause;
+    this.message = this.formatMessage();
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export abstract class ContextualError<
$Name extends string = string,
$Context extends object = object,
$Cause extends Error | undefined = Error | undefined,
> extends Error {
public name: $Name;
public context: $Context;
public cause: $Cause;
constructor(
...args: undefined extends $Cause
? [context: $Context, cause?: $Cause]
: [context: $Context, cause: $Cause]
) {
const [context, cause] = args;
super('Something went wrong.', { cause });
this.name = this.constructor.name as $Name;
this.context = context;
this.cause = cause as $Cause;
}
}
export abstract class ContextualError<
$Name extends string = string,
$Context extends object = object,
$Cause extends Error | undefined = Error | undefined,
> extends Error {
public name: $Name;
public context: $Context;
public cause: $Cause;
+ protected abstract formatMessage(): string;
constructor(
...args: undefined extends $Cause
? [context: $Context, cause?: $Cause]
: [context: $Context, cause: $Cause]
) {
const [context, cause] = args;
- super('Something went wrong.', { cause });
+ super('', { cause });
this.name = this.constructor.name as $Name;
this.context = context;
this.cause = cause as $Cause;
+ this.message = this.formatMessage();
}
}

@jasonkuhrt
Copy link
Member Author

I don't know how to test Sentry yet in our test suites, will explore that next.

Copy link
Contributor

github-actions bot commented Feb 19, 2025

🐋 This PR was built and pushed to the following Docker images:

Targets: build

Platforms: linux/arm64

Image Tag: 45759d68bcd46566b1c5c1fae5fdcbee805c4f90

Docker Bake metadata
{
"app": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/api/health",
          "build-arg:IMAGE_DESCRIPTION": "The app of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/app",
          "build-arg:PORT": "3000",
          "build-arg:RELEASE": "45759d68bcd46566b1c5c1fae5fdcbee805c4f90",
          "build-arg:SERVICE_DIR_NAME": "@hive/app",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:dockerfile": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:shared": "nf5ka4e3ldtpo0zup2awjjgh0"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ad1f7f5d-ac47-4c57-8963-4f4b35c00438/builder-ad1f7f5d-ac47-4c57-8963-4f4b35c004380/urvflunntxo5ox3ydgnp46dui",
  "containerimage.config.digest": "sha256:709e7062ae150b8a860024e56999e950dc519df21d90ca536f48e8715b72b4a4",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:63859d7e5163cdbb769e316007a8fc665ba9497e1dae2bb32d28f4a0ca3148da",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:63859d7e5163cdbb769e316007a8fc665ba9497e1dae2bb32d28f4a0ca3148da",
  "image.name": "ghcr.io/graphql-hive/app:45759d68bcd46566b1c5c1fae5fdcbee805c4f90-arm64,ghcr.io/graphql-hive/app:console_1077-arm64"
},
"buildx.build.warnings": [
  {
    "vertex": "sha256:43407945f5e31b89d334978cce8ebedc59ef8551e26be9f6177fcb7f18a726b4",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRSRUxFQVNFJyAobGluZSAyMSk=",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 21
        },
        "end": {
          "line": 21
        }
      }
    ]
  },
  {
    "vertex": "sha256:da6edd493d9fd5969f999dd48264ee4a33fb19df10eb78ab43b20b6ededfbb23",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDEwKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 10
        },
        "end": {
          "line": 10
        }
      }
    ]
  },
  {
    "vertex": "sha256:da6edd493d9fd5969f999dd48264ee4a33fb19df10eb78ab43b20b6ededfbb23",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDExKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 11
        },
        "end": {
          "line": 11
        }
      }
    ]
  },
  {
    "vertex": "sha256:da6edd493d9fd5969f999dd48264ee4a33fb19df10eb78ab43b20b6ededfbb23",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRJTUFHRV9USVRMRScgKGxpbmUgMTUp",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 15
        },
        "end": {
          "line": 15
        }
      }
    ]
  },
  {
    "vertex": "sha256:43407945f5e31b89d334978cce8ebedc59ef8551e26be9f6177fcb7f18a726b4",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDIyKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 22
        },
        "end": {
          "line": 22
        }
      }
    ]
  },
  {
    "vertex": "sha256:da6edd493d9fd5969f999dd48264ee4a33fb19df10eb78ab43b20b6ededfbb23",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRSRUxFQVNFJyAobGluZSAxMik=",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 12
        },
        "end": {
          "line": 12
        }
      }
    ]
  },
  {
    "vertex": "sha256:da6edd493d9fd5969f999dd48264ee4a33fb19df10eb78ab43b20b6ededfbb23",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDEyKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 12
        },
        "end": {
          "line": 12
        }
      }
    ]
  },
  {
    "vertex": "sha256:da6edd493d9fd5969f999dd48264ee4a33fb19df10eb78ab43b20b6ededfbb23",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRJTUFHRV9ERVNDUklQVElPTicgKGxpbmUgMTcp",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 17
        },
        "end": {
          "line": 17
        }
      }
    ]
  },
  {
    "vertex": "sha256:43407945f5e31b89d334978cce8ebedc59ef8551e26be9f6177fcb7f18a726b4",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRSRUxFQVNFJyAobGluZSAxMyk=",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 13
        },
        "end": {
          "line": 13
        }
      }
    ]
  },
  {
    "vertex": "sha256:43407945f5e31b89d334978cce8ebedc59ef8551e26be9f6177fcb7f18a726b4",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDIwKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 20
        },
        "end": {
          "line": 20
        }
      }
    ]
  },
  {
    "vertex": "sha256:43407945f5e31b89d334978cce8ebedc59ef8551e26be9f6177fcb7f18a726b4",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRQT1JUJyAobGluZSAyMik=",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 22
        },
        "end": {
          "line": 22
        }
      }
    ]
  },
  {
    "vertex": "sha256:43407945f5e31b89d334978cce8ebedc59ef8551e26be9f6177fcb7f18a726b4",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRJTUFHRV9USVRMRScgKGxpbmUgMTIp",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 12
        },
        "end": {
          "line": 12
        }
      }
    ]
  },
  {
    "vertex": "sha256:43407945f5e31b89d334978cce8ebedc59ef8551e26be9f6177fcb7f18a726b4",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRJTUFHRV9ERVNDUklQVElPTicgKGxpbmUgMTQp",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 14
        },
        "end": {
          "line": 14
        }
      }
    ]
  },
  {
    "vertex": "sha256:43407945f5e31b89d334978cce8ebedc59ef8551e26be9f6177fcb7f18a726b4",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDIxKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 21
        },
        "end": {
          "line": 21
        }
      }
    ]
  }
],
"composition-federation-2": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "Federation 2 Composition Service for GraphQL Hive.",
          "build-arg:IMAGE_TITLE": "graphql-hive/composition-federation-2",
          "build-arg:PORT": "3069",
          "build-arg:RELEASE": "45759d68bcd46566b1c5c1fae5fdcbee805c4f90",
          "build-arg:SERVICE_DIR_NAME": "@hive/external-composition",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:dockerfile": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:shared": "nf5ka4e3ldtpo0zup2awjjgh0"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ad1f7f5d-ac47-4c57-8963-4f4b35c00438/builder-ad1f7f5d-ac47-4c57-8963-4f4b35c004380/tqzc5p3vc68kycky04oerea2r",
  "containerimage.config.digest": "sha256:d17816b570974ace2f6b6d7f08cd8ac0adacfaa2ae23e58a4c7ea3defb18a6ab",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:14f6eedef335a694231cf0cf1e9b1ac96c39be461bfe8998d86bd1bedbdd9f8d",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:14f6eedef335a694231cf0cf1e9b1ac96c39be461bfe8998d86bd1bedbdd9f8d",
  "image.name": "ghcr.io/graphql-hive/composition-federation-2:45759d68bcd46566b1c5c1fae5fdcbee805c4f90-arm64,ghcr.io/graphql-hive/composition-federation-2:console_1077-arm64"
},
"emails": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The emails service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/emails",
          "build-arg:PORT": "3006",
          "build-arg:RELEASE": "45759d68bcd46566b1c5c1fae5fdcbee805c4f90",
          "build-arg:SERVICE_DIR_NAME": "@hive/emails",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:dockerfile": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:shared": "nf5ka4e3ldtpo0zup2awjjgh0"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ad1f7f5d-ac47-4c57-8963-4f4b35c00438/builder-ad1f7f5d-ac47-4c57-8963-4f4b35c004380/oghygyzktg9tgtxcn8kres0q9",
  "containerimage.config.digest": "sha256:188764e09887203324b4b1d6a6662a9f707d3de5bd7ee7ea4186d0af895daaee",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:18b6c33ba844dfcb5d39f1038dce5f1e240d980dd61ba526bd7ab1f1ccca35bf",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:18b6c33ba844dfcb5d39f1038dce5f1e240d980dd61ba526bd7ab1f1ccca35bf",
  "image.name": "ghcr.io/graphql-hive/emails:45759d68bcd46566b1c5c1fae5fdcbee805c4f90-arm64,ghcr.io/graphql-hive/emails:console_1077-arm64"
},
"policy": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The policy service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/policy",
          "build-arg:PORT": "3012",
          "build-arg:RELEASE": "45759d68bcd46566b1c5c1fae5fdcbee805c4f90",
          "build-arg:SERVICE_DIR_NAME": "@hive/policy",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:dockerfile": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:shared": "nf5ka4e3ldtpo0zup2awjjgh0"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ad1f7f5d-ac47-4c57-8963-4f4b35c00438/builder-ad1f7f5d-ac47-4c57-8963-4f4b35c004380/ffcc7msg5sql5swqnbvev2tgi",
  "containerimage.config.digest": "sha256:35e5027fcacc5d23d71d0e9e43b8a8bbbd33cc8998a96ea5f5bbb05e66d5ad48",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:b542aafb1f06569be983ced844e47b6cb5607db70d8d0c85bc1a166bc87f507e",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:b542aafb1f06569be983ced844e47b6cb5607db70d8d0c85bc1a166bc87f507e",
  "image.name": "ghcr.io/graphql-hive/policy:45759d68bcd46566b1c5c1fae5fdcbee805c4f90-arm64,ghcr.io/graphql-hive/policy:console_1077-arm64"
},
"rate-limit": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The rate limit service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/rate-limit",
          "build-arg:PORT": "3009",
          "build-arg:RELEASE": "45759d68bcd46566b1c5c1fae5fdcbee805c4f90",
          "build-arg:SERVICE_DIR_NAME": "@hive/rate-limit",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:dockerfile": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:shared": "nf5ka4e3ldtpo0zup2awjjgh0"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ad1f7f5d-ac47-4c57-8963-4f4b35c00438/builder-ad1f7f5d-ac47-4c57-8963-4f4b35c004380/oku71gb61eolnrd6srnvmm7c9",
  "containerimage.config.digest": "sha256:9b4377b0b8d467f57209522a9053544406f2f78fe333ea00f969bf673ab01e55",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:784bdb8cd889ce01a5fead4bff4984efbd5b83715845dfba7a80b21918b3bc12",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:784bdb8cd889ce01a5fead4bff4984efbd5b83715845dfba7a80b21918b3bc12",
  "image.name": "ghcr.io/graphql-hive/rate-limit:45759d68bcd46566b1c5c1fae5fdcbee805c4f90-arm64,ghcr.io/graphql-hive/rate-limit:console_1077-arm64"
},
"schema": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The schema service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/schema",
          "build-arg:PORT": "3002",
          "build-arg:RELEASE": "45759d68bcd46566b1c5c1fae5fdcbee805c4f90",
          "build-arg:SERVICE_DIR_NAME": "@hive/schema",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:dockerfile": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:shared": "nf5ka4e3ldtpo0zup2awjjgh0"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ad1f7f5d-ac47-4c57-8963-4f4b35c00438/builder-ad1f7f5d-ac47-4c57-8963-4f4b35c004380/7euezomxas0bn0mdc63mgneni",
  "containerimage.config.digest": "sha256:ee241083fd6dfc4df69308e47e580e65d9d8bc91ce6fe5d48c797d656facd864",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:98428e5dc86b6fd31d1d809f2e2d9c3013e40821057c65819cac424a75f18d02",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:98428e5dc86b6fd31d1d809f2e2d9c3013e40821057c65819cac424a75f18d02",
  "image.name": "ghcr.io/graphql-hive/schema:45759d68bcd46566b1c5c1fae5fdcbee805c4f90-arm64,ghcr.io/graphql-hive/schema:console_1077-arm64"
},
"server": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The server service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/server",
          "build-arg:PORT": "3001",
          "build-arg:RELEASE": "45759d68bcd46566b1c5c1fae5fdcbee805c4f90",
          "build-arg:SERVICE_DIR_NAME": "@hive/server",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:dockerfile": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:shared": "nf5ka4e3ldtpo0zup2awjjgh0"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ad1f7f5d-ac47-4c57-8963-4f4b35c00438/builder-ad1f7f5d-ac47-4c57-8963-4f4b35c004380/d74vvdwn6gp6i2i60zkusiebj",
  "containerimage.config.digest": "sha256:05fcf89242d3e6101a3b0491afbe315e5e05099103fcacc502f8d399662ccb49",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:154f598156cf04cfd9c1557b146b707c18c14d185854ba15467f51e43a94c80d",
    "size": 2076,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:154f598156cf04cfd9c1557b146b707c18c14d185854ba15467f51e43a94c80d",
  "image.name": "ghcr.io/graphql-hive/server:45759d68bcd46566b1c5c1fae5fdcbee805c4f90-arm64,ghcr.io/graphql-hive/server:console_1077-arm64"
},
"storage": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "migrations.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:IMAGE_DESCRIPTION": "The migrations service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/storage",
          "build-arg:RELEASE": "45759d68bcd46566b1c5c1fae5fdcbee805c4f90",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:dockerfile": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:shared": "nf5ka4e3ldtpo0zup2awjjgh0"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ad1f7f5d-ac47-4c57-8963-4f4b35c00438/builder-ad1f7f5d-ac47-4c57-8963-4f4b35c004380/i8lvhz0wqldccxo6j74013tin",
  "containerimage.config.digest": "sha256:b409f678af3b59e02df4bd0088a03bc4405c5517a0a9af22abaf3f09962d5be0",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:0349fb25e881fd0c9f6ae4385ff4adc1ceeb186bc8ee2292230e0f0f7e932b89",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:0349fb25e881fd0c9f6ae4385ff4adc1ceeb186bc8ee2292230e0f0f7e932b89",
  "image.name": "ghcr.io/graphql-hive/storage:45759d68bcd46566b1c5c1fae5fdcbee805c4f90-arm64,ghcr.io/graphql-hive/storage:console_1077-arm64"
},
"stripe-billing": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The stripe billing service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/stripe-billing",
          "build-arg:PORT": "3010",
          "build-arg:RELEASE": "45759d68bcd46566b1c5c1fae5fdcbee805c4f90",
          "build-arg:SERVICE_DIR_NAME": "@hive/stripe-billing",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:dockerfile": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:shared": "nf5ka4e3ldtpo0zup2awjjgh0"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ad1f7f5d-ac47-4c57-8963-4f4b35c00438/builder-ad1f7f5d-ac47-4c57-8963-4f4b35c004380/to1ote90ptpygdph73uekmvq9",
  "containerimage.config.digest": "sha256:fabb21ecd80a17bf9b1972c19ea11323d5b6020e08776504aa39e3420383f83f",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:2c02af305b3502e0ce0318dfda97912ef872839145b09b5888d24155ee0446c6",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:2c02af305b3502e0ce0318dfda97912ef872839145b09b5888d24155ee0446c6",
  "image.name": "ghcr.io/graphql-hive/stripe-billing:45759d68bcd46566b1c5c1fae5fdcbee805c4f90-arm64,ghcr.io/graphql-hive/stripe-billing:console_1077-arm64"
},
"tokens": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The tokens service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/tokens",
          "build-arg:PORT": "3003",
          "build-arg:RELEASE": "45759d68bcd46566b1c5c1fae5fdcbee805c4f90",
          "build-arg:SERVICE_DIR_NAME": "@hive/tokens",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:dockerfile": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:shared": "nf5ka4e3ldtpo0zup2awjjgh0"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ad1f7f5d-ac47-4c57-8963-4f4b35c00438/builder-ad1f7f5d-ac47-4c57-8963-4f4b35c004380/i1k2tzm9umd496ad4who2zuv1",
  "containerimage.config.digest": "sha256:ee306fbd54f52b64820d0a64af636e594510ed3800db21ba92b28dcc80b92e82",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:1f87d28976fc4e9b62d02385d2c177d79d2e37cd6a29e57f1ad18096149ad10d",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:1f87d28976fc4e9b62d02385d2c177d79d2e37cd6a29e57f1ad18096149ad10d",
  "image.name": "ghcr.io/graphql-hive/tokens:45759d68bcd46566b1c5c1fae5fdcbee805c4f90-arm64,ghcr.io/graphql-hive/tokens:console_1077-arm64"
},
"usage": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The usage ingestor service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/usage",
          "build-arg:PORT": "3006",
          "build-arg:RELEASE": "45759d68bcd46566b1c5c1fae5fdcbee805c4f90",
          "build-arg:SERVICE_DIR_NAME": "@hive/usage",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:dockerfile": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:shared": "nf5ka4e3ldtpo0zup2awjjgh0"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ad1f7f5d-ac47-4c57-8963-4f4b35c00438/builder-ad1f7f5d-ac47-4c57-8963-4f4b35c004380/kv9lhar2khtziyddealpzv1u8",
  "containerimage.config.digest": "sha256:67d799efa633632cf9cec12452d9c53b635ebc27174f5e042fab229883686f20",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:5cf4021ab4f1bfb1c206ace4e8cdd50071343b30a999f52e76d3eab66e900d05",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:5cf4021ab4f1bfb1c206ace4e8cdd50071343b30a999f52e76d3eab66e900d05",
  "image.name": "ghcr.io/graphql-hive/usage:45759d68bcd46566b1c5c1fae5fdcbee805c4f90-arm64,ghcr.io/graphql-hive/usage:console_1077-arm64"
},
"usage-estimator": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The usage estimator service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/usage-estimator",
          "build-arg:PORT": "3008",
          "build-arg:RELEASE": "45759d68bcd46566b1c5c1fae5fdcbee805c4f90",
          "build-arg:SERVICE_DIR_NAME": "@hive/usage-estimator",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:dockerfile": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:shared": "nf5ka4e3ldtpo0zup2awjjgh0"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ad1f7f5d-ac47-4c57-8963-4f4b35c00438/builder-ad1f7f5d-ac47-4c57-8963-4f4b35c004380/qjsfc4y126cs3nv4833ohrdn9",
  "containerimage.config.digest": "sha256:4e0b5c6f1abd5e08e506ccf7925114d1255dd3293d9bbffb380bc8e29eb289e3",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:33a0ed04f4e4bd76059c4d2093dedbfcaa3771d9f57116dcd8468fe956c56c3b",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:33a0ed04f4e4bd76059c4d2093dedbfcaa3771d9f57116dcd8468fe956c56c3b",
  "image.name": "ghcr.io/graphql-hive/usage-estimator:45759d68bcd46566b1c5c1fae5fdcbee805c4f90-arm64,ghcr.io/graphql-hive/usage-estimator:console_1077-arm64"
},
"usage-ingestor": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The usage ingestor service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/usage-ingestor",
          "build-arg:PORT": "3007",
          "build-arg:RELEASE": "45759d68bcd46566b1c5c1fae5fdcbee805c4f90",
          "build-arg:SERVICE_DIR_NAME": "@hive/usage-ingestor",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:dockerfile": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:shared": "nf5ka4e3ldtpo0zup2awjjgh0"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ad1f7f5d-ac47-4c57-8963-4f4b35c00438/builder-ad1f7f5d-ac47-4c57-8963-4f4b35c004380/4ikkr3e489j41tt0qoanbgdun",
  "containerimage.config.digest": "sha256:8fc7362b60bc5c31690639fd7e8029bbeb511d4eab65a9c3c1538e160c702caa",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:e87c9b9f9b579151735df27a871032969307db09475dd1023d6c1ba01c2eeed4",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:e87c9b9f9b579151735df27a871032969307db09475dd1023d6c1ba01c2eeed4",
  "image.name": "ghcr.io/graphql-hive/usage-ingestor:45759d68bcd46566b1c5c1fae5fdcbee805c4f90-arm64,ghcr.io/graphql-hive/usage-ingestor:console_1077-arm64"
},
"webhooks": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The webhooks ingestor service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/webhooks",
          "build-arg:PORT": "3005",
          "build-arg:RELEASE": "45759d68bcd46566b1c5c1fae5fdcbee805c4f90",
          "build-arg:SERVICE_DIR_NAME": "@hive/webhooks",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:dockerfile": "nf5ka4e3ldtpo0zup2awjjgh0",
          "local-sessionid:shared": "nf5ka4e3ldtpo0zup2awjjgh0"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ad1f7f5d-ac47-4c57-8963-4f4b35c00438/builder-ad1f7f5d-ac47-4c57-8963-4f4b35c004380/vq9zv4zw3axgc8zwdv9xf00x6",
  "containerimage.config.digest": "sha256:4004f1e9e9a8705cc63dadef97e87880888baed5319a5c069b2a8873367d9244",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:b8c9b80262bfba5378b655993b33fa67b488ce44c11fd8b4cac0ba40a9d78e97",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:b8c9b80262bfba5378b655993b33fa67b488ce44c11fd8b4cac0ba40a9d78e97",
  "image.name": "ghcr.io/graphql-hive/webhooks:45759d68bcd46566b1c5c1fae5fdcbee805c4f90-arm64,ghcr.io/graphql-hive/webhooks:console_1077-arm64"
}
}

return result.data as any; // z.infer<Exclude<schema, undefined>>;
}

return json as any; // Kit.Json.Value;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: prefer unknown to force us to use validation or cast

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is in the return position of an implementation it would not work and it does not matter, TS just doesn't make it possible to make implementations type safe. At best we could cast it to something matching the return type but its more maintenance for little gain and no increased type safety since it is a cast either way.

},
$Cause extends z.ZodError | SyntaxError | TypeError | DOMException,
> extends Kit.Errors.ContextualError<$Name, $Context, $Cause> {
message = 'Invalid response.';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer if we can provide more insight than this. E.g. should we include the cause's error message?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's all available through the chain data in a structured way. The missing part IMO is error renderers that are up to date with modern error types. Cause is part of the ECMA standard so it's confusing to me that default renderings are still AFAIK failing to raise good context about them. We would just have to add our own where it is needed such as top level catch all or logging middleware, etc.

super('Something went wrong.', { cause });
this.name = this.constructor.name as $Name;
this.context = context;
this.cause = cause as $Cause;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the call to super set this?

@@ -8,3 +8,20 @@ export const tryOr = <$PrimaryResult, $FallbackResult>(
return fallback();
}
};

export const oneOf = <type extends readonly unknown[]>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Please add a description block to make the behavior more obvious when we use this function.
When seeing it used, I wasnt 100% sure it threw, or what type of error it would throw.

return value;
}
}
throw new Error(`Unexpected value received by oneOf: ${value}`);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be a unique error class?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe! Its failure represents a defect not a normal error so we could encode that in a special class for sure.

import { fetchJson } from './fetch-json';

// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace SlackAPI {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

personally im not a fan of namespaces in typescript. I think namespacing by import path is good enough and this just adds an extra layer of complexity.
If typescript had a global namespace like ruby, then namespaces would serve a more important role of course.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer using ESM but I received pushback using that approach on this repo. If the desired style of this repo is ultimately to use types and functions with stringly namespaced identifiers (e.g. slackAPIauthorize(...) instead of `SlackAPI.authorize(...)) then I wasn't aware of that, and I think the DX on that is worse. I usually trade producer complexity for consumer simplicity based on my feeling that complexity should go down, not up, the stack.

// eslint-disable-next-line @typescript-eslint/no-namespace
namespace SlackIntegrationErrors {
export class DisabledError extends Kit.Errors.ContextualError<'DisabledError'> {
message = 'The Slack integration is not enabled.';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any recommendations you could provide in this message for how to correct this if it needs correcting?
e.g. "set environment variable X" or "see "

Copy link
Member Author

@jasonkuhrt jasonkuhrt Mar 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth putting a TODO about that here to keep the discussion going. We could write a catch all documentation here or link to some docs. Since this case is classified, however, upstream error rendering has the potential to switch on this and provide recommendations where it by definition has more context.

Error rendering is a really important thing that isn't addressed in this PR. For example that is where localization would be factored in, I think. Keeping strings out of error classes is generally good. But as you've pointed out in a few places, without good rendering, all that context and structure is just potential energy, not realized.

if (env.slack === null) {
throw new Error('The Slack integration is not enabled.');
const error = new SlackIntegrationErrors.DisabledError({});
Sentry.captureException(error, { tags: sentryTagsRouteLevel });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a request ID or any other identifiers that we could use to identify why/for who this is failing?

@jasonkuhrt
Copy link
Member Author

@jdolle thanks for the feedback. Feel free to take over this PR if you're interested! I'm currently on other tasks now without any plan to return here currently. If you see value here that's great, hope it helps! 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants