Skip to content

feat(actions): HPA/VPA right-sizing audit with Datadog percentiles and Slack review card - #592

Closed
isiddharthsingh wants to merge 13 commits into
mainfrom
sms10221/dev-1415-emmanuel-hpavpa
Closed

feat(actions): HPA/VPA right-sizing audit with Datadog percentiles and Slack review card#592
isiddharthsingh wants to merge 13 commits into
mainfrom
sms10221/dev-1415-emmanuel-hpavpa

Conversation

@isiddharthsingh

@isiddharthsingh isiddharthsingh commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Adds a scheduled action that compares real Datadog usage against the CPU/memory requests, limits, and HPA maxReplicas declared in a team's IaC, opens one PR per materially mis-sized workload, and posts a Slack card with View PR and Dismiss. Aurora never applies anything — the PR is the change and a human merges it.

Seeded enabled=False, so nothing runs on a schedule until someone turns it on.

What's here

  • metric_stats Datadog resource type — Datadog has no time-percentile capability (every documented route is rejected or silently returns zero series, and both target metrics are gauges), so p95 is computed in Python from rolled-up points, behind a _P95_BY_SOURCE dispatch seam with an import-time guard. Returns one compact row per series rather than a single fat dict: _truncate_results keeps a prefix, so today a 973 KB metrics payload becomes count: 0, which is indistinguishable from "no data" and would lead the agent to cut an idle-looking workload. Budgeted at 30 KB against PASS_THROUGH_CHARS (40 K), not MAX_OUTPUT_SIZE (120 K) — above that an LLM summarizer paraphrases percentiles into plausible fiction.
  • hpa_vpa_recommendations table + RLS, with a partial unique index enforcing at most one live proposal per workload.
  • Lifecycle module owning all table access plus close_pull_request as a provider dispatch (GitHub implemented; GitLab/Bitbucket are a table entry each, not a migration — hence the vcs_provider column).
  • Card toolssend_hpa_vpa_recommendation (first agent-callable Slack write tool in the repo) and list_hpa_vpa_recommendations, so the prompt checks cooldown state before opening a PR.
  • Dismiss handler — closes the PR and starts a 30-day cooldown, then rewrites the card with the buttons removed.
  • Prompt + registration — generic; it discovers the metrics and VCS providers rather than assuming them.

Design decisions worth review

  • Dismiss is DB-first, then GitHub. A GitHub failure leaves the rec dismissed with the PR open — deliberate. We never nag about a workload a human rejected, and a stale open PR is visible and closable by hand. GitHub-first would risk closing the PR then losing the cooldown.
  • A merged PR is acceptance, not rejection — status merged, no cooldown, or the next genuine drift goes unreported.
  • First requests.patch in server/. MCP update_pull_request is allowlisted in mcp_tools.py but absent from tool_registry.py, so gate_action denies it in background context. Direct REST is the only reliable path.
  • No tool_registry.py entry and no gate_action call on the card tool. It writes only to Aurora's own RLS table and the channel Aurora created; a registry key would be a dead switch (_is_org_tool_permitted is only consulted inside gate_action, which this native tool never calls), and gate_action denies unconditionally in scheduled runs. The working kill switch is enabled=False on the action.
  • resource_type='metrics' behaviour is unchanged. Its output contract predates this work and the RCA prompts depend on it; only metric_stats auto-picks an interval. A test pins that boundary.
  • Asymmetric sizing rules live in the prompt because they're judgement, not mechanism: memory decreases check observed max (not p95) because the failure mode is an OOM-kill; CPU is symmetric because throttling is recoverable.

Verification

  • 341 tests pass (84 new feature tests; tests/architectural/ unchanged at 67).
  • Triggered the action end-to-end twice against the live stack: clean success runs, and the agent independently built sum:kubernetes.{cpu.usage.total,memory.usage}{*} by {kube_deployment} — the intended sum-across-pods shape — then wrote the living document and correctly issued no recommendations rather than inventing any.
  • Card path exercised live: posted, then updated in place on the same row (no duplicate); cooldown suppression and the materially-worse breakthrough both confirmed; double-click Dismiss is a no-op with dismissed_at unchanged; merge clears the cooldown; a duplicate live claim is blocked by the partial unique index.
  • /slack/interactions: valid signature → 200, bad signature → 403, stale timestamp → 403, malformed UUID → 200 with a clean message.

Two bugs found in review and fixed with regression tests: a NaN metric point silently broke list.sort() and understated max (which guards the OOM rule), and Dismiss closed the PR as the clicker rather than the account that opened it, which would have failed for any org-mate without their own GitHub connection.

Summary by CodeRabbit

  • New Features
    • Added Datadog metric_stats support for percentile-style summaries (p50/p95/p99/max/mean) with interval auto-selection/clamping.
    • Added HPA/VPA right-sizing Slack recommendation cards, including “View PR” and “Dismiss”, plus proposal lifecycle with cooldowns.
    • Introduced a scheduled built-in rightsizing action (disabled by default).
  • Bug Fixes
    • Strengthened read-only Ask mode by denying the right-sizing recommendation send tool.
    • Improved Datadog result truncation and “no data” messaging consistency.
  • Documentation
    • Expanded Datadog metric_stats/interval guidance and truncation rules.
  • Tests
    • Added unit tests for metric_stats and the HPA/VPA recommendation lifecycle.

@isiddharthsingh
isiddharthsingh requested a review from a team as a code owner July 29, 2026 23:55
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The PR adds Datadog metric_stats percentile queries and interval handling, and introduces an HPA/VPA right-sizing recommendation lifecycle spanning scheduled actions, database records, Slack cards, PR dismissal handling, tool access controls, and tests.

Changes

Datadog metric statistics

Layer / File(s) Summary
Metric statistics backend and dispatch
server/chat/backend/agent/tools/datadog_tool.py
Adds metric_stats, interval handling, percentile summaries, grouping, unit handling, output budgets, dispatch, and truncation metadata.
Metric statistics guidance and validation
server/chat/backend/agent/skills/integrations/datadog/SKILL.md, server/chat/backend/agent/tools/cloud_tools.py, server/tests/chat/*, server/tests/conftest.py
Documents metric-stat query rules and tests interval behavior, summarization, JSON safety, truncation, dispatch, and imports.

HPA/VPA recommendation lifecycle

Layer / File(s) Summary
Recommendation storage and lifecycle primitives
server/utils/db/db_utils.py, server/services/actions/hpa_vpa_recommendations.py
Adds the RLS-protected table and helpers for scoring, cooldowns, concurrency, state transitions, listing, and GitHub PR closing.
Scheduled action and tool registration
server/services/actions/*, server/chat/backend/agent/tools/cloud_tools.py
Adds default instructions, a disabled scheduled action, and conditional Slack/GitHub-backed tool registration.
Slack recommendation card tools
server/chat/backend/agent/tools/hpa_vpa_card_tool.py
Adds validated send/list tools, Block Kit rendering, lifecycle updates, Slack posting, and failure compensation.
Slack dismissal interactions
server/routes/slack/slack_events.py
Adds View PR and Dismiss handling, authorization, idempotent dismissal, PR closing, and card rewriting.
Recommendation validation and access coverage
server/tests/services/test_hpa_vpa_recommendations.py, server/chat/backend/agent/access/mode_access_controller.py
Tests lifecycle behavior and blocks recommendation sending in Ask mode.

GitHub API integration documentation

Layer / File(s) Summary
GitHub API call documentation
server/chat/backend/agent/tools/github_rca_tool.py
Clarifies direct REST usage, stdio MCP execution, and authentication credential selection.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • Arvo-AI/aurora#137 — Directly relates to the expanded query_datadog contract and metric_stats handling.
  • Arvo-AI/aurora#257 — Also modifies the Datadog integration contract and resource-type guidance.
  • Arvo-AI/aurora#457 — Also changes dynamic agent tool registration in get_cloud_tools().

Suggested reviewers: beng360, damianloch, oliviertrudeau

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: a new HPA/VPA right-sizing audit with Datadog percentiles and a Slack review card.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sms10221/dev-1415-emmanuel-hpavpa

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@arvo-ai-staging arvo-ai-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Aurora Risk Review

Verdict: RISKY

This is a well-engineered, large feature addition with thoughtful failure handling throughout. One concrete CI signal stands out: the Linters workflow is currently failing on this PR's head SHA (run 30500910222), which is a gate that should pass before merge. All other infrastructure concerns — table creation ordering across containers, import-time assertions, connection handling, and the read-only mode access control — are correctly handled. The feature is seeded disabled, so no scheduled execution risk exists on deploy.

Findings

# Severity File Finding
1 MEDIUM server/chat/backend/agent/tools/hpa_vpa_card_tool.py:1 Linter CI gate is currently failing on this PR's head SHA

Aurora reviews PRs for incident prevention. This is advisory only and does not block merge.

Comment thread server/chat/backend/agent/tools/hpa_vpa_card_tool.py
Comment thread server/chat/backend/agent/tools/hpa_vpa_card_tool.py Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/chat/backend/agent/skills/integrations/datadog/SKILL.md`:
- Line 30: Add a blank line immediately after the headings Resource Types, The
interval Parameter, Percentiles, and Reconciliation vs Sizing in the Datadog
skill documentation, before their following content.
- Around line 42-47: Correct the interval documentation in the “The interval
Parameter” section to state that automatic granularity selection applies only to
metric_stats. Document that omitting interval for metrics uses Datadog’s default
resolution, while retaining the existing millisecond units and clamping guidance
where applicable.

In `@server/chat/backend/agent/tools/datadog_tool.py`:
- Line 530: Update query_datadog’s handler invocation to forward its accepted
**kwargs, including source, alongside interval so _query_metric_stats can
perform its source dispatch and unsupported-source validation; alternatively
remove the unused **kwargs contract, but keep the function signature and
behavior consistent.
- Around line 548-555: The truncated-all branch in the result-building flow must
preserve existing series context in result["note"]. Update the assignment under
“if was_truncated and not truncated_results and results_list” to append the
oversized-item message to any existing note rather than replacing it, while
retaining the truncated_all flag and current guidance.
- Around line 371-382: Update the window_note construction in the
expected_points cap check to distinguish auto-picked intervals from
caller-supplied intervals. When the interval was explicitly supplied, explain
that using a coarser interval can avoid the point cap; retain the
coarsest-supported-interval wording and 250-day guidance only for auto-picked
intervals.

In `@server/chat/backend/agent/tools/hpa_vpa_card_tool.py`:
- Around line 100-102: Update the `vcs_provider` schema and `_validate` flow to
match the providers supported by `_CLOSERS`: either remove GitLab and Bitbucket
from the field description or reject them during validation before any row is
claimed. Ensure unsupported providers fail early instead of allowing cards whose
later Dismiss operation cannot close the PR.

In `@server/routes/slack/slack_events.py`:
- Around line 705-710: Update the dismissal flow containing close_pull_request
to acknowledge Slack within the 3-second budget by moving the close and
card-rewrite work off the request thread through the existing Celery actions
mechanism; at minimum, ensure the close_pull_request invocation uses a short
timeout rather than its 30-second default. Preserve the committed dismissal
transition and existing degraded behavior when the GitHub close times out.

In `@server/services/actions/hpa_vpa_recommendations.py`:
- Around line 381-384: Validate repo_full_name against the expected
owner/repository format before constructing the GitHub URL in the relevant
PR-closing function. Add the required re import and reject invalid values,
including path traversal or extra slash segments, before requests.patch is
called; preserve the existing behavior for valid repository names.

In `@server/services/actions/system_actions.py`:
- Around line 73-76: Replace the assert-based invariant check near
SYSTEM_ACTIONS and _DEFAULT_INSTRUCTIONS with an explicit exception raise when
any system keys lack default instructions, preserving the existing missing-key
calculation and error message so the validation remains enforced under
optimization.

In `@server/tests/services/test_hpa_vpa_recommendations.py`:
- Around line 176-178: Update
test_missing_pr_reference_is_rejected_before_any_network_call to monkeypatch
_CLOSERS["github"] with a sentinel closer that records invocations, then assert
it remains uncalled after both invalid close_pull_request calls while retaining
the existing error assertions.
- Around line 91-100: Update compute_severity_score() to reject boolean metric
values in dimensions before numeric processing, since booleans pass numeric
validation. Extend the parameterized invalid-dimensions tests with boolean
current/recommended values and verify they produce the existing invalid/neutral
severity result without affecting cooldown gate logic.
- Around line 122-128: Update both pytest.mark.parametrize decorators at
server/tests/services/test_hpa_vpa_recommendations.py lines 122-128 and 134-140
to use tuple-form parameter names instead of comma-delimited strings, preserving
the existing test cases and values.

In `@server/utils/db/db_utils.py`:
- Around line 1458-1459: Update the DDL for idx_hpa_vpa_recs_pr in the database
initialization or migration flow to restrict uniqueness to rows with status
proposed, while preserving the existing org_id, repo_full_name, and pr_number
columns and pr_number non-null condition. Add the necessary migration step to
drop the existing broader index before recreating it, since IF NOT EXISTS will
not change an existing definition; leave idx_hpa_vpa_recs_live unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c6cb5dfd-4701-43c5-86d8-de1d806f09a3

📥 Commits

Reviewing files that changed from the base of the PR and between 8ceef9e and 8e038f0.

📒 Files selected for processing (13)
  • server/chat/backend/agent/access/mode_access_controller.py
  • server/chat/backend/agent/skills/integrations/datadog/SKILL.md
  • server/chat/backend/agent/tools/cloud_tools.py
  • server/chat/backend/agent/tools/datadog_tool.py
  • server/chat/backend/agent/tools/github_rca_tool.py
  • server/chat/backend/agent/tools/hpa_vpa_card_tool.py
  • server/routes/slack/slack_events.py
  • server/services/actions/hpa_vpa_action.py
  • server/services/actions/hpa_vpa_recommendations.py
  • server/services/actions/system_actions.py
  • server/tests/chat/test_datadog_metric_stats.py
  • server/tests/services/test_hpa_vpa_recommendations.py
  • server/utils/db/db_utils.py

`query_datadog(resource_type=TYPE, query=QUERY, time_from=START, time_to=END, limit=N)`
`query_datadog(resource_type=TYPE, query=QUERY, time_from=START, time_to=END, limit=N, interval=MS)`

### Resource Types

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

markdownlint MD022: add a blank line after these headings.

### Resource Types, ### The interval Parameter, ### Percentiles, and ### Reconciliation vs Sizing are immediately followed by content.

Also applies to: 42-42, 49-49, 70-70

🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 30-30: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/chat/backend/agent/skills/integrations/datadog/SKILL.md` at line 30,
Add a blank line immediately after the headings Resource Types, The interval
Parameter, Percentiles, and Reconciliation vs Sizing in the Datadog skill
documentation, before their following content.

Source: Linters/SAST tools

Comment on lines +42 to +47
### The `interval` Parameter
`interval` is the rollup granularity in **milliseconds**, and applies to `'metrics'` and
`'metric_stats'`. Omit it and a granularity is auto-picked that keeps each series under
~1000 points -- a 30-day window auto-picks `3600000` (1 hour, 720 points). Values are
clamped to `60000`..`14400000`. Datadog caps a series at 1500 points, so a long window
with a fine interval returns less than you asked for; prefer the auto-pick.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Auto-pick claim is wrong for 'metrics'.

_query_metrics deliberately does not auto-pick — omitting interval leaves Datadog's own default resolution (see datadog_tool.py Lines 151-156 and test_plain_metrics_does_not_auto_pick_an_interval). Only metric_stats auto-picks. As written, the agent will assume a ~1000-point rollup for plain metrics queries.

📝 Proposed doc fix
 `interval` is the rollup granularity in **milliseconds**, and applies to `'metrics'` and
-`'metric_stats'`. Omit it and a granularity is auto-picked that keeps each series under
-~1000 points -- a 30-day window auto-picks `3600000` (1 hour, 720 points). Values are
-clamped to `60000`..`14400000`. Datadog caps a series at 1500 points, so a long window
-with a fine interval returns less than you asked for; prefer the auto-pick.
+`'metric_stats'`. Values are clamped to `60000`..`14400000`.
+
+- `'metric_stats'`: omit it and a granularity is auto-picked that keeps each series under
+  ~1000 points -- a 30-day window auto-picks `3600000` (1 hour, 720 points).
+- `'metrics'`: omitting it leaves Datadog's own default resolution in place; no auto-pick.
+
+Datadog caps a series at 1500 points, so a long window with a fine interval returns less
+than you asked for; for `metric_stats` prefer the auto-pick.
📝 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
### The `interval` Parameter
`interval` is the rollup granularity in **milliseconds**, and applies to `'metrics'` and
`'metric_stats'`. Omit it and a granularity is auto-picked that keeps each series under
~1000 points -- a 30-day window auto-picks `3600000` (1 hour, 720 points). Values are
clamped to `60000`..`14400000`. Datadog caps a series at 1500 points, so a long window
with a fine interval returns less than you asked for; prefer the auto-pick.
### The `interval` Parameter
`interval` is the rollup granularity in **milliseconds**, and applies to `'metrics'` and
`'metric_stats'`. Values are clamped to `60000`..`14400000`.
- `'metric_stats'`: omit it and a granularity is auto-picked that keeps each series under
~1000 points -- a 30-day window auto-picks `3600000` (1 hour, 720 points).
- `'metrics'`: omitting it leaves Datadog's own default resolution in place; no auto-pick.
Datadog caps a series at 1500 points, so a long window with a fine interval returns less
than you asked for; for `metric_stats` prefer the auto-pick.
🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 42-42: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/chat/backend/agent/skills/integrations/datadog/SKILL.md` around lines
42 - 47, Correct the interval documentation in the “The interval Parameter”
section to state that automatic granularity selection applies only to
metric_stats. Document that omitting interval for metrics uses Datadog’s default
resolution, while retaining the existing millisecond units and clamping guidance
where applicable.

Comment thread server/chat/backend/agent/tools/datadog_tool.py Outdated
Comment thread server/chat/backend/agent/tools/datadog_tool.py Outdated
Comment thread server/chat/backend/agent/tools/datadog_tool.py Outdated
Comment thread server/services/actions/system_actions.py Outdated
Comment thread server/tests/services/test_hpa_vpa_recommendations.py
Comment on lines +122 to +128
@pytest.mark.parametrize("new,prior", [
(None, 0.60), # unknown new score
(0.90, None), # unknown prior score
(0.90, 0.0), # unusable prior
(0.90, -1.0),
("0.9", 0.60), # non-numeric
])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
git ls-files | rg '(^|/)test_hpa_vpa_recommendations\.py$|pyproject\.toml|pysnooper|ruff' || true

echo
echo "== target occurrences =="
python3 - <<'PY'
from pathlib import Path
p=Path('server/tests/services/test_hpa_vpa_recommendations.py')
if p.exists():
    lines=p.read_text().splitlines()
    for start,end in [(122,128),(134,140)]:
        print(f'-- {p}:{start}-{end}')
        for i in range(start, min(end, len(lines))+1):
            print(f'{i:4}: {lines[i-1]}')
else:
    print('missing target file')
PY

echo
echo "== ruff config =="
for f in pyproject.toml setup.cfg tox.ini.cfg; do
  if [ -f "$f" ]; then
    echo "-- $f"
    sed -n '1,220p' "$f" | rg -n "ruff|flake8-pytest|PT006|select|ignore|lint" -C 2 || true
  fi
done

echo
echo "== all annotated `@pytest.mark.parametrize` declarations =="
python3 - <<'PY'
from pathlib import Path
roots=['server/tests']
for root in roots:
    for path in Path(root).rglob('*.py'):
        text=path.read_text(errors='replace')
        if '`@pytest.mark.parametrize`' not in text:
            continue
        for n,line in enumerate(text.splitlines(),1):
            if '`@pytest.mark.parametrize`' in line or (n>1 and 'parametrize' in line):
                for i,l in enumerate(text.splitlines()[max(0,n-3):n+5], max(0,n-3)+1):
                    print(f'{path}:{i}: {l}')
                print('---')
PY

Repository: Arvo-AI/aurora

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Ruff availability and PT006 semantics probe =="
if command -v ruff >/dev/null 2>&1; then
  ruff --version
  cat >/tmp/ruff_pt006_probe.py <<'PY'
import pytest

# Ruff's PT006 only flags invalid parameter name patterns.
# It does not prohibit values like None, floats, strings, inf/nan.
def test_none(v):
    pass

def test_float(v):
    pass

def test_annotated(v: float):
    pass
PY

  tmpdir="$(mktemp -d)"
  cat > "$tmpdir/pyproject.toml" <<'TOML'
[project]
name = "probe"
requires-python = ">=3.10"

[tool.ruff]
select = ["PT006"]
TOML
  (cd "$tmpdir" && ruff check /tmp/ruff_pt006_probe.py --config pyproject.toml || true)
else
  echo "ruff not installed"
fi

echo
echo "== repository pytest/ruff related dependency declarations =="
git ls-files | rg '(^|/)(requirements.*\.txt|pyproject\.toml|setup\.cfg|setup\.py|Pipfile|poetry\.lock|uv\.lock)' | while read -r f; do
  echo "-- $f"
  rg -n "pytest|ruff|flake8-pytest|select|ignore" "$f" -C 1 || true
done

Repository: Arvo-AI/aurora

Length of output: 644


Fix the parameter naming format in both decorators. Convert the comma-delimited parameter names to tuple form to match the expected lint/style format.

@server/tests/services/test_hpa_vpa_recommendations.py:122
@server/tests/services/test_hpa_vpa_recommendations.py:134

🧰 Tools
🪛 Ruff (0.16.0)

[warning] 122-122: Wrong type passed to first argument of pytest.mark.parametrize; expected tuple

Use a tuple for the first argument

(PT006)

📍 Affects 1 file
  • server/tests/services/test_hpa_vpa_recommendations.py#L122-L128 (this comment)
  • server/tests/services/test_hpa_vpa_recommendations.py#L134-L140
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/tests/services/test_hpa_vpa_recommendations.py` around lines 122 -
128, Update both pytest.mark.parametrize decorators at
server/tests/services/test_hpa_vpa_recommendations.py lines 122-128 and 134-140
to use tuple-form parameter names instead of comma-delimited strings, preserving
the existing test cases and values.

Source: Linters/SAST tools

Comment thread server/tests/services/test_hpa_vpa_recommendations.py Outdated
Comment thread server/utils/db/db_utils.py Outdated

@arvo-ai-staging arvo-ai-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Aurora Risk Review — Latest changes

Verdict: SAFE

No new incident risk in the latest changes.


Aurora reviews PRs for incident prevention.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/tests/conftest.py`:
- Around line 149-160: Change the stub setup loop to evaluate installation
status for each entry in _STUBBED_SUBMODULES rather than skipping the entire
namespace based on _is_installed(_ns). Ensure missing google.* submodules are
stubbed even when the google namespace is provided by another installed
distribution, while preserving the existing real-package and
_STUB_WHEN_GOOGLE_ABSENT behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3ecccc74-4305-45c5-acdf-a4a6d295d2b1

📥 Commits

Reviewing files that changed from the base of the PR and between 8e038f0 and 170e7fb.

📒 Files selected for processing (1)
  • server/tests/conftest.py

Comment thread server/tests/conftest.py
Comment on lines +149 to +160
for _ns in _STUBBED_NAMESPACES:
if _is_installed(_ns):
continue # real package installed -- never mask it
for _mod in _STUBBED_SUBMODULES:
if _mod.split(".")[0] == _ns:
_stub(_mod)
if _ns == "google":
# These wrappers import google.* at module scope, so they cannot load
# against stubs even when they are themselves installed.
for _wrapper in _STUB_WHEN_GOOGLE_ABSENT:
sys.modules.pop(_wrapper, None)
sys.modules[_wrapper] = _StubModule(_wrapper)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Namespace-level _is_installed check may mask missing sub-packages under google.*.

_is_installed("google") returns True as soon as any google.* distribution (e.g., google-auth) provides the namespace, causing the whole submodule loop to be skipped — even when a different distribution under the same namespace (e.g., google-genai providing google.genai, or google-cloud-bigquery providing google.cloud.bigquery) is actually absent. Unlike google.generativeai in google_provider.py (which has its own try/except ImportError), other google.* consumers may not be guarded and would hit a real ModuleNotFoundError in that partial-install scenario.

Consider gating per-submodule instead of per-namespace-root:

🔧 Proposed fix for per-submodule gating
 for _ns in _STUBBED_NAMESPACES:
-    if _is_installed(_ns):
-        continue  # real package installed -- never mask it
     for _mod in _STUBBED_SUBMODULES:
-        if _mod.split(".")[0] == _ns:
-            _stub(_mod)
+        if _mod.split(".")[0] == _ns and not _is_installed(_mod):
+            _stub(_mod)
     if _ns == "google":
-        # These wrappers import google.* at module scope, so they cannot load
-        # against stubs even when they are themselves installed.
-        for _wrapper in _STUB_WHEN_GOOGLE_ABSENT:
-            sys.modules.pop(_wrapper, None)
-            sys.modules[_wrapper] = _StubModule(_wrapper)
+        # These wrappers import google.genai.* at module scope, so they cannot
+        # load if that specific submodule is missing, even when `google` itself
+        # resolves via another distribution.
+        if not _is_installed("google.genai"):
+            for _wrapper in _STUB_WHEN_GOOGLE_ABSENT:
+                sys.modules.pop(_wrapper, None)
+                sys.modules[_wrapper] = _StubModule(_wrapper)

If the slim CI environment always installs/omits all google.* distributions atomically (never partially), this gap is theoretical — worth confirming.

📝 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
for _ns in _STUBBED_NAMESPACES:
if _is_installed(_ns):
continue # real package installed -- never mask it
for _mod in _STUBBED_SUBMODULES:
if _mod.split(".")[0] == _ns:
_stub(_mod)
if _ns == "google":
# These wrappers import google.* at module scope, so they cannot load
# against stubs even when they are themselves installed.
for _wrapper in _STUB_WHEN_GOOGLE_ABSENT:
sys.modules.pop(_wrapper, None)
sys.modules[_wrapper] = _StubModule(_wrapper)
for _ns in _STUBBED_NAMESPACES:
for _mod in _STUBBED_SUBMODULES:
if _mod.split(".")[0] == _ns and not _is_installed(_mod):
_stub(_mod)
if _ns == "google":
# These wrappers import google.genai.* at module scope, so they cannot
# load if that specific submodule is missing, even when `google` itself
# resolves via another distribution.
if not _is_installed("google.genai"):
for _wrapper in _STUB_WHEN_GOOGLE_ABSENT:
sys.modules.pop(_wrapper, None)
sys.modules[_wrapper] = _StubModule(_wrapper)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/tests/conftest.py` around lines 149 - 160, Change the stub setup loop
to evaluate installation status for each entry in _STUBBED_SUBMODULES rather
than skipping the entire namespace based on _is_installed(_ns). Ensure missing
google.* submodules are stubbed even when the google namespace is provided by
another installed distribution, while preserving the existing real-package and
_STUB_WHEN_GOOGLE_ABSENT behavior.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
server/routes/slack/slack_events.py (3)

634-693: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Enforce centralized RBAC for dismissal authorization.

This flow authorizes the action through manual Slack-user and organization checks, but does not apply the required centralized permission check. Any linked member of the organization can therefore dismiss a recommendation and trigger PR closure regardless of application permissions. Protect the invoking route with @require_permission or the repository’s centralized Slack-compatible RBAC equivalent, while retaining the RLS/org check as defense in depth.

As per path instructions, routes under server/routes/** must use @require_permission and must not perform manual auth checks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/routes/slack/slack_events.py` around lines 634 - 693, Apply the
centralized `@require_permission` decorator to the Slack dismissal route
containing the shown flow, using the permission required for dismissing
recommendations. Remove the manual Slack-user authentication and related warning
logic from this route, while retaining the RLS/org ownership check as defense in
depth; preserve the existing dismissal transition behavior for authorized
callers.

Source: Path instructions


745-796: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Add recovery for failed dismissed-card rewrites.

If update_message fails, the database row is already dismissed and this helper only logs the failure. A later click hits the idempotent no-op path, so the original card can retain a live Dismiss button permanently without any way to repair it. Persist a rewrite-needed state or enqueue a retry; alternatively, make the already-dismissed path idempotently rebuild the card.

Based on the status = 'proposed' transition contract, subsequent clicks cannot re-enter the rewrite path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/routes/slack/slack_events.py` around lines 745 - 796, The
dismissed-card flow must recover when _rewrite_dismissed_card or its
client.update_message call fails. Persist a rewrite-needed state or enqueue a
retry, and ensure the already-dismissed/idempotent click path can rebuild the
button-free card; do not rely solely on logging because status='proposed'
prevents subsequent clicks from re-entering the rewrite path.

720-734: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Do not claim that no cooldown was applied when mark_merged fails.

The initial dismissal transition has already committed the cooldown. If mark_merged raises, the row remains dismissed/cooling down, but the card still says “No cooldown applied.” Track whether the merge-state update succeeded and show an indeterminate/error status when it did not.

Based on the committed dismissal transition, the recommendation already carries cooldown state when this branch begins.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/routes/slack/slack_events.py` around lines 720 - 734, Update the
already_merged branch around mark_merged to track whether the merge-state update
succeeds. Since the dismissal transition has already committed the cooldown,
retain that state and change status_line to report an indeterminate/error
outcome when mark_merged raises instead of claiming no cooldown was applied;
keep the existing success message only for a successful mark_merged call.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/chat/backend/agent/tools/datadog_tool.py`:
- Around line 377-392: Update the window_note branching around
_clamp_interval(interval) so a caller-pinned interval that clamps to
_MAX_INTERVAL_MS receives the existing “coarsest supported interval” guidance
instead of the coarser-interval suggestion. Preserve the current pinned-interval
guidance only when a smaller supported interval remains available, and keep the
auto-selected path unchanged.

In `@server/routes/slack/slack_events.py`:
- Around line 39-44: Move the close-pull-request and message-rewrite operations
out of the Slack interaction request handler and enqueue them on the existing
Celery task mechanism before returning the acknowledgement response. Ensure the
handler returns 200 immediately after the dismissal transition is committed,
while the background task performs close_pull_request and update_message with
the existing failure behavior; update the _GITHUB_CLOSE_TIMEOUT usage only as
needed for that asynchronous flow.
- Line 652: Update the logger.exception calls in the unauthenticated Slack-user
warning paths around the relevant handlers to use lazy percent-style formatting,
passing the sanitized slack_user_id as a logging argument. Remove the appended
caught exception because logger.exception includes it automatically, and apply
the same change to all occurrences at the referenced locations.

In `@server/tests/services/test_hpa_vpa_recommendations.py`:
- Around line 182-189: In
test_missing_pr_reference_is_rejected_before_any_network_call, update the
sentinel lambda registered in _CLOSERS so its unused keyword-argument parameter
is named _kwargs or captured with **_. Apply the same unused-parameter rename to
the other sentinel lambda in this test file, without changing behavior.

---

Outside diff comments:
In `@server/routes/slack/slack_events.py`:
- Around line 634-693: Apply the centralized `@require_permission` decorator to
the Slack dismissal route containing the shown flow, using the permission
required for dismissing recommendations. Remove the manual Slack-user
authentication and related warning logic from this route, while retaining the
RLS/org ownership check as defense in depth; preserve the existing dismissal
transition behavior for authorized callers.
- Around line 745-796: The dismissed-card flow must recover when
_rewrite_dismissed_card or its client.update_message call fails. Persist a
rewrite-needed state or enqueue a retry, and ensure the
already-dismissed/idempotent click path can rebuild the button-free card; do not
rely solely on logging because status='proposed' prevents subsequent clicks from
re-entering the rewrite path.
- Around line 720-734: Update the already_merged branch around mark_merged to
track whether the merge-state update succeeds. Since the dismissal transition
has already committed the cooldown, retain that state and change status_line to
report an indeterminate/error outcome when mark_merged raises instead of
claiming no cooldown was applied; keep the existing success message only for a
successful mark_merged call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8032225e-9aef-44d3-b789-ffe154f211b1

📥 Commits

Reviewing files that changed from the base of the PR and between 170e7fb and b9c5f89.

📒 Files selected for processing (9)
  • server/chat/backend/agent/tools/datadog_tool.py
  • server/chat/backend/agent/tools/hpa_vpa_card_tool.py
  • server/routes/slack/slack_events.py
  • server/services/actions/hpa_vpa_recommendations.py
  • server/services/actions/system_actions.py
  • server/tests/chat/test_datadog_metric_stats.py
  • server/tests/conftest.py
  • server/tests/services/test_hpa_vpa_recommendations.py
  • server/utils/db/db_utils.py

Comment thread server/chat/backend/agent/tools/datadog_tool.py Outdated
Comment on lines +39 to +44
# Slack expects an interaction response within ~3s. The dismissal transition is
# already committed before this call, so a timeout only costs the automatic PR
# close (surfaced on the card as "could not be closed automatically"), never the
# cooldown. Kept short for that reason; moving the close and card rewrite onto a
# Celery task would remove the constraint entirely and is the better long-term fix.
_GITHUB_CLOSE_TIMEOUT = 5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major

Five seconds still exceeds Slack’s acknowledgement budget.

The handler waits for close_pull_request and then performs update_message before returning 200. A slow provider can still exceed Slack’s roughly 3-second response window, causing retries or a failed interaction after the dismissal is already committed. Move close/rewrite work off the request thread; a shorter timeout alone is only a mitigation.

This is the same unresolved Slack acknowledgement-budget issue noted in the previous review.

Also applies to: 712-718

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/routes/slack/slack_events.py` around lines 39 - 44, Move the
close-pull-request and message-rewrite operations out of the Slack interaction
request handler and enqueue them on the existing Celery task mechanism before
returning the acknowledgement response. Ensure the handler returns 200
immediately after the dismissal transition is committed, while the background
task performs close_pull_request and update_message with the existing failure
behavior; update the _GITHUB_CLOSE_TIMEOUT usage only as needed for that
asynchronous flow.

Comment thread server/routes/slack/slack_events.py Outdated
Comment thread server/tests/services/test_hpa_vpa_recommendations.py
@isiddharthsingh

Copy link
Copy Markdown
Contributor Author

Worked through the SonarQube and CodeRabbit findings. Fixed the valid ones; below are the ones I'm deliberately leaving, with reasons.

Fixed in 2e2679cf

  • S8572 — one logger.error(..., exc_info=True) in _send_ephemeral I missed last round; now logger.exception.
  • Lazy logging — all four logger.exception calls converted to %s args and the redundant caught-exception object dropped (exception() already logs the traceback).
  • window_note at the interval ceiling — real gap in my previous fix. It only checked whether the caller pinned an interval, not whether that interval was already _MAX_INTERVAL_MS, so pinning the ceiling on an oversized window advised "re-run with a coarser interval" when none exists. Now gated on chosen_interval < _MAX_INTERVAL_MS, with a regression test covering all four paths.
  • Unused lambda variable in the sentinel-closer tests.
  • Two new S3776 (cognitive complexity) — both were regressions my own previous fixes introduced, so they're in scope: compute_severity_score (16) and _p95_datadog (16). Extracted _is_real_number, _dimension_score, and _point_cap_note; the first also removes duplication between compute_severity_score and is_materially_worse. Verified behaviour is byte-identical on every input class before and after.

Not fixing — 4 pre-existing S3776 + S107

_handle_hpa_vpa_dismiss (30), send_hpa_vpa_recommendation (27), _validate (22), query_datadog (20), and claim_recommendation's 16 params.

These are maintainability-only, and the quality gate already passes maintainability at rating 1 — they are not what's failing it. Each is a linear sequence of guard clauses or an ordered transaction (authenticate → parse → authorize → transition → close → rewrite), where the step count is the cognitive complexity. Splitting them would mean threading a shared connection, cursor, and RLS context through new helper boundaries, which trades a lint metric for a genuinely higher chance of a correctness bug in the exact code paths that must not break: the atomic dismiss transition and the claim-row-before-post ordering.

claim_recommendation's 16 keyword-only params map 1:1 to NOT NULL-ish columns; collapsing them into a dict would move column validation from the call site to runtime.

Happy to do these as a follow-up PR where they can be reviewed as a pure refactor with no behavioural changes mixed in.

Not fixing — 2 in SKILL.md

  • MD022 blank lines after headings — cosmetic, and not the convention in this file. ### Tool Usage, ### Datadog Query Syntax, and ### Examples all predate this PR and put content on the next line. markdownlint isn't run on this repo, so matching the surrounding style is the better call.
  • "Auto-pick claim is wrong for 'metrics'" — already correct. Fixed in an earlier round; the section reads "For 'metrics', omitting it leaves Datadog's own default resolution in place." The comment was written against the pre-fix text.

Acknowledged, mitigated, not fully fixed — Slack 3s ack budget

CodeRabbit is right that a 5s timeout is a mitigation rather than a fix, and that the proper answer is moving the PR close and card rewrite onto Celery.

I'm leaving it as a short timeout for this PR because the ordering is already designed so a timeout is safe: the dismissal transition commits before the GitHub call, so a slow provider costs only the automatic PR close — surfaced on the card as "could not be closed automatically; please close it on GitHub" — and never the 30-day cooldown. That's the failure direction we want; the alternative (closing the PR then losing the cooldown) is exactly the nagging this feature exists to prevent.

Deferring to Celery means a new task, its own RLS context, and a retry/idempotency story for the card rewrite. That's a meaningful chunk of new surface area, and I'd rather it not ride along in a PR this size. Tracked as the follow-up; the constraint and the reasoning are both in a comment at _GITHUB_CLOSE_TIMEOUT.

Validation

1133 tests pass in the exact CI environment (python:3.12-slim + the 8 pinned packages from linters.yml); 94 feature tests; architectural 67. Live card path re-verified after the refactors: post → update-in-place on the same row → GitLab rejected up front.

@arvo-ai-staging arvo-ai-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Aurora Risk Review — Latest changes

Verdict: SAFE

No new incident risk in the latest changes.


Aurora reviews PRs for incident prevention.

@arvo-ai-staging arvo-ai-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Aurora Risk Review — Latest changes

Verdict: SAFE

No new incident risk in the latest changes.


Aurora reviews PRs for incident prevention.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
server/routes/slack/slack_events.py (3)

709-742: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle missing and non-GitHub PRs in the failure status.

When pr_number is absent, this code intentionally creates a “No PR recorded” result but later renders PR #None``. It also always tells users to close the change on GitHub even when vcs_provider is GitLab. Render a no-PR-specific message and derive the provider label from the stored provider.

The lifecycle is provider-dispatched per the PR objectives.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/routes/slack/slack_events.py` around lines 709 - 742, Update the
failure-status branch around close_result and the dismissed provider so missing
pr_number renders a specific “No PR recorded” message instead of PR `#None`, while
existing PR failures include the number. Derive the manual-close destination
from dismissed.get("vcs_provider") (defaulting consistently to GitHub) and use
the appropriate provider label, preserving the provider-dispatched lifecycle.

635-685: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Enforce dismissal authorization through centralized RBAC.

This handler authorizes the action by checking Slack identity and organization ownership only, so any authenticated organization member can dismiss and close a recommendation. Route authorization must go through the centralized permission policy rather than this inline check; use the RBAC decorator at the route boundary or an equivalent centralized adapter for Slack identities.

As per path instructions, routes must use RBAC decorators (@require_permission), never manual auth checks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/routes/slack/slack_events.py` around lines 635 - 685, Replace the
inline authorization logic in the Slack dismissal route, including the clicker
identity and organization checks around get_user_id_from_slack_user and
set_rls_context, with the centralized RBAC route authorization using
`@require_permission`. Ensure Slack identities are adapted through the established
centralized RBAC mechanism and enforce the dismissal permission at the route
boundary; do not retain manual authorization checks.

Source: Path instructions


719-733: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not report “No cooldown applied” when merge reconciliation fails.

dismiss_recommendation has already committed the dismissal before mark_merged runs. If mark_merged raises, the record can retain its dismissal cooldown, but the card still claims that no cooldown was applied. Make the merge transition retryable/durable and only emit the no-cooldown status after the state change succeeds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/routes/slack/slack_events.py` around lines 719 - 733, Update the
already-merged branch in dismiss_recommendation so mark_merged succeeds durably
before reporting “No cooldown applied.” Track whether the merge transition
committed successfully, preserve the existing exception logging, and emit a
failure/retryable status when mark_merged raises instead of claiming no cooldown
was applied.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/services/actions/hpa_vpa_recommendations.py`:
- Around line 84-96: Replace the value parameter annotation in _is_real_number
and _dimension_score from Any to object, preserving their existing runtime
checks and behavior while satisfying ANN401.
- Around line 84-93: Update _is_real_number to handle oversized integer values
without propagating OverflowError: preserve rejection of booleans, non-numeric
values, NaN, and infinities, while treating integers that cannot be safely
evaluated by math.isfinite as invalid. Ensure the scoring flow and any later
float conversions treat these malformed values as unusable rather than aborting.

---

Outside diff comments:
In `@server/routes/slack/slack_events.py`:
- Around line 709-742: Update the failure-status branch around close_result and
the dismissed provider so missing pr_number renders a specific “No PR recorded”
message instead of PR `#None`, while existing PR failures include the number.
Derive the manual-close destination from dismissed.get("vcs_provider")
(defaulting consistently to GitHub) and use the appropriate provider label,
preserving the provider-dispatched lifecycle.
- Around line 635-685: Replace the inline authorization logic in the Slack
dismissal route, including the clicker identity and organization checks around
get_user_id_from_slack_user and set_rls_context, with the centralized RBAC route
authorization using `@require_permission`. Ensure Slack identities are adapted
through the established centralized RBAC mechanism and enforce the dismissal
permission at the route boundary; do not retain manual authorization checks.
- Around line 719-733: Update the already-merged branch in
dismiss_recommendation so mark_merged succeeds durably before reporting “No
cooldown applied.” Track whether the merge transition committed successfully,
preserve the existing exception logging, and emit a failure/retryable status
when mark_merged raises instead of claiming no cooldown was applied.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d597e6a7-5711-4746-91c1-2e9e08a4290c

📥 Commits

Reviewing files that changed from the base of the PR and between b9c5f89 and 2e2679c.

📒 Files selected for processing (5)
  • server/chat/backend/agent/tools/datadog_tool.py
  • server/routes/slack/slack_events.py
  • server/services/actions/hpa_vpa_recommendations.py
  • server/tests/chat/test_datadog_metric_stats.py
  • server/tests/services/test_hpa_vpa_recommendations.py

Comment thread server/services/actions/hpa_vpa_recommendations.py Outdated
Comment thread server/services/actions/hpa_vpa_recommendations.py Outdated

@arvo-ai-staging arvo-ai-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Aurora Risk Review — Latest changes

Verdict: SAFE

No new incident risk in the latest changes.


Aurora reviews PRs for incident prevention.

@sonarqubecloud

Copy link
Copy Markdown

@isiddharthsingh

Copy link
Copy Markdown
Contributor Author

Superseded by #593 — the branch was renamed and GitHub auto-closed this PR. Same commits, same head SHA (693be8c6). Review history here is preserved for reference; please continue on #593.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like chats can now post a slack card. The posting of the slack card should be exclusive to the seeded templated action being ran

row = _summarize_series(
meta.get("group_tags"),
meta.get("unit"),
values[idx] if idx < len(values) else [],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

When values is shorter than series, the substituted [] produces points: 0, note: "no non-null points in window", indistinguishable from a genuinely idle workload, which is the exact failure mode this resource type exists to prevent. Fix: Emit a distinct marker (e.g. note: "series metadata present but no values row returned") so the agent can tell a malformed response from real absence.

Comment on lines +1445 to +1448
dismissed_at TIMESTAMP,
cooldown_until TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

cooldown_until is written as aware UTC but compared with NOW(), the 30-day window drifts on a non-UTC session. TIMESTAMPTZ instead?

Comment on lines +689 to +703
if client:
_send_ephemeral(client, channel_id, slack_user_id,
"Unauthorized: that recommendation does not belong to your organization.")
return jsonify({"text": ""}), 200

dismissed = dismiss_recommendation(cursor, rec_id, clicker_org_id, slack_user_id)
# Commit before the GitHub call so a slow API cannot hold the row lock.
conn.commit()

if not dismissed:
logger.info(f"Recommendation {rec_id} was already dismissed; no-op")
if client:
_send_ephemeral(client, channel_id, slack_user_id,
"That recommendation was already dismissed.")
return jsonify({"text": ""}), 200

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The DB transition and PR close are unconditional, but every piece of feedback — ephemerals and _rewrite_dismissed_card — is gated on if client: / an early return when get_slack_client_for_user yields None, so the workload gets a 30-day cooldown and the PR gets closed while the card keeps its live Dismiss button and no message appears.

Comment on lines +741 to +742
status_line = (f"*Status:* Dismissed by <@{slack_user_id}> -- PR #{pr_number} could not be "
"closed automatically; please close it on GitHub.")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

status_line says "could not be closed automatically; please close it on GitHub", but the rewrite passes with_actions=False, which drops the entire actions block including the View PR button — so the one message asking for manual work deletes the only link to the PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Invalid UUID, unresolvable org, and unauthenticated-without-workspace-token all return an empty 200 with no ephemeral, so the click is indistinguishable from a broken button

"Unauthorized: that recommendation does not belong to your organization.")
return jsonify({"text": ""}), 200

dismissed = dismiss_recommendation(cursor, rec_id, clicker_org_id, slack_user_id)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe there should be a ui confirmation for users if they want to mute it for 30 days or not

Comment on lines +308 to +315
def mark_superseded(cursor, rec_id: str) -> None:
"""Retire a row whose mis-size worsened, or that a newer rec took over."""
cursor.execute(
"""UPDATE hpa_vpa_recommendations
SET status = %s, cooldown_until = NULL, updated_at = NOW()
WHERE id = %s::uuid""",
(STATUS_SUPERSEDED, rec_id),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

mark_superseded is committed before the Slack post, but the post's compensation path only deletes the new claim, the superseded dismissal is never restored, so the remaining anti-nag window is gone for good.

if body.get("merged"):
return {"success": True, "already_merged": True, "state": body.get("state")}
return {"success": True, "state": body.get("state", "closed")}
if resp.status_code == 404:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i think this should be treated as a error or reqiures more code to tell if its realyl a success. Maybe a use toast saying not reachable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

severity_score is an LLM-supplied argument. Someone clicks Dismiss and gets 30 days of quiet but the AI is allowed to break that silence early if the problem got 25% worse, but the AI is the one reporting how bad it is so this sort of doesnt make sense

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants