Files
git.stella-ops.org/src/Findings/StellaOps.Findings.Ledger/Observability/LedgerTimeline.cs
StellaOps Bot 965cbf9574
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Add unit tests for PhpFrameworkSurface and PhpPharScanner
- Implement comprehensive tests for PhpFrameworkSurface, covering scenarios such as empty surfaces, presence of routes, controllers, middlewares, CLI commands, cron jobs, and event listeners.
- Validate metadata creation for route counts, HTTP methods, protected and public routes, and route patterns.
- Introduce tests for PhpPharScanner, including handling of non-existent files, null or empty paths, invalid PHAR files, and minimal PHAR structures.
- Ensure correct computation of SHA256 for valid PHAR files and validate the properties of PhpPharArchive, PhpPharEntry, and PhpPharScanResult.
2025-12-07 13:44:13 +02:00

284 lines
9.1 KiB
C#

using System.Diagnostics;
using Microsoft.Extensions.Logging;
using StellaOps.Findings.Ledger.Domain;
using StellaOps.Findings.Ledger.Infrastructure.Exports;
namespace StellaOps.Findings.Ledger.Observability;
/// <summary>
/// Emits structured timeline events for ledger operations.
/// Currently materialised as structured logs; can be swapped for event sink later.
/// </summary>
internal static class LedgerTimeline
{
private static readonly EventId LedgerAppended = new(6101, "ledger.event.appended");
private static readonly EventId ProjectionUpdated = new(6201, "ledger.projection.updated");
private static readonly EventId OrchestratorExport = new(6301, "ledger.export.recorded");
private static readonly EventId AirgapImport = new(6401, "ledger.airgap.imported");
private static readonly EventId EvidenceSnapshotLinkedEvent = new(6501, "ledger.evidence.snapshot_linked");
private static readonly EventId AirgapTimelineImpactEvent = new(6601, "ledger.airgap.timeline_impact");
private static readonly EventId AttestationPointerLinkedEvent = new(6701, "ledger.attestation.pointer_linked");
private static readonly EventId SnapshotCreatedEvent = new(6801, "ledger.snapshot.created");
private static readonly EventId SnapshotDeletedEvent = new(6802, "ledger.snapshot.deleted");
private static readonly EventId TimeTravelQueryEvent = new(6803, "ledger.timetravel.query");
private static readonly EventId ReplayCompletedEvent = new(6804, "ledger.replay.completed");
private static readonly EventId DiffComputedEvent = new(6805, "ledger.diff.computed");
public static void EmitLedgerAppended(ILogger logger, LedgerEventRecord record, string? evidenceBundleRef = null)
{
if (logger is null)
{
return;
}
var traceId = Activity.Current?.TraceId.ToHexString() ?? string.Empty;
logger.LogInformation(
LedgerAppended,
"timeline ledger.event.appended tenant={Tenant} chain={ChainId} seq={Sequence} event={EventId} type={EventType} policy={PolicyVersion} finding={FindingId} trace={TraceId} evidence_ref={EvidenceRef}",
record.TenantId,
record.ChainId,
record.SequenceNumber,
record.EventId,
record.EventType,
record.PolicyVersion,
record.FindingId,
traceId,
evidenceBundleRef ?? record.EvidenceBundleReference ?? string.Empty);
}
public static void EmitProjectionUpdated(
ILogger logger,
LedgerEventRecord record,
string? evaluationStatus,
string? evidenceBundleRef = null)
{
if (logger is null)
{
return;
}
var traceId = Activity.Current?.TraceId.ToHexString() ?? string.Empty;
logger.LogInformation(
ProjectionUpdated,
"timeline ledger.projection.updated tenant={Tenant} chain={ChainId} seq={Sequence} event={EventId} policy={PolicyVersion} finding={FindingId} status={Status} trace={TraceId} evidence_ref={EvidenceRef}",
record.TenantId,
record.ChainId,
record.SequenceNumber,
record.EventId,
record.PolicyVersion,
record.FindingId,
evaluationStatus ?? string.Empty,
traceId,
evidenceBundleRef ?? record.EvidenceBundleReference ?? string.Empty);
}
public static void EmitOrchestratorExport(ILogger logger, OrchestratorExportRecord record)
{
if (logger is null)
{
return;
}
logger.LogInformation(
OrchestratorExport,
"timeline ledger.export.recorded tenant={Tenant} run={RunId} artifact={ArtifactHash} policy={PolicyHash} status={Status} merkle_root={MerkleRoot}",
record.TenantId,
record.RunId,
record.ArtifactHash,
record.PolicyHash,
record.Status,
record.MerkleRoot);
}
public static void EmitAirgapImport(ILogger logger, string tenantId, string bundleId, string merkleRoot, Guid? ledgerEventId)
{
if (logger is null)
{
return;
}
logger.LogInformation(
AirgapImport,
"timeline ledger.airgap.imported tenant={Tenant} bundle={BundleId} merkle_root={MerkleRoot} ledger_event={LedgerEvent}",
tenantId,
bundleId,
merkleRoot,
ledgerEventId?.ToString() ?? string.Empty);
}
public static void EmitEvidenceSnapshotLinked(ILogger logger, string tenantId, string findingId, string bundleUri, string dsseDigest)
{
if (logger is null)
{
return;
}
logger.LogInformation(
EvidenceSnapshotLinkedEvent,
"timeline ledger.evidence.snapshot_linked tenant={Tenant} finding={FindingId} bundle_uri={BundleUri} dsse_digest={DsseDigest}",
tenantId,
findingId,
bundleUri,
dsseDigest);
}
public static void EmitAirgapTimelineImpact(
ILogger logger,
string tenantId,
string bundleId,
int newFindings,
int resolvedFindings,
int criticalDelta,
DateTimeOffset timeAnchor,
bool sealedMode)
{
if (logger is null)
{
return;
}
logger.LogInformation(
AirgapTimelineImpactEvent,
"timeline ledger.airgap.timeline_impact tenant={Tenant} bundle={BundleId} new_findings={NewFindings} resolved_findings={ResolvedFindings} critical_delta={CriticalDelta} time_anchor={TimeAnchor} sealed_mode={SealedMode}",
tenantId,
bundleId,
newFindings,
resolvedFindings,
criticalDelta,
timeAnchor.ToString("O"),
sealedMode);
}
public static void EmitAttestationPointerLinked(
ILogger logger,
string tenantId,
string findingId,
Guid pointerId,
string attestationType,
string digest)
{
if (logger is null)
{
return;
}
logger.LogInformation(
AttestationPointerLinkedEvent,
"timeline ledger.attestation.pointer_linked tenant={Tenant} finding={FindingId} pointer={PointerId} attestation_type={AttestationType} digest={Digest}",
tenantId,
findingId,
pointerId,
attestationType,
digest);
}
public static void EmitSnapshotCreated(
ILogger logger,
string tenantId,
Guid snapshotId,
long sequenceNumber,
long findingsCount)
{
if (logger is null)
{
return;
}
logger.LogInformation(
SnapshotCreatedEvent,
"timeline ledger.snapshot.created tenant={Tenant} snapshot={SnapshotId} sequence={SequenceNumber} findings_count={FindingsCount}",
tenantId,
snapshotId,
sequenceNumber,
findingsCount);
}
public static void EmitSnapshotDeleted(
ILogger logger,
string tenantId,
Guid snapshotId)
{
if (logger is null)
{
return;
}
logger.LogInformation(
SnapshotDeletedEvent,
"timeline ledger.snapshot.deleted tenant={Tenant} snapshot={SnapshotId}",
tenantId,
snapshotId);
}
public static void EmitTimeTravelQuery(
ILogger logger,
string tenantId,
string entityType,
long atSequence,
int resultCount)
{
if (logger is null)
{
return;
}
logger.LogInformation(
TimeTravelQueryEvent,
"timeline ledger.timetravel.query tenant={Tenant} entity_type={EntityType} at_sequence={AtSequence} result_count={ResultCount}",
tenantId,
entityType,
atSequence,
resultCount);
}
public static void EmitReplayCompleted(
ILogger logger,
string tenantId,
long fromSequence,
long toSequence,
int eventsCount,
long durationMs)
{
if (logger is null)
{
return;
}
logger.LogInformation(
ReplayCompletedEvent,
"timeline ledger.replay.completed tenant={Tenant} from_sequence={FromSequence} to_sequence={ToSequence} events_count={EventsCount} duration_ms={DurationMs}",
tenantId,
fromSequence,
toSequence,
eventsCount,
durationMs);
}
public static void EmitDiffComputed(
ILogger logger,
string tenantId,
long fromSequence,
long toSequence,
int added,
int modified,
int removed)
{
if (logger is null)
{
return;
}
logger.LogInformation(
DiffComputedEvent,
"timeline ledger.diff.computed tenant={Tenant} from_sequence={FromSequence} to_sequence={ToSequence} added={Added} modified={Modified} removed={Removed}",
tenantId,
fromSequence,
toSequence,
added,
modified,
removed);
}
}