mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-25 05:00:07 +00:00
821c41e2ba
* feat: tenant Hello wait timeout up to 1 hour The "Hello Wait Timeout" setting (30..300 s in the UI) had no effect in the V2 agent: its only consumer, HelloTracker.StartHelloWaitTimer, has no production caller, while the decision engine armed a fixed 300 s hello_safety deadline after the ESP exit and the tracker ran a fixed 300 s completion timer after the wizard start. The setting is now the real Hello budget, clamped to 300..3600 s (HelloWaitTimeout in AutopilotMonitor.Shared). Every value that was valid before (<= 300, including the old 30 s default) keeps today's 5 minutes. - Engine: the agent stamps helloWaitTimeoutSeconds on EnrollmentFactsObserved (only when > 300), recorded set-once in EnrollmentScenarioObservations. All six hello_safety arm sites share one builder with the tenant window; a fact that arrives after hello_safety was armed moves it out. The WDP backstop is never shorter than the tenant window; the signal census surfaces hello_wait_extended. - Agent: the Hello tracker's wizard completion timer uses the same budget; the stall probe skips session_stalled while hello_safety is pending. - Backend: ValidateModel rejects values outside 300..3600; stored rows are read as their effective value, so legacy 30 s rows never block unrelated saves or backup reverts. Default 30 -> 300; shared manifest and wire types regenerated. - Web: input 300..3600, bounds pinned against the C# constants. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S17pipjWqjZ8ULAu1JrFyG * fix: keep the Hello wait's 30 s minimum and default, raise only the maximum Product decision: the setting keeps its UI contract (30 s minimum and default, same description); only the maximum moves to 3600 s. Configured values up to 300 s keep the built-in 5-minute Hello window, larger values extend it. - HelloWaitTimeout: MinSeconds/DefaultSeconds 30; BuiltInSeconds 300 is the floor of EffectiveSeconds. - Backend validates only a changed value (30..3600), like the retention cap; the read-time normalization is gone again. - The agent stamps the tenant window only above BuiltInSeconds. - Web keeps the original text and default; only the maximum and its hint change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S17pipjWqjZ8ULAu1JrFyG * fix: Hello wait default 300 s, stale-fire guard, deferred stall report Product decision: default 300 s (what V2 has always waited), minimum 30 s still accepted, maximum 3600 s. Values up to 300 s keep the built-in 5-minute window, which the settings hint now says. Review follow-ups: - Engine: a hello_safety fire whose incarnation was replaced (armed due later than the fire's due time) is dead-ended, same guard as RealmJoin — a late tenant window could otherwise be undercut by the queued fire of the built-in deadline. The tenant fact is recorded only when it extends the built-in window. - Stall probe: session_stalled is held back while a tenant-extended Hello wait (hello_safety or the WDP backstop) is pending and emitted once it is over and the session is still idle; real activity drops it. Default tenants never hit the guard. - Comments: WDP backstop wording, 5-min reference, AgentConfiguration. - Web pins the default against the shared manifest; tracker constructor defaults use HelloWaitTimeout.DefaultSeconds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S17pipjWqjZ8ULAu1JrFyG --------- Co-authored-by: Claude <noreply@anthropic.com>
124 lines
4.7 KiB
C#
124 lines
4.7 KiB
C#
using AutopilotMonitor.Functions.DataAccess.TableStorage;
|
|
using AutopilotMonitor.Functions.Helpers;
|
|
using AutopilotMonitor.Shared.Models;
|
|
using Xunit;
|
|
|
|
namespace AutopilotMonitor.Functions.Tests;
|
|
|
|
/// <summary>
|
|
/// Tenant Hello wait (<see cref="TenantConfiguration.HelloWaitTimeoutSeconds"/>): a changed value
|
|
/// must lie in 30..3600 s. Like the retention cap, only a CHANGED value is checked, so a value
|
|
/// stored before the rule existed never blocks an unrelated save or a backup revert (both
|
|
/// validate the whole model).
|
|
/// </summary>
|
|
public class HelloWaitTimeoutConfigTests
|
|
{
|
|
private const string TenantId = "11111111-1111-1111-1111-111111111111";
|
|
|
|
[Theory]
|
|
[InlineData(30)]
|
|
[InlineData(300)]
|
|
[InlineData(1800)]
|
|
[InlineData(3600)]
|
|
public void ValidateModel_accepts_the_supported_range(int seconds)
|
|
{
|
|
var candidate = TenantConfiguration.CreateDefault(TenantId);
|
|
candidate.HelloWaitTimeoutSeconds = seconds;
|
|
|
|
Assert.Null(TenantConfigValidation.ValidateModel(candidate, TenantConfiguration.CreateDefault(TenantId), isGlobalAdmin: false));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(29)]
|
|
[InlineData(3601)]
|
|
[InlineData(0)]
|
|
[InlineData(-1)]
|
|
public void ValidateModel_rejects_a_changed_value_outside_the_range(int seconds)
|
|
{
|
|
var candidate = TenantConfiguration.CreateDefault(TenantId);
|
|
candidate.HelloWaitTimeoutSeconds = seconds;
|
|
|
|
var error = TenantConfigValidation.ValidateModel(candidate, TenantConfiguration.CreateDefault(TenantId), isGlobalAdmin: true);
|
|
|
|
Assert.Equal("Hello wait timeout must be between 30 and 3600 seconds.", error);
|
|
}
|
|
|
|
[Fact]
|
|
public void ValidateModel_lets_an_unchanged_out_of_range_value_through()
|
|
{
|
|
// Written through the API before the range was validated — an unrelated save (or a
|
|
// revert to a snapshot holding the same value) must not fail on it.
|
|
var existing = TenantConfiguration.CreateDefault(TenantId);
|
|
existing.HelloWaitTimeoutSeconds = 86400;
|
|
var candidate = TenantConfiguration.CreateDefault(TenantId);
|
|
candidate.HelloWaitTimeoutSeconds = 86400;
|
|
candidate.ContactEmail = "it@example.com";
|
|
|
|
Assert.Null(TenantConfigValidation.ValidateModel(candidate, existing, isGlobalAdmin: false));
|
|
}
|
|
|
|
[Fact]
|
|
public void Default_is_the_built_in_five_minutes()
|
|
{
|
|
Assert.Equal(300, TenantConfiguration.CreateDefault(TenantId).HelloWaitTimeoutSeconds);
|
|
Assert.Equal(300, new CollectorConfiguration().HelloWaitTimeoutSeconds);
|
|
Assert.Equal(300, HelloWaitTimeout.EffectiveSeconds(HelloWaitTimeout.DefaultSeconds));
|
|
}
|
|
|
|
[Fact]
|
|
public void Stored_old_default_stays_valid_and_keeps_the_built_in_window()
|
|
{
|
|
// Every tenant that ever saved the agent settings page has the old 30 s default stored.
|
|
var existing = TenantConfiguration.CreateDefault(TenantId);
|
|
existing.HelloWaitTimeoutSeconds = 30;
|
|
var candidate = TenantConfiguration.CreateDefault(TenantId);
|
|
candidate.HelloWaitTimeoutSeconds = 30;
|
|
candidate.ContactEmail = "it@example.com";
|
|
|
|
Assert.Null(TenantConfigValidation.ValidateModel(candidate, existing, isGlobalAdmin: false));
|
|
Assert.Equal(300, HelloWaitTimeout.EffectiveSeconds(30));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(30, 300)]
|
|
[InlineData(300, 300)]
|
|
[InlineData(301, 301)]
|
|
[InlineData(3600, 3600)]
|
|
[InlineData(86400, 3600)]
|
|
[InlineData(0, 300)]
|
|
public void EffectiveSeconds_keeps_the_built_in_window_as_floor_and_caps_at_one_hour(int configured, int expected)
|
|
{
|
|
Assert.Equal(expected, HelloWaitTimeout.EffectiveSeconds(configured));
|
|
}
|
|
|
|
[Fact]
|
|
public void Row_without_the_column_reads_as_the_default()
|
|
{
|
|
var entity = TableConfigRepository.ConvertToTenantTableEntity(TenantConfiguration.CreateDefault(TenantId));
|
|
entity.Remove("HelloWaitTimeoutSeconds");
|
|
|
|
Assert.Equal(300, TableConfigRepository.ConvertFromTenantTableEntity(entity).HelloWaitTimeoutSeconds);
|
|
}
|
|
|
|
[Fact]
|
|
public void Stored_value_is_read_verbatim()
|
|
{
|
|
var entity = TableConfigRepository.ConvertToTenantTableEntity(TenantConfiguration.CreateDefault(TenantId));
|
|
entity["HelloWaitTimeoutSeconds"] = 30;
|
|
|
|
Assert.Equal(30, TableConfigRepository.ConvertFromTenantTableEntity(entity).HelloWaitTimeoutSeconds);
|
|
}
|
|
|
|
[Fact]
|
|
public void Configured_value_roundtrips_store_and_map()
|
|
{
|
|
var config = TenantConfiguration.CreateDefault(TenantId);
|
|
config.HelloWaitTimeoutSeconds = 2400;
|
|
|
|
var mapped = TableConfigRepository.ConvertFromTenantTableEntity(
|
|
TableConfigRepository.ConvertToTenantTableEntity(config));
|
|
|
|
Assert.Equal(2400, mapped.HelloWaitTimeoutSeconds);
|
|
}
|
|
}
|