feat(admin): require expiration for credit grants unless 'Never expires' is checked#15
Conversation
…es' is checked - Add 'Never expires' checkbox option to the credit grant form - Make expiration date OR expiration hours required by default - When 'Never expires' is checked, expiration fields become disabled - Add form validation to enforce the requirement - Clear expiration fields when 'Never expires' is checked This prevents accidentally creating credits without expiration dates, which creates unnecessary financial liabilities. Users who genuinely need non-expiring credits can explicitly check the 'Never expires' box.
| <div> | ||
| <Label className="text-sm font-medium" htmlFor="expiry-hours"> | ||
| Expiry Hours | ||
| Expiry Hours{!neverExpires && !expirationDate ? ' (required)' : ''} |
There was a problem hiding this comment.
WARNING: Required indicator condition is incorrect for "Expiry Hours"
This shows "(required)" whenever expirationDate is empty, even if the user already entered expiryHours. Consider only showing required when neither expiration field is set.
| Expiry Hours{!neverExpires && !expirationDate ? ' (required)' : ''} | |
| Expiry Hours{!neverExpires && !expiryHours && !expirationDate ? ' (required)' : ''} |
| step="0.01" | ||
| id="expiry-hours" | ||
| disabled={!selectedCredit} | ||
| disabled={!selectedCredit || neverExpires} |
There was a problem hiding this comment.
WARNING: Required indicator condition is incorrect for "Expiration Date"
This shows "(required)" whenever expiryHours is empty, even if the user already selected an expirationDate. Consider only showing required when neither expiration field is set.
| disabled={!selectedCredit || neverExpires} | |
| Expiration Date{!neverExpires && !expiryHours && !expirationDate ? ' (required)' : ''} |
| <div> | ||
| <Label className="text-sm font-medium" htmlFor="date"> | ||
| Expiration Date | ||
| Expiration Date{!neverExpires && !expiryHours ? ' (required)' : ''} |
There was a problem hiding this comment.
SUGGESTION: Date input value fallback likely never triggers + format mismatch
expirationDate is a string state initialized to '', so expirationDate ?? ... will always pick expirationDate (empty string is not nullish) and never fall back to the category default. Also, <input type="date"> expects YYYY-MM-DD, while toISOString() produces a full timestamp (YYYY-MM-DDTHH:mm:ss.sssZ).
Consider using something like expirationDate || (selectedCreditCategory?.credit_expiry_date ? selectedCreditCategory.credit_expiry_date.toISOString().slice(0, 10) : '').
Code Review SummaryStatus: 3 Issues Found | Recommendation: Address before merge Overview
Fix these issues in Kilo Cloud Issue Details (click to expand)WARNING
SUGGESTION
Other Observations (not in diff)Issues found in unchanged code that cannot receive inline comments:
Files Reviewed (1 files)
|
Comprehensive audit of observability across all 6 workflows (sync, triage, sandbox, extraction, auto-dismiss, stale cleanup). The feature has basic Sentry error capture and console.log debug logging, but no operational observability: no LLM call timing or token usage tracking (despite emitApiMetrics existing in the codebase), no cron heartbeats, no GitHub API rate limit monitoring, no correlation IDs across the multi-tier pipeline, and no metrics for triage/extraction fallback rates or auto-dismiss volumes. https://claude.ai/code/session_01RG48f6grwtxGT6Mo3uHRxj
Addresses Finding #15 (HIGH: No Operational Observability) from the security agent production readiness review. Lays out a 5-phase plan covering correlation IDs, structured logging, LLM call timing/token tracking, cron heartbeats, sync metrics, pipeline instrumentation, and degradation detection — all using existing codebase infrastructure (emitApiMetrics, sentryLogger, Sentry spans, BetterStack heartbeats). https://claude.ai/code/session_01H6HahwjayzdFFZXbpE9Hg7
…kflows Implements all 5 phases of the observability plan (Finding #15): Phase 1 - Correlation ID & Structured Logging: - Generate correlationId (UUID) at analysis start, thread through all tiers - Store correlationId in SecurityFindingAnalysis JSONB for queryability - Replace ~76 console.log/error calls with sentryLogger (dual console+Sentry) - Wrap startSecurityAnalysis in Sentry withScope for tag propagation Phase 2 - LLM Call Timing & Token Tracking: - Wrap triage and extraction LLM calls in Sentry startSpan (op: ai.inference) - Extract token usage from sendProxiedChatCompletion responses - Emit metrics via emitApiMetrics with mode security-agent-triage/extraction - Track input/output tokens as span attributes Phase 3 - Cron Heartbeats & Sync Metrics: - Add BetterStack heartbeat support to both cron jobs (env-configurable URLs) - Send /fail heartbeat on sync errors - Add per-repository sync timing in syncDependabotAlertsForRepo - Track GitHub API rate limits via x-ratelimit-remaining headers Phase 4 - Pipeline Timing & R2 Retry Instrumentation: - Wrap processAnalysisStream in Sentry span (op: ai.pipeline) - Track stream duration, R2 retry attempts, and retry wait time - Log tier transition timing (Tier 1 duration) - Record stream outcome status on span attributes Phase 5 - Outcome Distribution & Degradation Detection: - Add Sentry breadcrumbs for triage/extraction outcomes with isFallback flag - Track auto-dismiss decisions with correlationId and source - Add stale analysis anomaly detection (warn when count > threshold) - Log bulk auto-dismiss summaries https://claude.ai/code/session_01H6HahwjayzdFFZXbpE9Hg7
Summary
This PR updates the admin user detail page to require an expiration date or hours when granting credits, unless the user explicitly checks a "Never expires" checkbox.
Changes
Motivation
This prevents accidentally creating credits without expiration dates, which creates unnecessary financial liabilities. Users who genuinely need non-expiring credits can explicitly check the "Never expires" box.
Testing
Built for Brendan by Kilo for Slack