Skip to content

feat(mcp): rewrite dashboard authoring prompts; expose filters on save tool#2264

Draft
alex-fedotyev wants to merge 9 commits into
mainfrom
alex/HDX-mcp-prompts-overhaul
Draft

feat(mcp): rewrite dashboard authoring prompts; expose filters on save tool#2264
alex-fedotyev wants to merge 9 commits into
mainfrom
alex/HDX-mcp-prompts-overhaul

Conversation

@alex-fedotyev
Copy link
Copy Markdown
Contributor

Summary

I rewrote the three MCP dashboard authoring prompts and added filters to hyperdx_save_dashboard's input schema. The goal: when an agent calls the MCP server to build a dashboard, it picks the right pattern by intent, follows the renderer's actual constraints, and produces a JSON shape that renders correctly on the first try.

What changed in the prompts:

  • create_dashboard now leads with a ten-rule design checklist (RED columns with aliases, per-series numberFormat for durations, groupByColumnsOnLeft for inventory tables, dashboard-level filters instead of per-tile where literals, one-metric-per-tile for metric sources, containers and tabs for grouping). The wall-of-JSON canonical example is gone; the four dashboard_examples patterns carry the concrete shapes. The prompt is shorter and the rules are easier for the model to scan.

  • dashboard_examples is now four verified patterns (service_inventory, service_detail, log_analytics, backend_dependencies) plus the existing infrastructure_sql. Each non-SQL example leads with a "When to use" header and a "Why this shape" note so the model picks by intent, not by surface keyword match. I built and rendered every example on a live dev stack before landing the prompt; that surfaced two design issues now captured as rules:

    • chart-level numberFormat on a table that mixes counts and durations formats every value as a duration. Per-series numberFormat is the fix.
    • metric tiles take exactly one select item. The renderer destructures select[0] and ignores the rest; multi-metric authoring needs one tile per metric.
  • query_guide gains a DASHBOARD FILTERS section that documents the filters: [{ type, name, expression, sourceId, where?, whereLanguage? }] shape, a NUMBER FORMAT section that explains the per-series vs. chart-level distinction, and a PER-TILE TYPE CONSTRAINTS note about the metric one-select rule.

What changed in hyperdx_save_dashboard:

  • New filters input field on the tool's inputSchema. Reuses externalDashboardFilterSchemaWithId from @/utils/zod so the MCP and REST surfaces stay in lockstep and the existing convertExternalFiltersToInternal helper handles the conversion without any translation layer. Same body schema (createDashboardBodySchema / updateDashboardBodySchema) that the v2 REST handler already uses.

Voice pass: every prompt string is now em-dash-free, with a snapshot test guarding regressions.

Drill-down behavior (onClick from a service-inventory row into the service-detail dashboard) is a separate follow-up PR; landing the prompt rewrite first so review surface stays focused.

Test plan

  • yarn workspace @hyperdx/api jest mcp/__tests__/dashboards (51 passed, 0 failed)
  • yarn workspace @hyperdx/api ci:lint (lint + tsc + openapi lint, clean)
  • Filter round-trip: save dashboard with two filters via MCP, get back, update with one renamed filter, fetch again, assert shape
  • Filter rejection: save with bogus sourceId returns 4xx
  • Backward compat: save dashboard without filters returns filters: []
  • Snapshot tests: design checklist present, four patterns listed, each example carries "When to use", zero em-dashes in any prompt string
  • Built every example dashboard on a live dev stack (slot 10) and confirmed every tile renders with realistic synthetic data: 90K traces across 8 services with realistic StatusMessage values, 17K logs across 8 services with severity distribution
  • Em-dash-free prose verified by prose-lint.py on every changed file

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 12, 2026

🦋 Changeset detected

Latest commit: e409a29

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/api Patch
@hyperdx/app Patch
@hyperdx/otel-collector Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview, Comment May 14, 2026 4:27pm

Request Review

@github-actions github-actions Bot added the review/tier-3 Standard — full human review required label May 12, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 12, 2026

🟡 Tier 3 — Standard

Introduces new logic, modifies core functionality, or touches areas with non-trivial risk.

Why this tier:

  • Diff size: 988 production lines changed (Tier 2 max: < 250)

Review process: Full human review — logic, architecture, edge cases.
SLA: First-pass feedback within 1 business day.

Stats
  • Production files changed: 5
  • Production lines changed: 988 (+ 474 in test files, excluded from tier calculation)
  • Branch: alex/HDX-mcp-prompts-overhaul
  • Author: alex-fedotyev

To override this classification, remove the review/tier-3 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 12, 2026

PR Review

✅ No critical issues found.

Nice surface coverage: the create/update filter-id normalization (stripFilterIds for create, assignFilterIds for update) is the right shape to bridge the mcpFiltersParam.partial({ id: true }) input schema with the stricter createDashboardBodySchema / updateDashboardBodySchema, and both branches are exercised by tests. getMissingSources already takes filters, so source-existence is gated correctly. The having clause on mcpTableTileSchema was a real silent-strip risk in the prior round — good catch and matching round-trip test.

Minor (non-blocking) observations:

  • assignFilterIds populates an id for brand-new filters, but convertExternalFiltersToInternal will then immediately generate yet another fresh id (since the assigned id is not in existingFilterIds). Harmless waste, but the assigned id is never the persisted one — fine to leave.
  • mcpFiltersParam uses .partial({ id: true }) over a .strict() schema. Zod preserves strictness through .partial, so unknown keys still reject — confirmed safe.
  • Prompt voice and em-dash guards look thorough; buildSourceSummary regression coverage is welcome.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 12, 2026

E2E Test Results

All tests passed • 178 passed • 3 skipped • 1268s

Status Count
✅ Passed 178
❌ Failed 0
⚠️ Flaky 3
⏭️ Skipped 3

Tests ran across 4 shards in parallel.

View full report →

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 12, 2026

Deep Review

🟡 P2 -- recommended

  • packages/api/src/mcp/__tests__/dashboards.test.ts:1448 -- The new filter test block exercises round-trip and "add new filter on update," but never asserts the contract for omitting filters on update (preserve) or sending filters: [] on update (wipe), and updateDashboard at saveDashboard.ts:453 silently gates that behavior on filters !== undefined.
    • Fix: Add two update-path tests against a seeded dashboard -- one that omits filters and asserts the persisted filters are unchanged, one that sends filters: [] and asserts the persisted array is empty.
    • ce-testing-reviewer, ce-api-contract-reviewer
🔵 P3 nitpicks (8)
  • packages/api/src/mcp/tools/dashboards/saveDashboard.ts:124 -- On the update path, an LLM that resends an existing filter without its id triggers assignFilterIds to mint a fresh ObjectId, which convertExternalFiltersToInternal then mints over again, so the persisted filter ends up with a brand-new id and the prior filter id is silently lost; no consumer keys persistent state by filter id today, so the failure is latent rather than active.
    • Fix: Either document this "no id means new filter" semantic on mcpFiltersParam and add a dedicated test, or change assignFilterIds to fall back to content-matching against existingFilterIds so a re-sent filter without an id preserves the original id.
    • ce-correctness-reviewer, ce-maintainability-reviewer, ce-api-contract-reviewer
  • packages/api/src/mcp/tools/dashboards/schemas.ts:468 -- mcpFiltersParam's description lists the filter shape as { type, name, expression, sourceId, where?, whereLanguage? } with no mention of id, so an LLM has no signal about copy-back behavior between create (id stripped) and update (id preserved when provided, re-minted when missing).
    • Fix: Extend the description to say id is optional, ignored on create, and required-for-preservation on update so callers know to round-trip it from hyperdx_get_dashboard.
    • ce-api-contract-reviewer
  • packages/api/src/mcp/tools/dashboards/saveDashboard.ts:45 -- The tool description doesn't say that on update, omitting filters preserves existing filters while filters: [] wipes them; the asymmetric behavior is only encoded in the filters !== undefined guard at saveDashboard.ts:453.
    • Fix: Add a one-line note to the hyperdx_save_dashboard description (or mcpFiltersParam description) clarifying the omit-to-preserve / empty-array-to-wipe contract.
    • ce-api-contract-reviewer
  • packages/api/src/mcp/__tests__/dashboards.test.ts:1449 -- The new having-clause test only exercises the create path, so a future regression where updateDashboardBodySchema drops having from the table-tile config wouldn't be caught.
    • Fix: Extend the test to also update the saved dashboard with a different having value and assert it persists.
    • ce-testing-reviewer
  • packages/api/src/mcp/__tests__/dashboards.test.ts:1322 -- The "reject filter with missing source" test runs against a create call (no id), so the equivalent check on the update path (saveDashboard.ts:358, which calls the same getMissingSources(sources, tilesWithId, filters) helper) is not directly covered.
    • Fix: Add a parallel test that creates a dashboard, then issues an update with a filter whose sourceId doesn't exist, and asserts the same Could not find source IDs envelope.
    • ce-testing-reviewer
  • packages/api/src/mcp/tools/dashboards/saveDashboard.ts:119 -- stripFilterIds and assignFilterIds cast each filter to ExternalDashboardFilterWithId before destructuring id, but the actual input type is the union ExternalDashboardFilter | ExternalDashboardFilterWithId; the cast is fine today because runtime fields overlap, but it sidesteps the type system and would let a future schema split go unnoticed.
    • Fix: Drop the union and type the parameter as (ExternalDashboardFilter & { id?: string })[] (or rely on the inferred zod schema type) so the destructure is type-checked rather than cast-through.
    • ce-maintainability-reviewer
  • packages/api/src/mcp/tools/dashboards/saveDashboard.ts:102 -- The Create helper section divider sits directly above stripFilterIds and assignFilterIds, but only stripFilterIds is used by create; assignFilterIds is used exclusively by update, which is mildly confusing when scanning by section.
    • Fix: Move both helpers above the Create helper divider into a shared Filter normalization section, or move assignFilterIds next to updateDashboard.
    • ce-maintainability-reviewer
  • packages/api/src/mcp/tools/dashboards/saveDashboard.ts:132 -- assignFilterIds treats an empty-string id as "missing" via id.length > 0, but this branch has no test; a future refactor that flips the check to id != null would silently start trying to persist a filter with id: ''.
    • Fix: Add a small update-path test that sends a filter with id: '' and asserts the persisted filter has a valid, non-empty id.
    • ce-testing-reviewer

Reviewers (5): correctness, testing, maintainability, kieran-typescript, api-contract.

Testing gaps:

  • Update-path filter behavior when filters is omitted (preserve) vs [] (wipe).
  • Update-path having round-trip and update-path filter sourceId rejection.
  • assignFilterIds empty-string id branch and the re-mint-on-no-id case for an existing filter.

alex-fedotyev and others added 6 commits May 14, 2026 00:19
…ilters on save tool

The `create_dashboard` prompt now leads with a ten-rule design checklist
(RED columns with aliases, per-series numberFormat for durations,
groupByColumnsOnLeft for inventory tables, dashboard-level filters
instead of per-tile where literals, one-metric-per-tile for metric
sources, containers and tabs for grouping). The wall-of-JSON canonical
example is gone; the four dashboard_examples patterns carry the
concrete shapes.

The dashboard_examples set is replaced with four verified patterns
(service_inventory, service_detail, log_analytics,
backend_dependencies) plus the existing infrastructure_sql. Each
non-SQL example leads with a "When to use" header and a "Why this
shape" note so the model picks by intent, not by surface keyword
match. Examples were built and rendered on a live dev stack before
landing, which surfaced two design issues now captured as rules in
the prompt:

- chart-level numberFormat on a table that mixes counts and durations
  formats every value as a duration; per-series numberFormat is the fix.
- metric tiles take exactly one select item (renderer destructures
  select[0] and ignores the rest).

The query_guide prompt gains a DASHBOARD FILTERS section that
documents the filters: [{ type, name, expression, sourceId, where?,
whereLanguage? }] shape, a NUMBER FORMAT section that explains the
per-series vs. chart-level distinction, and a PER-TILE TYPE
CONSTRAINTS note about the metric one-select rule.

hyperdx_save_dashboard now accepts `filters` on its input schema,
reusing externalDashboardFilterSchemaWithId so the MCP and REST
surfaces stay in lockstep and the existing
convertExternalFiltersToInternal helper handles the conversion
without translation. Filters round-trip through create, get, and
update.

Voice pass: every prompt string is now em-dash-free, with a snapshot
test guarding regressions.

Drill-down behavior (onClick from a service inventory row into the
service detail dashboard) is a separate follow-up PR; landing the
prompt rewrite first.
The buildSourceSummary helper is the source-list block that
buildCreateDashboardPrompt prepends to the prompt body. It still
carried em-dashes from before the voice pass, so the assembled
`create_dashboard` prompt that the MCP transport returns to a client
contained eight em-dashes even though every builder in content.ts is
clean. Caught by an end-to-end check that fetched the prompt through
the live MCP transport and counted em-dashes in the response payload.
…rompts

claude-review flagged:
- `service_detail` example uses `having: "StatusMessage != ''"` on a
  table tile, but mcpTableTileSchema.config does not include `having`.
  Zod's `.strip()` silently drops the field, so an LLM following the
  example through MCP would save a table that includes empty-message
  rows. Added `having: z.string().max(10000).optional()` mirroring
  externalDashboardTableChartConfigSchema and a round-trip test.
- Heatmap tile's numberFormat describe still referenced
  `output: "time"` while the rest of the file uses `output: "duration"`.
  Aligned to "duration" for consistency with the prompt guidance.

deep-review additionally flagged:
- query_guide told the model that metric tiles take "1 select item with
  metricName + metricType", but mcpTileSelectItemSchema does not expose
  those fields, so the keys are silently stripped. Replaced the metric
  authoring guidance with an explicit "not currently exposed via the MCP
  schema; use raw SQL for infrastructure metrics" note. Same edit on
  the design-checklist rule 9 (replaced with a "replace, not merge"
  update-semantic note that addresses a separate review concern about
  partial-payload data loss on update).
- mcpFiltersParam advertised id as optional via .partial({ id: true }),
  but createDashboardBodySchema rejects id (strict, no id) and
  updateDashboardBodySchema requires id. So an LLM copy-pasting a
  filter from get-dashboard into create, or adding a brand-new filter
  on update without an id, would hit a confusing strict-validation
  rejection. saveDashboard.ts now normalizes per flow: stripFilterIds()
  on create, assignFilterIds() on update.
- Em-dash regression check did not cover buildQueryGuidePrompt (largest
  prompt, most likely to regress) or buildSourceSummary (helpers.ts
  carried em-dashes through the initial PR until fix `3539649e`). Added
  assertions for both.
- Bad-sourceId test asserted only `text.toContain('source')`, which
  matches almost any error string. Tightened to assert
  "Could not find source IDs" plus the literal bad id.
- Numbered-rule check used `prompt.toContain('${i}.')` which would
  match substrings like "1.2s" or "0.000000001". Anchored the check
  to line start (`/^${i}\\. /m`) and scoped it to the DESIGN CHECKLIST
  section.
- DASHBOARD FILTERS and NUMBER FORMAT body content was not asserted
  beyond the heading. Now asserts substantive content (QUERY_EXPRESSION
  / expression / sourceId for filters; factor: 0.000000001 / duration /
  per-series for number format).
- Filter round-trip did not cover the freshly-generated-id branch of
  convertExternalFiltersToInternal at the MCP layer. Added a test that
  ships a new no-id filter on update and asserts saveDashboard assigns
  a fresh id.
…oard

The service_inventory pattern's Services table now carries an onClick that
drills into the partner "Service Detail" dashboard. Using mode: "template"
with the constant template "Service Detail" resolves the target by name
at click time, so the canonical flow (save both dashboards in one shot)
works without needing to thread the detail dashboard's ID back into the
inventory tile.

The onClick filter's expression "ServiceName" matches the service_detail
filter declaration, so the destination dashboard's "Service" dropdown
auto-populates with the clicked row's value rather than asking the user
to re-select it.

Snapshot test asserts the template target, filter expression, and
template field shape so a future refactor cannot silently break the
inventory -> detail drill-down link.
Worked Claude through five starter dashboards on a fresh stack; this pass
folds the patterns it got wrong back into the prompts.

DESIGN CHECKLIST changes:

- Rule 2 (alias) now reads ALIAS EVERY SELECT ITEM, not ALIAS EVERY
  AGGREGATION. Claude landed three number tiles with no alias on the
  quantile() select item; rule 2 as written felt table-specific.
- Rule 10 (containers) is now REQUIRED at five-plus tiles, not a soft
  hint. Claude built five dashboards averaging ten tiles each with
  zero containers when the rule was a SHOULD.
- New rule 11 VALIDATE EVERY TILE AFTER SAVE, separated from the
  workflow step so it has its own anchor in the checklist.
- New rule 12 NO TITLE-RECAP MARKDOWN TILE. Claude added a 24x2
  "About this dashboard" markdown tile to every starter dashboard; the
  renderer styles markdown headings at title-bar scale so the tile
  ate a row of vertical space before the first KPI showed.

TILE TYPE GUIDE: markdown line rewritten to actively discourage the
title-recap habit (use containers/tabs for sectioning, skip the
markdown tile entirely on starters, no headings inside markdown bodies
if you must add one).

QUERY GUIDE additions:

- LUCENE FILTER SYNTAX gains a GOTCHA block: Lucene comparison
  operators and wildcards on dotted attribute paths
  (http.status_code:>=500, http.route:*) parse and save without error
  but fail silently at query time. Workaround: switch to SQL with
  bracket access. This was the cause of every "Error loading chart"
  Claude hit during its run.
- PER-TILE TYPE CONSTRAINTS expanded for metric sources: builder tile
  authoring on metric sources currently renders as "Both table name
  and UUID are empty" at query time. Use a raw SQL tile with explicit
  table reference (otel_metrics_gauge / otel_metrics_sum /
  otel_metrics_histogram) until the builder catches up. Discovery note
  added because metric sources don't publish mapAttributeKeys the way
  log and trace sources do.
- TABLE TILE LINKING gains a GROUPBY ALIASES AND ROW-CLICK TEMPLATES
  subsection. Builder tiles don't expose a groupBy alias today, so
  groupBy: SpanAttributes['http.route'] produces a column name no
  Handlebars template can reference as {{Route}}. Workaround:
  configType: "sql" with explicit AS Route.
- COMMON MISTAKES rule 8 (validate-after-save) tightened to "every
  tile, not just one". Rule 13 (missing alias) and rule 14 (Lucene
  comparison on map paths) added with concrete wrong/correct examples.
  Rules 11/12 onClick entries were duplicate-numbered in the
  cherry-pick; renumbered to 15-18.
- New REFERENCES section at the end of the query guide pins three
  ClickStack / ClickHouse doc URLs: search syntax for Lucene, the SQL
  reference for the where field with whereLanguage:"sql", and the
  sql-visualizations page for the $__timeFilter / $__timeInterval /
  $__filters macros used in raw SQL tiles.

DEFAULT TIME WINDOW: short note in the create prompt that dashboards
open at 15 minutes and the user-side time picker is currently the only
way to widen the window. Tells the agent to surface this to the user
rather than padding individual tile time ranges to compensate.

Snapshot tests extended to lock the new rules in place: design
checklist now iterates 1 to 12, asserts on the strengthened phrasings
("ALIAS EVERY SELECT ITEM", "REQUIRED at five or more tiles",
"VALIDATE EVERY TILE AFTER SAVE", "NO TITLE-RECAP MARKDOWN TILE"),
plus per-section coverage tests for the GROUPBY ALIASES workaround,
the Lucene gotcha (in both LUCENE FILTER SYNTAX and COMMON MISTAKES),
the metric-source workaround text, and the three reference URLs.
…ond pass

Watched Claude run on a fresh stack with the previous prompt revision;
this commit closes the remaining gaps surfaced by that run.

DESIGN CHECKLIST changes:

- Rule 2 (alias) now ships a copy-pasteable number-tile example next
  to the rule body. The prior phrasing got better tables but Claude
  still dropped aliases on every builder number tile across three
  dashboards (P50/P95/P99 latency, Server Requests, Failed Spans, Log
  Errors). Concrete shape next to the text is what makes it stick.
- Rule 10 (containers) now ships a copy-pasteable containers/tabs
  block. The prior REQUIRED phrasing did not bite at all; Claude built
  five dashboards with 9-10 tiles each and zero containers. The model
  reads "REQUIRED" as a soft hint when the next concrete shape is two
  files away in the dashboard_examples prompt. Inline the shape and
  the tile -> containerId wiring directly in the rule.

WORKFLOW changes:

- Now six steps. Step 2 says "list existing dashboards with
  hyperdx_get_dashboard, fetch one or two, learn local idioms before
  designing", which Claude's reflection asked for. Step 4 says
  "sketch tiles, THEN group them into 2-4 containers before assembling
  the save payload" so the container decision happens at the same
  level as picking source kind, not buried in a checklist.

QUERY GUIDE changes:

- LUCENE FILTER SYNTAX gotcha broadened from "comparison and wildcard
  on dotted map-attribute paths" to "ALL Lucene operations on
  map-attribute paths". Claude saw db.system:mongodb (simple equality)
  also fail to translate to SpanAttributes['db.system'] on this round.
  The previous phrasing said "simple equality on a dotted path is
  fine"; that was wrong. The corrected guidance is: use SQL with
  bracket access for any operation on map-attribute keys.

- Added a FUZZY-MATCH NOTE alongside the gotcha: Lucene field:value
  on top-level string columns translates to ilike(field, '%value%'),
  not equality. That is fine for free-text columns but surprising for
  enum-like ones (SpanKind, SeverityText, StatusCode). Claude hit this
  on SpanKind:Server matching broader strings than intended. The
  canonical fix is whereLanguage: "sql" with =.

- COMMON MISTAKES rule 14 rewritten to cover any-operation, with
  db.system:mongodb as the equality example.
- Rule 15 added for the fuzzy-substring trap on top-level enum-like
  columns.
- Rule 16 added for the empty-string trap on map-attribute groupBy:
  ClickHouse map columns return '' for unset keys, so groupBy on a
  sparse attribute produces an empty bucket alongside real values.
  Workaround: where: "SpanAttributes['<key>'] != ''" alongside the
  groupBy. Comes from Claude's MongoDB dashboard work where the empty
  collection-name bucket was the loudest visual noise.
- Renumbered the onClick mistakes from 15-18 to 17-20 to make room.

Tests extended to assert: workflow has six numbered steps and names
the read-existing and group-into-containers steps explicitly; rule 2
carries the Server Requests example; rule 10 carries the kpis/trends/
errors shape; LUCENE FILTER SYNTAX mentions both wildcard and equality
gotchas plus the FUZZY-MATCH NOTE; COMMON MISTAKES has dedicated rules
for the map-attr-any-operation gotcha, the enum-like fuzzy-match
trap, and the empty-string map-attr trap. All 6 example patterns
still save and query cleanly end-to-end (48 tiles checked through
hyperdx_query_tile).
Adds a new rule 3 GROUP BY HAS NO ALIAS HOOK that calls out the
schema limit on the v2 chart config: groupBy is a single expression
string, so the renderer uses it verbatim as the table column header
and as the raw column name in CSV export, tooltips, orderBy, and
onClick template references. A table grouped by
SpanAttributes['http.route'] renders arrayElement(SpanAttributes,
'http.route') as the header. The rule pushes the model toward a
top-level column when one carries the same semantic
(SpanName / ServiceName / SeverityText) and falls back to a raw
SQL tile with AS alias when only a Map attribute is available.

Second-pass dashboards consistently produced ugly Map-expression
headers on App-Performance tables even though every alias rule on
the select side was already in the checklist; the gap was the
missing acknowledgement that groupBy has no alias hook.

Also fixes a pre-existing assertion in dashboards.test.ts left
over from the rewrite: the test expected the phrase "not
currently exposed via the MCP" which is no longer in the
PER-TILE TYPE CONSTRAINTS section. Anchors on the surviving
"Authoring builder tiles on a metric source is not reliable"
and "MCP select-item shape does not carry" phrasing instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/tier-3 Standard — full human review required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants