mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 04:32:32 +00:00
fix: close live catalog/type drift across web, backend and agent (audit P1)
- adminConfig.ts: drop retired V1 latestAgent*/customSettings ghost fields,
add the 8 backend fields the interface was missing
- ops alerts: register the Sla category (BreachNotification/ConsecutiveFailures/
EvaluationCompleted) + OrphanEventsCleaned so operators can route them
- analyze rules: expose app_install_duration in the builder SOURCES;
fix stale XML docs (event_data_array, count_per_group_gte/in/not_in)
- UploadIntervalSeconds: collapse 5 diverged defaults (10 vs 30) onto
Constants.DefaultUploadIntervalSeconds = 10 (the served value)
- combine-rules workflow: commit ALL three combine.js outputs and include
rules/guardrails.json in the path filters
- api.ts apps sessions builders: move trailing optional strings into an
options object (adjacent optional strings swallowed a forgotten tenantId)
- SummaryDialog: canonical phase display names ("Apps (Device)")
- mcp-server.bicep: fix pre-EU-cutover hostname in deploy docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ on:
|
||||
- 'rules/ime-log-patterns/**'
|
||||
- 'rules/schema/**'
|
||||
- 'rules/scripts/**'
|
||||
- 'rules/guardrails.json'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'rules/gather/**'
|
||||
@@ -15,6 +16,7 @@ on:
|
||||
- 'rules/ime-log-patterns/**'
|
||||
- 'rules/schema/**'
|
||||
- 'rules/scripts/**'
|
||||
- 'rules/guardrails.json'
|
||||
|
||||
jobs:
|
||||
validate-and-combine:
|
||||
@@ -55,9 +57,11 @@ jobs:
|
||||
- name: Combine rules into dist/
|
||||
run: node rules/scripts/combine.js
|
||||
|
||||
- name: Commit dist/ changes
|
||||
- name: Commit generated outputs
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
uses: stefanzweifel/git-auto-commit-action@v7
|
||||
with:
|
||||
commit_message: "chore: regenerate rules/dist from individual files"
|
||||
file_pattern: "rules/dist/*"
|
||||
commit_message: "chore: regenerate rules/dist + generated TS from individual files"
|
||||
# combine.js writes THREE outputs; committing only dist/ would silently discard
|
||||
# the two generated TS modules and leave the guardrails-in-sync CI check red.
|
||||
file_pattern: "rules/dist/* src/Web/autopilot-monitor-web/utils/guardrails.generated.ts src/McpServer/autopilot-monitor-mcp/src/rule-authoring.generated.ts"
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
// az deployment group create \
|
||||
// --resource-group <rg-name> \
|
||||
// --template-file infra/mcp-server.bicep \
|
||||
// --parameters apiUrl=https://autopilotmonitor-api.azurewebsites.net \
|
||||
// --parameters apiUrl=https://autopilotmonitor-api-eu.azurewebsites.net \
|
||||
// entraClientSecret=<secret-value>
|
||||
// This emits `containerAppUrl` as an output (e.g. https://autopilotmonitor-mcp.<env-suffix>.<region>.azurecontainerapps.io).
|
||||
//
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Configuration
|
||||
/// <summary>
|
||||
/// Upload interval in seconds
|
||||
/// </summary>
|
||||
public int UploadIntervalSeconds { get; set; } = 30;
|
||||
public int UploadIntervalSeconds { get; set; } = Shared.Constants.DefaultUploadIntervalSeconds;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum batch size for events
|
||||
|
||||
@@ -322,7 +322,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Configuration
|
||||
return new AgentConfigResponse
|
||||
{
|
||||
ConfigVersion = 0,
|
||||
UploadIntervalSeconds = 30,
|
||||
UploadIntervalSeconds = Shared.Constants.DefaultUploadIntervalSeconds,
|
||||
SelfDestructOnComplete = true,
|
||||
KeepLogFile = false,
|
||||
EnableGeoLocation = true,
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace AutopilotMonitor.Agent.V2.Runtime
|
||||
// or hour-long gaps that hide live UI from operators).
|
||||
private const int MinUploadIntervalSeconds = 5;
|
||||
private const int MaxUploadIntervalSeconds = 300;
|
||||
private const int DefaultUploadIntervalSeconds = 30;
|
||||
private const int DefaultUploadIntervalSeconds = Shared.Constants.DefaultUploadIntervalSeconds;
|
||||
private const int MinUploadBatchSize = 1;
|
||||
private const int MaxUploadBatchSize = 500;
|
||||
private const int DefaultUploadBatchSize = 100;
|
||||
|
||||
@@ -869,9 +869,26 @@ namespace AutopilotMonitor.SummaryDialog
|
||||
|
||||
private static string FormatPhaseName(string phase)
|
||||
{
|
||||
// Convert "DeviceSetup" -> "Device Setup", "AccountSetup" -> "Account Setup"
|
||||
if (string.IsNullOrEmpty(phase)) return phase;
|
||||
|
||||
// Canonical display names, mirroring EnrollmentEvent.GetPhaseName in Shared.
|
||||
// This dialog is deliberately dependency-free (standalone EXE), so the mapping is
|
||||
// restated here instead of referencing Shared — the camel-case splitter below is
|
||||
// only the fallback for unknown tokens and cannot produce "Apps (Device)".
|
||||
switch (phase)
|
||||
{
|
||||
case "Start": return "Start";
|
||||
case "DevicePreparation": return "Device Preparation";
|
||||
case "DeviceSetup": return "Device Setup";
|
||||
case "AppsDevice": return "Apps (Device)";
|
||||
case "AccountSetup": return "Account Setup";
|
||||
case "AppsUser": return "Apps (User)";
|
||||
case "FinalizingSetup": return "Finalizing Setup";
|
||||
case "Complete": return "Complete";
|
||||
case "Failed": return "Failed";
|
||||
}
|
||||
|
||||
// Fallback: "SomeNewPhase" -> "Some New Phase"
|
||||
var result = new System.Text.StringBuilder();
|
||||
for (int i = 0; i < phase.Length; i++)
|
||||
{
|
||||
|
||||
@@ -275,7 +275,7 @@ namespace AutopilotMonitor.Functions.Functions.Config
|
||||
await response.WriteAsJsonAsync(new AgentConfigResponse
|
||||
{
|
||||
ConfigVersion = 37, // EnableEspContinueAnywayObservation (Continue-Anyway observation mode)
|
||||
UploadIntervalSeconds = 10,
|
||||
UploadIntervalSeconds = Shared.Constants.DefaultUploadIntervalSeconds,
|
||||
SelfDestructOnComplete = tenantConfig.SelfDestructOnComplete ?? true,
|
||||
KeepLogFile = tenantConfig.KeepLogFile ?? false,
|
||||
EnableGeoLocation = tenantConfig.EnableGeoLocation ?? true,
|
||||
|
||||
@@ -238,9 +238,12 @@ namespace AutopilotMonitor.Shared
|
||||
public const int MaxBatchSize = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Default upload interval in seconds (fallback timer; normal path uses FileSystemWatcher)
|
||||
/// Default upload interval in seconds (fallback timer; normal path uses FileSystemWatcher).
|
||||
/// Single source for this default: GetAgentConfigFunction serves it, AgentConfigResponse
|
||||
/// and AgentConfiguration initialize from it, RemoteConfigService's offline fallback and
|
||||
/// AgentRuntimeHost's clamp fallback reuse it — keep them referencing this constant.
|
||||
/// </summary>
|
||||
public const int DefaultUploadIntervalSeconds = 30;
|
||||
public const int DefaultUploadIntervalSeconds = 10;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// API endpoint paths (relative to ApiBaseUrl)
|
||||
|
||||
@@ -16,8 +16,10 @@ namespace AutopilotMonitor.Shared.Models
|
||||
|
||||
/// <summary>
|
||||
/// Event upload debounce interval in seconds.
|
||||
/// Sourced from the shared constant so the served value, the offline fallback and
|
||||
/// this deserialization default can never diverge.
|
||||
/// </summary>
|
||||
public int UploadIntervalSeconds { get; set; } = 30;
|
||||
public int UploadIntervalSeconds { get; set; } = Constants.DefaultUploadIntervalSeconds;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to self-destruct after enrollment completion (remove Scheduled Task and all files).
|
||||
|
||||
@@ -211,7 +211,9 @@ namespace AutopilotMonitor.Shared.Models
|
||||
public string Signal { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Source of the signal: "event_type", "event_data", "phase_duration", "event_count", "app_install_duration", "event_correlation"
|
||||
/// Source of the signal: "event_type", "event_data", "event_data_array", "phase_duration",
|
||||
/// "event_count", "app_install_duration", "event_correlation".
|
||||
/// Must stay in sync with DryRunAnalyzeRuleFunction.KnownSources and the RuleEngine switch.
|
||||
/// </summary>
|
||||
public string Source { get; set; } = default!;
|
||||
|
||||
@@ -240,8 +242,10 @@ namespace AutopilotMonitor.Shared.Models
|
||||
public string ItemField { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Comparison operator: "equals", "not_equals", "contains", "not_contains", "regex", "not_regex", "gt", "lt", "gte", "lte", "exists", "not_exists", "count_gte"
|
||||
/// Comparison operator: "equals", "not_equals", "contains", "not_contains", "regex", "not_regex",
|
||||
/// "gt", "lt", "gte", "lte", "exists", "not_exists", "count_gte", "count_per_group_gte", "in", "not_in".
|
||||
/// For "event_correlation": operator for the Event B filter (applied to DataField).
|
||||
/// Must stay in sync with DryRunAnalyzeRuleFunction.KnownOperators.
|
||||
/// </summary>
|
||||
public string Operator { get; set; } = default!;
|
||||
|
||||
|
||||
@@ -61,6 +61,18 @@ const OPS_EVENT_TYPES: Record<string, string[]> = {
|
||||
// PR2: GA restored a single row from a backup. Warning severity so operators
|
||||
// can wire a Telegram rule and see the audit in near-real-time.
|
||||
"BackupRowRestored",
|
||||
// Orphan cleanup sweep (Warning): events whose parent session no longer exists were
|
||||
// deleted during maintenance aggregation. Backend helper RecordOrphanEventsCleanedAsync.
|
||||
"OrphanEventsCleaned",
|
||||
],
|
||||
// SLA breach evaluation (SlaBreachEvaluationService). Dual-register per memory
|
||||
// feedback_ops_event_types_dual_register. BreachNotification = Warning per tenant+type
|
||||
// (cooldown-gated), ConsecutiveFailures = Error (threshold crossed), EvaluationCompleted =
|
||||
// Info run summary. Backend helpers RecordSla{BreachNotification,ConsecutiveFailures,EvaluationCompleted}Async.
|
||||
Sla: [
|
||||
"SlaBreachNotification",
|
||||
"SlaConsecutiveFailures",
|
||||
"SlaEvaluationCompleted",
|
||||
],
|
||||
Security: [
|
||||
"DeviceBlocked",
|
||||
@@ -145,6 +157,7 @@ const SEVERITIES = ["Info", "Warning", "Error", "Critical"];
|
||||
const CATEGORY_COLORS: Record<string, string> = {
|
||||
Consent: "text-indigo-700 dark:text-indigo-300",
|
||||
Maintenance: "text-purple-700 dark:text-purple-300",
|
||||
Sla: "text-rose-700 dark:text-rose-300",
|
||||
Security: "text-orange-700 dark:text-orange-300",
|
||||
Tenant: "text-teal-700 dark:text-teal-300",
|
||||
Agent: "text-cyan-700 dark:text-cyan-300",
|
||||
|
||||
@@ -116,7 +116,7 @@ export const CATEGORIES = ["network", "identity", "apps", "device", "esp", "enro
|
||||
export const SEVERITIES = ["info", "warning", "high", "critical"] as const;
|
||||
export const TRIGGERS = ["single", "correlation"] as const;
|
||||
export const OPERATORS = ["equals", "not_equals", "contains", "not_contains", "regex", "not_regex", "gt", "lt", "gte", "lte", "exists", "not_exists", "count_gte", "count_per_group_gte", "in", "not_in"] as const;
|
||||
export const SOURCES = ["event_type", "event_data", "event_data_array", "phase_duration", "event_count", "event_correlation"] as const;
|
||||
export const SOURCES = ["event_type", "event_data", "event_data_array", "phase_duration", "app_install_duration", "event_count", "event_correlation"] as const;
|
||||
export const PRECONDITION_OPERATORS = ["equals", "not_equals", "contains", "not_contains", "regex", "not_regex", "gt", "lt", "gte", "lte", "exists", "not_exists", "in", "not_in"] as const;
|
||||
|
||||
export const SEVERITY_COLORS: Record<string, { bg: string; text: string; border: string; dot: string }> = {
|
||||
|
||||
@@ -60,7 +60,6 @@ export interface TenantConfiguration {
|
||||
homedAppClientId?: string | null;
|
||||
dataRetentionDays: number;
|
||||
sessionTimeoutHours: number;
|
||||
customSettings?: string;
|
||||
// Agent collector settings
|
||||
enablePerformanceCollector: boolean;
|
||||
performanceCollectorIntervalSeconds: number;
|
||||
|
||||
@@ -309,6 +309,9 @@ export const api = {
|
||||
`${API_BASE_URL}/api/apps/list${qs({ tenantId, days: String(days) })}`,
|
||||
analytics: (tenantId: string, appName: string, days: number) =>
|
||||
`${API_BASE_URL}/api/apps/${encodeURIComponent(appName)}/analytics${qs({ tenantId, days: String(days) })}`,
|
||||
// model/version live in an options object: as trailing positional strings they sat
|
||||
// adjacent to other optional strings, so a forgotten argument silently shifted the
|
||||
// next one into its place (compiles fine, queries the wrong thing).
|
||||
sessions: (
|
||||
tenantId: string,
|
||||
appName: string,
|
||||
@@ -316,8 +319,7 @@ export const api = {
|
||||
status: "all" | "failed" | "succeeded" = "all",
|
||||
offset = 0,
|
||||
limit = 50,
|
||||
model?: string,
|
||||
version?: string
|
||||
opts?: { model?: string; version?: string }
|
||||
) =>
|
||||
`${API_BASE_URL}/api/apps/${encodeURIComponent(appName)}/sessions${qs({
|
||||
tenantId,
|
||||
@@ -325,8 +327,8 @@ export const api = {
|
||||
status,
|
||||
offset: String(offset),
|
||||
limit: String(limit),
|
||||
model,
|
||||
version,
|
||||
model: opts?.model,
|
||||
version: opts?.version,
|
||||
})}`,
|
||||
|
||||
// Global Admin variants — tenantId is optional:
|
||||
@@ -343,8 +345,7 @@ export const api = {
|
||||
offset = 0,
|
||||
limit = 50,
|
||||
tenantId?: string,
|
||||
model?: string,
|
||||
version?: string
|
||||
opts?: { model?: string; version?: string }
|
||||
) =>
|
||||
`${API_BASE_URL}/api/global/apps/${encodeURIComponent(appName)}/sessions${qs({
|
||||
days: String(days),
|
||||
@@ -352,8 +353,8 @@ export const api = {
|
||||
offset: String(offset),
|
||||
limit: String(limit),
|
||||
tenantId,
|
||||
model,
|
||||
version,
|
||||
model: opts?.model,
|
||||
version: opts?.version,
|
||||
})}`,
|
||||
},
|
||||
|
||||
|
||||
@@ -38,7 +38,22 @@ export interface AdminConfiguration {
|
||||
opsEventRetentionDays?: number;
|
||||
slaNotificationCooldownHours?: number;
|
||||
diagnosticsGlobalLogPathsJson?: string;
|
||||
customSettings?: string;
|
||||
/** Max diagnostics download size in MB; blobs above are rejected (413). 0 = no limit. Default 500. */
|
||||
maxDiagnosticsDownloadSizeMB?: number;
|
||||
/** Timeout in seconds for the whole diagnostics download+stream. 0 = no timeout. Default 120. */
|
||||
diagnosticsDownloadTimeoutSeconds?: number;
|
||||
/** JSON tier name → rate limits/features, e.g. {"pro":{"apiRateLimit":300}}. */
|
||||
planTierDefinitionsJson?: string;
|
||||
/** MCP server access: "Disabled" | "WhitelistOnly" (default) | "AllMembers". */
|
||||
mcpAccessPolicy?: string;
|
||||
/** Global kill-switch for the in-app feedback prompt. Default true. */
|
||||
feedbackEnabled?: boolean;
|
||||
/** Minimum tenant age in days before the feedback prompt shows. Default 14. */
|
||||
feedbackMinTenantAgeDays?: number;
|
||||
/** Cooldown in days before a user is re-prompted for feedback. 0 = single wave. Default 60. */
|
||||
feedbackCooldownDays?: number;
|
||||
/** JSON list of ImeLogPattern IDs re-emitted as WhiteGloveSealingPatternDetected. Empty = off. */
|
||||
whiteGloveSealingPatternIdsJson?: string;
|
||||
nvdApiKey?: string;
|
||||
vulnerabilityCorrelationEnabled?: boolean;
|
||||
vulnerabilityDataLastSyncUtc?: string;
|
||||
@@ -52,12 +67,9 @@ export interface AdminConfiguration {
|
||||
opsAlertSlackWebhookUrl?: string;
|
||||
allowAgentDowngrade?: boolean;
|
||||
modernDeploymentHarmlessEventIdsJson?: string;
|
||||
// Agent hash oracle (written by CI/CD / build scripts, surfaced read-only; must round-trip via Save to survive Replace)
|
||||
latestAgentVersion?: string;
|
||||
latestAgentSha256?: string;
|
||||
latestAgentExeSha256?: string;
|
||||
latestBootstrapScriptVersion?: string;
|
||||
// V2 agent hash oracle (separate release line — written by scripts/Deployment/V2/*.ps1)
|
||||
// V2 agent hash oracle (written by CI/CD / build scripts, surfaced read-only; must
|
||||
// round-trip via Save to survive Replace). The V1-era latestAgent*/latestBootstrapScriptVersion
|
||||
// fields were retired with the legacy agent line and no longer exist on the C# model.
|
||||
latestAgentV2Version?: string;
|
||||
latestAgentV2Sha256?: string;
|
||||
latestAgentV2ExeSha256?: string;
|
||||
|
||||
@@ -35,8 +35,9 @@ export const EXPORT_V1_PHASE_ORDER = ["Start", "Device Preparation", "Device Set
|
||||
export const EXPORT_V2_PHASE_ORDER = ["Start", "Device Preparation", "App Installation",
|
||||
"Finalizing Setup", "Complete", "Failed"];
|
||||
|
||||
const SEVERITY_INT: Record<string, number> = {
|
||||
Debug: 0, Info: 1, Warning: 2, Error: 3, Critical: 4
|
||||
// Exported for the shared-manifest parity test — must match the C# EventSeverity enum.
|
||||
export const SEVERITY_INT: Record<string, number> = {
|
||||
Trace: -1, Debug: 0, Info: 1, Warning: 2, Error: 3, Critical: 4
|
||||
};
|
||||
|
||||
export function generateCsvExport(events: SessionExportEvent[]) {
|
||||
|
||||
Reference in New Issue
Block a user