fd4be80913
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
47 lines
2.3 KiB
C#
47 lines
2.3 KiB
C#
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
|
|
|
/// <summary>
|
|
/// Hash-chain tamper evidence for the central <c>AuditLog</c> table (audit roadmap T1,
|
|
/// alog.md §11.4, ScadaBridge#41).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// One chain per <b>partition</b> = UTC calendar month of <c>OccurredAtUtc</c> (the same
|
|
/// month the <c>ps_AuditLog_Month</c> scheme partitions on). Each chained row carries a
|
|
/// <c>ChainSeq</c> (1, 2, 3 … within its month) and
|
|
/// <c>RowHash = SHA-256(prev.RowHash || canonical(row))</c>, where <c>prev</c> is the row
|
|
/// at <c>ChainSeq - 1</c> (32 zero bytes for the first). The canonical serialisation
|
|
/// covers every writable column plus the month and sequence number.
|
|
/// </para>
|
|
/// <para>
|
|
/// Rows are linked by a <b>chaining job</b>, not by the hot-path writers: two central
|
|
/// nodes and the site-ingest path all INSERT concurrently, so a write-time chain would
|
|
/// need a global serialisation point on every audit write. The job serialises itself
|
|
/// with an exclusive SQL Server application lock, so even a dual-active central pair
|
|
/// produces one gap-free chain per month.
|
|
/// </para>
|
|
/// </remarks>
|
|
public interface IAuditLogHashChain
|
|
{
|
|
/// <summary>
|
|
/// Links up to <paramref name="batchSize"/> unchained rows (oldest
|
|
/// <c>OccurredAtUtc</c> first) onto the end of their month's chain, in one transaction.
|
|
/// Returns immediately with <see cref="AuditChainBatchResult.LockBusy"/> set when another
|
|
/// chainer holds the chain lock.
|
|
/// </summary>
|
|
/// <param name="batchSize">Maximum rows to chain in this pass.</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
/// <returns>How many rows were chained, and whether the lock was busy.</returns>
|
|
Task<AuditChainBatchResult> ChainPendingAsync(int batchSize, CancellationToken ct = default);
|
|
|
|
/// <summary>
|
|
/// Verifies one month's chain end to end and reports the first break.
|
|
/// </summary>
|
|
/// <param name="monthStartUtc">The first instant of the month (UTC, day 1, midnight).</param>
|
|
/// <param name="ct">Cancellation token.</param>
|
|
/// <returns>The verification result.</returns>
|
|
Task<AuditChainVerificationResult> VerifyMonthAsync(DateTime monthStartUtc, CancellationToken ct = default);
|
|
}
|