Skip to content

Commit 5db9f8b

Browse files
authored
Merge pull request #34 from f2calv/f2calv/2026-06-updates
xUnit v3 migration, source-generated logging, scoped Copilot instructions & dependency bumps
2 parents d5fa975 + 1cb3f4f commit 5db9f8b

55 files changed

Lines changed: 828 additions & 589 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/copilot-instructions.md

Lines changed: 36 additions & 440 deletions
Large diffs are not rendered by default.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
description: 'appsettings.json configuration sync rules for IAppConfig properties.'
3+
applyTo: '**/appsettings*.json'
4+
---
5+
6+
# Configuration
7+
8+
## Configuration Sync
9+
10+
- Configuration properties (e.g. polling delays, feature flags, thresholds) are defined with sensible defaults directly on the `IAppConfig` record/class. Having defaults in the record means the application works out-of-the-box, but every property can be overridden via `appsettings*.json` or directly with environment variables in Kubernetes deployments (using the standard `CasCap__SectionName__PropertyName` double-underscore convention).
11+
- When adding, renaming, or removing a property on any class or record that implements `IAppConfig` — or on any child/nested type reachable from such a class — update **all** `appsettings*.json` files (`appsettings.json`, `appsettings.Development.json`, and any other environment-specific variants) in the same commit. This includes adding new keys with sensible defaults, renaming keys to match the new property name, and removing keys for deleted properties. If the new property's record default is already the desired value for all environments, the `appsettings*.json` files do not need a new entry — only add one when an environment-specific override is required.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
description: 'Azure Table Storage entity and Redis key-naming conventions.'
3+
applyTo: '**/*.cs'
4+
---
5+
6+
# Cloud (Azure)
7+
8+
## Azure Table Storage
9+
10+
- **Column naming**: For high-volume line-item/reading entities where many thousands of rows are retrieved, use ultra-short column names (even single letters) to reduce payload size and improve retrieval speed. This optimization is not needed for low-volume snapshot/summary entities where readability is more important.
11+
- **Expanded accessor properties**: High-volume `ITableEntity` reading entities with ultra-short column names must also expose full-name expression-bodied read-only accessor properties decorated with `[IgnoreDataMember]` for developer ergonomics (e.g. `public string IpAddress => ip;`). This provides readable access without adding storage overhead.
12+
- **ReadingEntity constructors**: Reading entity constructors must accept the domain event record directly (e.g. `SensorReadingEntity(SensorEvent evt)`) — never individual properties unpacked at the call site. The constructor is responsible for mapping event properties to ultra-short column fields.
13+
- **SnapshotEntity constructors**: When a snapshot entity's `RowKey` is always derivable from a property on the event (e.g. `DeviceId`, `NodeName`), the constructor should accept `(string partitionKey, TEvent evt)` and derive `RowKey` internally. Only pass `RowKey` as a separate parameter when it is a constant not present on the event (e.g. `"latest"` for single-device tables).
14+
- **Entity-scoped PartitionKey**: When a reading entity stores data from multiple devices or sensors, `PartitionKey` should be the device/entity identifier (`DeviceId`, `NodeName`, `CameraId`, `DatapointId`, etc.) rather than a date string. Date-based PK (`yyMMdd`) is acceptable only for single-device-per-table scenarios.
15+
- **Table name versioning**: When changing the `PartitionKey` or `RowKey` structure of an Azure Table entity, increment the table name version suffix (e.g. `readingsv1``readingsv2`). Old data under the previous key structure is orphaned by design — treat structural changes as fresh-start migrations.
16+
17+
## Redis Key Naming
18+
19+
When a project uses a feature enum (e.g. `AppFeature`) to gate services, derive the domain segment of every Redis key from the **lowercase enum member name** so that key prefixes stay tightly coupled to the feature taxonomy (e.g. `AppFeature.Billing``billing:`, `AppFeature.Notifications``notifications:`).
20+
21+
- **All lowercase**, colon-separated segments: `{domain}:{type}:{detail}`.
22+
- **Domain** — derived from the feature enum member name via `nameof(AppFeature.Member).ToLowerInvariant()`. Cross-feature keys use a functional domain (e.g. `comms`, `lock`, `stats`).
23+
- **Standard type segments**:
24+
25+
| Segment | Redis Type | Purpose |
26+
| --- | --- | --- |
27+
| `snapshot` | Hash | Current state (field per entity) |
28+
| `series` | Sorted Set | Time-series readings (score = UTC ticks) |
29+
| `stream` | Stream | Event/message streams |
30+
| `cache` | String | Temporary data with TTL |
31+
| `lock` | String | Distributed locks (Redlock) |
32+
| `stats` | Hash | Observability / call counters with TTL |
33+
34+
- **Detail** — further qualifiers such as entity IDs, date partitions (`{yyMMdd}`, `{yyyy-MM-dd}`), or sub-categories (e.g. `values`, `timestamps`).
35+
- **Consumer groups** — use `{domain}:{role}` format (e.g. `billing:processors`, `comms:agents`).
36+
- **Stats keys** — date-partitioned with a 7-day TTL: `stats:{domain}:{yyyy-MM-dd}`. Hash fields are the method or operation names.
37+
- **Lock keys**`lock:{domain}:{resource}` format string, configured via `RedisKeyFormat` in `appsettings.json`.
38+
- **Config-driven keys**: Snapshot and series key names should be stored in `appsettings.json` per-sink settings (via a settings dictionary), not hardcoded in service code. This allows key migration by config change alone.
39+
- **Key sync on rename**: When renaming Redis keys, update `appsettings*.json`, Lua scripts, and any C# code that constructs or references the old key name in the same commit. Existing Redis data under the old key will be orphaned — treat key renames as a fresh-start migration.

0 commit comments

Comments
 (0)