Class SchemaChangeLogStore

Namespace
Kuestenlogik.Bowire
Assembly
Kuestenlogik.Bowire.dll

Disk-backed store for a workspace's schema-change log (#185). The schema watch (#48) diffs two discovery results in the browser; this store is what makes the result durable — the client posts each poll's delta here so "what changed while I was at lunch" survives a reload, a browser reset, and other clients of the same workspace. Entries older than RetentionDays are pruned on every write and filtered on every read.

public sealed class SchemaChangeLogStore
Inheritance
SchemaChangeLogStore
Inherited Members

Remarks

Layout: one file per workspace at workspaces/<wsId>/schema-changes/log.json, resolved through GetWorkspacePath(string, string?, string) so the per-identity / per-storage-root seams (#28, #212) keep working. Registered as a DI singleton (not a static class like the older stores) — the instance owns its file lock and test-path override.

Constructors

SchemaChangeLogStore()

public SchemaChangeLogStore()

Fields

DedupWindow

Two clients watching the same workspace both observe — and both post — the same schema transition. An incoming entry that matches an existing one (type + service + method + detail) stamped within this window is dropped as a duplicate observation, not a second change.

public static readonly TimeSpan DedupWindow

Field Value

TimeSpan

MaxDetailLength

public const int MaxDetailLength = 500

Field Value

int

MaxEntries

Hard cap on retained entries, newest win. A watch polling a schema under heavy churn every 5 s could otherwise grow the file without bound inside the retention window.

public const int MaxEntries = 500

Field Value

int

MaxNameLength

Per-string byte diet. A Detail is a one-line human summary and service/method names are identifiers — anything longer is truncated so a single client can't balloon the log file (which is re-read and re-written under the lock on every append, and shipped to every client on boot).

public const int MaxNameLength = 300

Field Value

int

RetentionDays

How long a change entry is retained.

public const int RetentionDays = 7

Field Value

int

Methods

Append(string, string?, IReadOnlyList<SchemaChangeEntry?>)

Append a batch of change entries (one schema-watch poll's delta), prune the retention window, and persist. Returns the resulting envelope. Throws ArgumentException on a null entry, an unknown change type or a missing service so a malformed POST can't corrupt the on-disk log.

public SchemaChangeLogEnvelope Append(string workspaceId, string? storageRoot, IReadOnlyList<SchemaChangeEntry?> entries)

Parameters

workspaceId string
storageRoot string
entries IReadOnlyList<SchemaChangeEntry>

Returns

SchemaChangeLogEnvelope

Remarks

The server stamps At on every entry, ignoring whatever the client sent. One clock authority keeps the retention window, the entry cap and the unread-vs-LastReadAt compare consistent — a browser clock ahead of the server would otherwise produce entries that can never be marked read, and one behind would produce entries born read.

Load(string, string?)

Load the change log, dropping entries outside the retention window. Returns the empty envelope when the file is missing or corrupt — never throws so the UI keeps working.

public SchemaChangeLogEnvelope Load(string workspaceId, string? storageRoot)

Parameters

workspaceId string
storageRoot string

Returns

SchemaChangeLogEnvelope

MarkRead(string, string?)

Move the read watermark to now — every current entry becomes "read". Returns the resulting envelope.

public SchemaChangeLogEnvelope MarkRead(string workspaceId, string? storageRoot)

Parameters

workspaceId string
storageRoot string

Returns

SchemaChangeLogEnvelope

OverrideStorePathForTesting(string?)

Pin the store to a fixed file for tests, bypassing the workspace-path resolution. Pass null to restore.

public void OverrideStorePathForTesting(string? path)

Parameters

path string