synergy moats product advisory implementations
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// InstallTimestampService.cs
|
||||
// Sprint: SPRINT_20260117_028_Telemetry_p0_metrics
|
||||
// Task: P0M-001 - Time-to-First-Verified-Release Metric
|
||||
// Description: Service to record and retrieve install timestamp for P0M-001
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace StellaOps.Telemetry.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Service for tracking install timestamp to enable time-to-first-release metrics.
|
||||
/// </summary>
|
||||
public sealed class InstallTimestampService
|
||||
{
|
||||
private readonly ILogger<InstallTimestampService>? _logger;
|
||||
private readonly string _timestampFilePath;
|
||||
private DateTimeOffset? _cachedTimestamp;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the install timestamp service.
|
||||
/// </summary>
|
||||
/// <param name="dataPath">Path to data directory for storing timestamp.</param>
|
||||
/// <param name="logger">Optional logger.</param>
|
||||
public InstallTimestampService(string dataPath, ILogger<InstallTimestampService>? logger = null)
|
||||
{
|
||||
_logger = logger;
|
||||
_timestampFilePath = Path.Combine(dataPath, ".install-timestamp");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records the install timestamp if not already recorded.
|
||||
/// Call this on first service startup.
|
||||
/// </summary>
|
||||
/// <returns>The install timestamp (existing or newly recorded).</returns>
|
||||
public async Task<DateTimeOffset> EnsureInstallTimestampAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (_cachedTimestamp.HasValue)
|
||||
{
|
||||
return _cachedTimestamp.Value;
|
||||
}
|
||||
|
||||
// Check if timestamp already exists
|
||||
if (File.Exists(_timestampFilePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
var content = await File.ReadAllTextAsync(_timestampFilePath, ct);
|
||||
if (DateTimeOffset.TryParse(content.Trim(), out var existing))
|
||||
{
|
||||
_cachedTimestamp = existing;
|
||||
_logger?.LogDebug("Existing install timestamp loaded: {Timestamp}", existing);
|
||||
return existing;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogWarning(ex, "Failed to read install timestamp file");
|
||||
}
|
||||
}
|
||||
|
||||
// Record new timestamp
|
||||
var timestamp = DateTimeOffset.UtcNow;
|
||||
try
|
||||
{
|
||||
var directory = Path.GetDirectoryName(_timestampFilePath);
|
||||
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
await File.WriteAllTextAsync(_timestampFilePath, timestamp.ToString("o"), ct);
|
||||
_cachedTimestamp = timestamp;
|
||||
_logger?.LogInformation("Install timestamp recorded: {Timestamp}", timestamp);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogWarning(ex, "Failed to persist install timestamp");
|
||||
_cachedTimestamp = timestamp;
|
||||
}
|
||||
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the install timestamp if available.
|
||||
/// </summary>
|
||||
/// <returns>The install timestamp or null if not yet recorded.</returns>
|
||||
public DateTimeOffset? GetInstallTimestamp()
|
||||
{
|
||||
if (_cachedTimestamp.HasValue)
|
||||
{
|
||||
return _cachedTimestamp.Value;
|
||||
}
|
||||
|
||||
if (File.Exists(_timestampFilePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
var content = File.ReadAllText(_timestampFilePath);
|
||||
if (DateTimeOffset.TryParse(content.Trim(), out var existing))
|
||||
{
|
||||
_cachedTimestamp = existing;
|
||||
return existing;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore read errors
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates duration from install to now.
|
||||
/// </summary>
|
||||
/// <returns>Duration since install, or null if not installed.</returns>
|
||||
public TimeSpan? GetTimeSinceInstall()
|
||||
{
|
||||
var installTime = GetInstallTimestamp();
|
||||
if (!installTime.HasValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return DateTimeOffset.UtcNow - installTime.Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// P0ProductMetrics.cs
|
||||
// Sprint: SPRINT_20260117_028_Telemetry_p0_metrics
|
||||
// Task: P0M-001 through P0M-004 - P0 Product Metrics
|
||||
// Description: P0 product-level metrics as defined in AI Economics Moat advisory
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
namespace StellaOps.Telemetry.Core;
|
||||
|
||||
/// <summary>
|
||||
/// P0 product-level metrics for tracking Stella Ops health and adoption.
|
||||
/// These metrics are the scoreboard - prioritize work that improves them.
|
||||
/// </summary>
|
||||
public sealed class P0ProductMetrics : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Meter name for P0 product metrics.
|
||||
/// </summary>
|
||||
public const string MeterName = "StellaOps.P0Metrics";
|
||||
|
||||
private readonly Meter _meter;
|
||||
private bool _disposed;
|
||||
|
||||
// P0M-001: Time to First Verified Release
|
||||
private readonly Histogram<double> _timeToFirstVerifiedRelease;
|
||||
|
||||
// P0M-002: Mean Time to Answer "Why Blocked"
|
||||
private readonly Histogram<double> _whyBlockedLatency;
|
||||
|
||||
// P0M-003: Support Minutes per Customer
|
||||
private readonly Counter<long> _supportBurdenMinutes;
|
||||
|
||||
// P0M-004: Determinism Regressions
|
||||
private readonly Counter<long> _determinismRegressions;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes P0 product metrics.
|
||||
/// </summary>
|
||||
public P0ProductMetrics()
|
||||
{
|
||||
_meter = new Meter(MeterName, "1.0.0");
|
||||
|
||||
// P0M-001: Time from fresh install to first successful verified promotion
|
||||
// Buckets: 5m, 15m, 30m, 1h, 2h, 4h, 8h, 24h, 48h, 168h (1 week)
|
||||
_timeToFirstVerifiedRelease = _meter.CreateHistogram<double>(
|
||||
name: "stella_time_to_first_verified_release_seconds",
|
||||
unit: "s",
|
||||
description: "Elapsed time from fresh install to first successful verified promotion");
|
||||
|
||||
// P0M-002: Time from block decision to user viewing explanation
|
||||
// Buckets: 1s, 5s, 30s, 1m, 5m, 15m, 1h, 4h, 24h
|
||||
_whyBlockedLatency = _meter.CreateHistogram<double>(
|
||||
name: "stella_why_blocked_latency_seconds",
|
||||
unit: "s",
|
||||
description: "Time from block decision to user viewing explanation");
|
||||
|
||||
// P0M-003: Accumulated support time per customer per month
|
||||
_supportBurdenMinutes = _meter.CreateCounter<long>(
|
||||
name: "stella_support_burden_minutes_total",
|
||||
unit: "min",
|
||||
description: "Accumulated support time per customer");
|
||||
|
||||
// P0M-004: Count of detected determinism failures
|
||||
_determinismRegressions = _meter.CreateCounter<long>(
|
||||
name: "stella_determinism_regressions_total",
|
||||
unit: "{regression}",
|
||||
description: "Count of detected determinism failures in production");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records time to first verified release for a tenant.
|
||||
/// Call this when a tenant completes their first successful verified promotion.
|
||||
/// </summary>
|
||||
/// <param name="durationSeconds">Time in seconds from install to first verified release.</param>
|
||||
/// <param name="tenant">Tenant identifier.</param>
|
||||
/// <param name="deploymentType">fresh or upgrade.</param>
|
||||
public void RecordTimeToFirstVerifiedRelease(
|
||||
double durationSeconds,
|
||||
string tenant,
|
||||
string deploymentType = "fresh")
|
||||
{
|
||||
_timeToFirstVerifiedRelease.Record(
|
||||
durationSeconds,
|
||||
new KeyValuePair<string, object?>("tenant", tenant),
|
||||
new KeyValuePair<string, object?>("deployment_type", deploymentType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records latency for "why blocked" explanation view.
|
||||
/// Call this when a user views a block explanation via CLI, UI, or API.
|
||||
/// </summary>
|
||||
/// <param name="durationSeconds">Time in seconds from block decision to explanation view.</param>
|
||||
/// <param name="tenant">Tenant identifier.</param>
|
||||
/// <param name="surface">Surface where explanation was viewed: cli, ui, api.</param>
|
||||
/// <param name="resolutionType">immediate (same session) or delayed (different session).</param>
|
||||
public void RecordWhyBlockedLatency(
|
||||
double durationSeconds,
|
||||
string tenant,
|
||||
string surface,
|
||||
string resolutionType = "immediate")
|
||||
{
|
||||
_whyBlockedLatency.Record(
|
||||
durationSeconds,
|
||||
new KeyValuePair<string, object?>("tenant", tenant),
|
||||
new KeyValuePair<string, object?>("surface", surface),
|
||||
new KeyValuePair<string, object?>("resolution_type", resolutionType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records support time spent on a customer.
|
||||
/// Call this when logging support events via CLI or API.
|
||||
/// </summary>
|
||||
/// <param name="minutes">Support time in minutes.</param>
|
||||
/// <param name="tenant">Tenant identifier.</param>
|
||||
/// <param name="category">Support category: install, config, policy, integration, bug, other.</param>
|
||||
/// <param name="month">Month in YYYY-MM format.</param>
|
||||
public void RecordSupportBurden(
|
||||
long minutes,
|
||||
string tenant,
|
||||
string category,
|
||||
string month)
|
||||
{
|
||||
_supportBurdenMinutes.Add(
|
||||
minutes,
|
||||
new KeyValuePair<string, object?>("tenant", tenant),
|
||||
new KeyValuePair<string, object?>("category", category),
|
||||
new KeyValuePair<string, object?>("month", month));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records a determinism regression detection.
|
||||
/// Call this when determinism verification fails.
|
||||
/// </summary>
|
||||
/// <param name="tenant">Tenant identifier.</param>
|
||||
/// <param name="component">Component where regression occurred: scanner, policy, attestor, export.</param>
|
||||
/// <param name="severity">Fidelity tier: bitwise, semantic, policy.</param>
|
||||
public void RecordDeterminismRegression(
|
||||
string tenant,
|
||||
string component,
|
||||
string severity)
|
||||
{
|
||||
_determinismRegressions.Add(
|
||||
1,
|
||||
new KeyValuePair<string, object?>("tenant", tenant),
|
||||
new KeyValuePair<string, object?>("component", component),
|
||||
new KeyValuePair<string, object?>("severity", severity));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_meter.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user