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
- 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.
284 lines
9.1 KiB
C#
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);
|
|
}
|
|
}
|