feat(audit): SHA-256 hash chain over the central AuditLog (ScadaBridge#41)
Audit roadmap T1 (alog.md §11.4): one chain per UTC calendar month of OccurredAtUtc. Each chained row carries ChainSeq (gap-free within its month) and RowHash = SHA-256(prev.RowHash || canonical(row)); the v1 canonical form (AuditChainHasher) is pinned by a known-answer vector computed independently. - Migration AddAuditLogHashChain: nullable ChainSeq/RowHash on dbo.AuditLog (metadata-only add), AuditLogChainHead (per-month chain end + partition-purge checkpoint anchor), AuditLogChainTombstone, two partition-aligned filtered indexes, scadabridge_audit_chainer role with a column-level UPDATE grant. Production script docs/plans/sql/AddAuditLogHashChain.sql. - AuditLogHashChain chains unchained rows oldest month first under an exclusive sp_getapplock (zero timeout, so a dual-active central pair still yields one chain), with an optimistic head check and a ChainSeq IS NULL row-count check that roll a pass back instead of leaving a gap. The ingest path is untouched. - Purges keep the chain verifiable: the partition switch records the month's checkpoint anchor and drops its tombstones; the per-channel DELETE writes a tombstone per chained row in the same transaction (OUTPUT INTO). Both, and the SourceNode backfill, hold the chain lock; the backfill skips chained rows. The switch staging table gains the two new columns. - AuditChainVerifier reports the first break (partition, ChainSeq, EventId, kind) for edited, deleted, reordered rows and tail truncation. - AuditLogHashChainService (central, default OFF via AuditLog:HashChain). Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -139,6 +139,10 @@ public class ScadaBridgeDbContext : DbContext, IDataProtectionKeyContext
|
||||
public DbSet<AuditLogEntry> AuditLogEntries => Set<AuditLogEntry>();
|
||||
/// <summary>Gets the set of audit log rows (central <c>dbo.AuditLog</c> persistence shape; mapped to/from the canonical record at the repository boundary).</summary>
|
||||
public DbSet<AuditLogRow> AuditLogs => Set<AuditLogRow>();
|
||||
/// <summary>Gets the per-month AuditLog hash-chain heads (ScadaBridge#41).</summary>
|
||||
public DbSet<AuditLogChainHead> AuditLogChainHeads => Set<AuditLogChainHead>();
|
||||
/// <summary>Gets the AuditLog hash-chain purge tombstones (ScadaBridge#41).</summary>
|
||||
public DbSet<AuditLogChainTombstone> AuditLogChainTombstones => Set<AuditLogChainTombstone>();
|
||||
/// <summary>Gets the set of site calls.</summary>
|
||||
public DbSet<SiteCall> SiteCalls => Set<SiteCall>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user